fullstack-classic

Apply when working with classic fullstack patterns including jQuery AJAX, form handling, and C# MVC integration

16 stars

Best use case

fullstack-classic is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Apply when working with classic fullstack patterns including jQuery AJAX, form handling, and C# MVC integration

Teams using fullstack-classic should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/fullstack-classic/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/fullstack-web/fullstack-classic/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/fullstack-classic/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How fullstack-classic Compares

Feature / Agentfullstack-classicStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Apply when working with classic fullstack patterns including jQuery AJAX, form handling, and C# MVC integration

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Classic Fullstack Integration Patterns

## Form Handling

### Server-Side Form Processing

```csharp
// Controller
public class ContactController : Controller
{
    private readonly IContactService _contactService;
    private readonly ILogger<ContactController> _logger;

    public ContactController(
        IContactService contactService,
        ILogger<ContactController> logger)
    {
        _contactService = contactService;
        _logger = logger;
    }

    [HttpGet]
    public IActionResult Index()
    {
        return View(new ContactFormModel());
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Index(ContactFormModel model, CancellationToken ct)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        try
        {
            await _contactService.ProcessContactAsync(model, ct);
            TempData["SuccessMessage"] = "Thank you for your message!";
            return RedirectToAction(nameof(Index));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process contact form");
            ModelState.AddModelError("", "An error occurred. Please try again.");
            return View(model);
        }
    }
}
```

### Razor Form

```cshtml
@model ContactFormModel

@if (TempData["SuccessMessage"] != null)
{
    <div class="alert alert-success">
        @TempData["SuccessMessage"]
    </div>
}

<form asp-action="Index" method="post">
    @Html.AntiForgeryToken()
    
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <label asp-for="Email"></label>
        <input asp-for="Email" class="form-control" type="email" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <label asp-for="Message"></label>
        <textarea asp-for="Message" class="form-control" rows="5"></textarea>
        <span asp-validation-for="Message" class="text-danger"></span>
    </div>
    
    <button type="submit" class="btn btn-primary">Send Message</button>
</form>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}
```

## jQuery AJAX Integration

### AJAX Form Submission

```javascript
// JavaScript
$(document).ready(function() {
    $('#contact-form').on('submit', function(e) {
        e.preventDefault();
        
        var $form = $(this);
        var $submitBtn = $form.find('button[type="submit"]');
        var $result = $('#form-result');
        
        // Disable button and show loading state
        $submitBtn.prop('disabled', true).text('Sending...');
        $result.empty();
        
        $.ajax({
            url: $form.attr('action'),
            type: 'POST',
            data: $form.serialize(),
            success: function(response) {
                if (response.success) {
                    $result.html('<div class="alert alert-success">' + response.message + '</div>');
                    $form[0].reset();
                } else {
                    showValidationErrors(response.errors);
                }
            },
            error: function(xhr, status, error) {
                $result.html('<div class="alert alert-danger">An error occurred. Please try again.</div>');
                console.error('Form submission failed:', error);
            },
            complete: function() {
                $submitBtn.prop('disabled', false).text('Send Message');
            }
        });
    });
    
    function showValidationErrors(errors) {
        // Clear previous errors
        $('.field-validation-error').text('');
        $('.input-validation-error').removeClass('input-validation-error');
        
        // Show new errors
        $.each(errors, function(field, messages) {
            var $field = $('[name="' + field + '"]');
            $field.addClass('input-validation-error');
            $field.siblings('.field-validation-error').text(messages.join(', '));
        });
    }
});
```

### AJAX Controller Action

```csharp
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SubmitAjax(ContactFormModel model, CancellationToken ct)
{
    if (!ModelState.IsValid)
    {
        var errors = ModelState
            .Where(x => x.Value.Errors.Count > 0)
            .ToDictionary(
                kvp => kvp.Key,
                kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
            );
            
        return Json(new { success = false, errors });
    }

    try
    {
        await _contactService.ProcessContactAsync(model, ct);
        return Json(new { success = true, message = "Thank you for your message!" });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Failed to process contact form");
        return Json(new { 
            success = false, 
            errors = new { General = new[] { "An error occurred. Please try again." } }
        });
    }
}
```

## Anti-Forgery Token Handling

### Include Token in AJAX Requests

```javascript
// Setup for all AJAX requests
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (settings.type === 'POST' || settings.type === 'PUT' || settings.type === 'DELETE') {
            var token = $('input[name="__RequestVerificationToken"]').val();
            if (token) {
                xhr.setRequestHeader('RequestVerificationToken', token);
            }
        }
    }
});

// Or include in data for form-encoded requests
$.ajax({
    url: '/api/items',
    type: 'POST',
    data: {
        __RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val(),
        name: 'New Item'
    }
});
```

### Token in Layout

```cshtml
@* Add to _Layout.cshtml for global availability *@
<form id="__AjaxAntiForgeryForm" action="#" method="post">
    @Html.AntiForgeryToken()
</form>
```

```javascript
// Get token from hidden form
function getAntiForgeryToken() {
    return $('#__AjaxAntiForgeryForm input[name="__RequestVerificationToken"]').val();
}
```

## Loading Content Dynamically

### Partial View Loading

```csharp
// Controller
[HttpGet]
public async Task<IActionResult> LoadProducts(
    int page = 1, 
    string category = null,
    CancellationToken ct = default)
{
    var products = await _productService.GetPagedAsync(page, 12, category, ct);
    return PartialView("_ProductGrid", products);
}

[HttpGet]
public async Task<IActionResult> ProductDetails(Guid id, CancellationToken ct)
{
    var product = await _productService.GetByIdAsync(id, ct);
    if (product == null)
    {
        return NotFound();
    }
    return PartialView("_ProductDetails", product);
}
```

```javascript
// Load more products
$('#load-more').on('click', function() {
    var $btn = $(this);
    var page = parseInt($btn.data('page')) + 1;
    var category = $btn.data('category');
    
    $btn.prop('disabled', true).text('Loading...');
    
    $.get('/Products/LoadProducts', { page: page, category: category })
        .done(function(html) {
            $('#product-grid').append(html);
            $btn.data('page', page);
        })
        .fail(function() {
            alert('Failed to load products');
        })
        .always(function() {
            $btn.prop('disabled', false).text('Load More');
        });
});

// Load product details in modal
$(document).on('click', '[data-product-details]', function(e) {
    e.preventDefault();
    var productId = $(this).data('product-details');
    
    $.get('/Products/ProductDetails/' + productId)
        .done(function(html) {
            $('#modal-content').html(html);
            $('#product-modal').modal('show');
        })
        .fail(function() {
            alert('Failed to load product details');
        });
});
```

## Search with Debounce

### JavaScript

```javascript
var MYAPP = MYAPP || {};

MYAPP.search = (function($) {
    var debounceTimer;
    var $input;
    var $results;
    var minChars = 3;
    var debounceDelay = 300;
    
    function init() {
        $input = $('#search-input');
        $results = $('#search-results');
        
        $input.on('keyup', function() {
            var query = $(this).val().trim();
            
            clearTimeout(debounceTimer);
            
            if (query.length < minChars) {
                $results.empty().hide();
                return;
            }
            
            debounceTimer = setTimeout(function() {
                performSearch(query);
            }, debounceDelay);
        });
        
        // Close results when clicking outside
        $(document).on('click', function(e) {
            if (!$(e.target).closest('.search-container').length) {
                $results.hide();
            }
        });
    }
    
    function performSearch(query) {
        $results.html('<div class="search-loading">Searching...</div>').show();
        
        $.get('/Search/Results', { q: query })
            .done(function(html) {
                $results.html(html).show();
            })
            .fail(function() {
                $results.html('<div class="search-error">Search failed</div>');
            });
    }
    
    return { init: init };
})(jQuery);

$(document).ready(function() {
    MYAPP.search.init();
});
```

### Controller

```csharp
[HttpGet]
public async Task<IActionResult> Results(string q, CancellationToken ct)
{
    if (string.IsNullOrWhiteSpace(q) || q.Length < 3)
    {
        return PartialView("_NoResults");
    }

    var results = await _searchService.SearchAsync(q, maxResults: 10, ct);
    return PartialView("_SearchResults", results);
}
```

## Pagination

### Controller

```csharp
[HttpGet]
public async Task<IActionResult> Index(int page = 1, CancellationToken ct = default)
{
    const int pageSize = 12;
    var result = await _productService.GetPagedAsync(page, pageSize, ct);
    
    ViewBag.CurrentPage = page;
    ViewBag.TotalPages = result.TotalPages;
    ViewBag.HasPrevious = page > 1;
    ViewBag.HasNext = page < result.TotalPages;
    
    return View(result.Items);
}
```

### Razor Partial

```cshtml
@* _Pagination.cshtml *@
@{
    var currentPage = (int)ViewBag.CurrentPage;
    var totalPages = (int)ViewBag.TotalPages;
    var hasPrevious = (bool)ViewBag.HasPrevious;
    var hasNext = (bool)ViewBag.HasNext;
}

@if (totalPages > 1)
{
    <nav aria-label="Page navigation">
        <ul class="pagination">
            <li class="page-item @(!hasPrevious ? "disabled" : "")">
                <a class="page-link" 
                   asp-action="Index" 
                   asp-route-page="@(currentPage - 1)"
                   aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
            
            @for (int i = 1; i <= totalPages; i++)
            {
                <li class="page-item @(i == currentPage ? "active" : "")">
                    <a class="page-link" asp-action="Index" asp-route-page="@i">@i</a>
                </li>
            }
            
            <li class="page-item @(!hasNext ? "disabled" : "")">
                <a class="page-link" 
                   asp-action="Index" 
                   asp-route-page="@(currentPage + 1)"
                   aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        </ul>
    </nav>
}
```

### AJAX Pagination

```javascript
$(document).on('click', '.pagination a', function(e) {
    e.preventDefault();
    
    var url = $(this).attr('href');
    
    $.get(url)
        .done(function(html) {
            $('#content-container').html(html);
            // Update browser URL without reload
            history.pushState(null, '', url);
        })
        .fail(function() {
            alert('Failed to load page');
        });
});

// Handle browser back/forward
$(window).on('popstate', function() {
    $.get(location.href)
        .done(function(html) {
            $('#content-container').html(html);
        });
});
```

## File Upload

### Razor Form

```cshtml
<form asp-action="Upload" method="post" enctype="multipart/form-data">
    @Html.AntiForgeryToken()
    
    <div class="form-group">
        <label for="file">Select file</label>
        <input type="file" name="file" id="file" class="form-control-file" accept=".jpg,.png,.pdf" />
        <small class="form-text text-muted">Max size: 5MB. Allowed: JPG, PNG, PDF</small>
    </div>
    
    <button type="submit" class="btn btn-primary">Upload</button>
</form>
```

### Controller

```csharp
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Upload(IFormFile file, CancellationToken ct)
{
    if (file == null || file.Length == 0)
    {
        ModelState.AddModelError("file", "Please select a file");
        return View();
    }

    if (file.Length > 5 * 1024 * 1024) // 5MB
    {
        ModelState.AddModelError("file", "File size cannot exceed 5MB");
        return View();
    }

    var allowedExtensions = new[] { ".jpg", ".png", ".pdf" };
    var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
    if (!allowedExtensions.Contains(extension))
    {
        ModelState.AddModelError("file", "Invalid file type");
        return View();
    }

    var fileName = $"{Guid.NewGuid()}{extension}";
    var filePath = Path.Combine(_uploadPath, fileName);

    using (var stream = new FileStream(filePath, FileMode.Create))
    {
        await file.CopyToAsync(stream, ct);
    }

    TempData["SuccessMessage"] = "File uploaded successfully";
    return RedirectToAction(nameof(Index));
}
```

### AJAX File Upload

```javascript
$('#upload-form').on('submit', function(e) {
    e.preventDefault();
    
    var formData = new FormData(this);
    var $progress = $('#upload-progress');
    var $progressBar = $progress.find('.progress-bar');
    
    $progress.show();
    
    $.ajax({
        url: $(this).attr('action'),
        type: 'POST',
        data: formData,
        processData: false,
        contentType: false,
        xhr: function() {
            var xhr = new window.XMLHttpRequest();
            xhr.upload.addEventListener('progress', function(e) {
                if (e.lengthComputable) {
                    var percent = Math.round((e.loaded / e.total) * 100);
                    $progressBar.css('width', percent + '%').text(percent + '%');
                }
            });
            return xhr;
        },
        success: function(response) {
            if (response.success) {
                showSuccess('File uploaded successfully');
                $('#file-input').val('');
            } else {
                showError(response.message);
            }
        },
        error: function() {
            showError('Upload failed');
        },
        complete: function() {
            setTimeout(function() {
                $progress.hide();
                $progressBar.css('width', '0%').text('');
            }, 1000);
        }
    });
});
```

## Error Handling

### Global AJAX Error Handler

```javascript
$(document).ajaxError(function(event, xhr, settings, error) {
    if (xhr.status === 401) {
        // Redirect to login
        window.location.href = '/Account/Login?returnUrl=' + encodeURIComponent(window.location.pathname);
        return;
    }
    
    if (xhr.status === 403) {
        showError('You do not have permission to perform this action');
        return;
    }
    
    if (xhr.status === 404) {
        showError('The requested resource was not found');
        return;
    }
    
    if (xhr.status >= 500) {
        showError('A server error occurred. Please try again later.');
        return;
    }
    
    console.error('AJAX error:', settings.url, error);
});
```

### Display Errors from Server

```csharp
// Controller returning JSON errors
[HttpPost]
public IActionResult Process(ProcessRequest request)
{
    try
    {
        // Process...
        return Json(new { success = true });
    }
    catch (ValidationException ex)
    {
        return BadRequest(new { 
            success = false, 
            errors = ex.Errors 
        });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Processing failed");
        return StatusCode(500, new { 
            success = false, 
            message = "An unexpected error occurred" 
        });
    }
}
```

```javascript
$.ajax({
    url: '/api/process',
    type: 'POST',
    data: formData,
    success: function(response) {
        if (response.success) {
            showSuccess('Operation completed');
        }
    },
    error: function(xhr) {
        if (xhr.responseJSON) {
            if (xhr.responseJSON.errors) {
                displayFieldErrors(xhr.responseJSON.errors);
            } else if (xhr.responseJSON.message) {
                showError(xhr.responseJSON.message);
            }
        } else {
            showError('An error occurred');
        }
    }
});
```

## Session and State Management

### Server-Side Session

```csharp
// Store in session
HttpContext.Session.SetString("UserPreference", "dark");
HttpContext.Session.SetInt32("CartCount", 5);

// Complex objects
HttpContext.Session.SetString("Cart", JsonSerializer.Serialize(cart));

// Retrieve from session
var preference = HttpContext.Session.GetString("UserPreference");
var cartCount = HttpContext.Session.GetInt32("CartCount");
var cart = JsonSerializer.Deserialize<Cart>(HttpContext.Session.GetString("Cart"));
```

### Client-Side with Cookies

```javascript
// Set cookie
function setCookie(name, value, days) {
    var expires = '';
    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        expires = '; expires=' + date.toUTCString();
    }
    document.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/';
}

// Get cookie
function getCookie(name) {
    var nameEQ = name + '=';
    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
        var cookie = cookies[i].trim();
        if (cookie.indexOf(nameEQ) === 0) {
            return decodeURIComponent(cookie.substring(nameEQ.length));
        }
    }
    return null;
}

// Delete cookie
function deleteCookie(name) {
    setCookie(name, '', -1);
}
```

## Notification/Toast Messages

### JavaScript Toast System

```javascript
var MYAPP = MYAPP || {};

MYAPP.toast = (function($) {
    var $container;
    
    function init() {
        $container = $('<div id="toast-container"></div>').appendTo('body');
    }
    
    function show(message, type, duration) {
        type = type || 'info';
        duration = duration || 5000;
        
        var $toast = $('<div class="toast toast-' + type + '">' + 
            '<span class="toast-message">' + message + '</span>' +
            '<button class="toast-close">&times;</button>' +
            '</div>');
        
        $container.append($toast);
        
        setTimeout(function() {
            $toast.addClass('show');
        }, 10);
        
        var timer = setTimeout(function() {
            remove($toast);
        }, duration);
        
        $toast.find('.toast-close').on('click', function() {
            clearTimeout(timer);
            remove($toast);
        });
    }
    
    function remove($toast) {
        $toast.removeClass('show');
        setTimeout(function() {
            $toast.remove();
        }, 300);
    }
    
    function success(message) { show(message, 'success'); }
    function error(message) { show(message, 'error'); }
    function warning(message) { show(message, 'warning'); }
    function info(message) { show(message, 'info'); }
    
    return {
        init: init,
        show: show,
        success: success,
        error: error,
        warning: warning,
        info: info
    };
})(jQuery);

$(document).ready(function() {
    MYAPP.toast.init();
});
```

### CSS for Toasts

```scss
#toast-container {
    position: fixed;
    top: 20px;
    right: 20px;
    z-index: 9999;
}

.toast {
    min-width: 300px;
    padding: 15px 40px 15px 15px;
    margin-bottom: 10px;
    border-radius: 4px;
    opacity: 0;
    transform: translateX(100%);
    transition: all 0.3s ease;
    
    &.show {
        opacity: 1;
        transform: translateX(0);
    }
    
    &-success { background: #28a745; color: white; }
    &-error { background: #dc3545; color: white; }
    &-warning { background: #ffc107; color: #333; }
    &-info { background: #17a2b8; color: white; }
    
    &-close {
        position: absolute;
        top: 10px;
        right: 10px;
        background: none;
        border: none;
        color: inherit;
        font-size: 20px;
        cursor: pointer;
        opacity: 0.7;
        
        &:hover { opacity: 1; }
    }
}
```

Related Skills

fullstack-modern

16
from diegosouzapw/awesome-omni-skill

Apply when working with modern fullstack patterns including React/Vue, GraphQL, REST APIs, and headless architectures

senior-fullstack

16
from diegosouzapw/awesome-omni-skill

Fullstack development toolkit with project scaffolding for Next.js/FastAPI/MERN/Django stacks and code quality analysis. Use when scaffolding new projects, analyzing codebase quality, or implementing fullstack architecture patterns.

senior-fullstack-ai-engineer

16
from diegosouzapw/awesome-omni-skill

Senior full-stack developer with 10+ years of experience and AI engineering expertise. Builds production-ready applications using modern frameworks (Flask, FastAPI, React), AI/ML technologies (LLMs, RAG, model deployment), and cloud infrastructure. Use for all development tasks requiring full-stack and AI/ML implementation.

fullstack

16
from diegosouzapw/awesome-omni-skill

Use this skill when building web applications, React components, Next.js apps, APIs, databases, or doing rapid prototyping. Activates on mentions of React, Next.js, TypeScript, Node.js, Express, Fastify, PostgreSQL, MongoDB, Prisma, Drizzle, tRPC, REST API, GraphQL, authentication, server components, client components, SSR, SSG, ISR, or general web development.

fullstack-validation

16
from diegosouzapw/awesome-omni-skill

Comprehensive validation methodology for multi-component applications including backend, frontend, database, and infrastructure

fullstack-template-generator

16
from diegosouzapw/awesome-omni-skill

Generates a complete fullstack application template with Python FastAPI backend and React Vite frontend. Includes OpenAI ChatGPT integration, CORS configuration, comprehensive error handling, and a modern Tailwind CSS + shadcn/ui React UI. Use this skill when the user wants to bootstrap a new fullstack web application project with both API backend and web frontend components ready to go.

fullstack-mirror-arch

16
from diegosouzapw/awesome-omni-skill

풀스택 미러 아키텍처 규칙. BE↔FE 1:1 타입 동기화, 레이어 의존 규칙, barrel re-export, API 클라이언트 패턴, 상태관리 분리 규칙을 적용. 풀스택 프로젝트 설계 시 사용.

fullstack-guardian

16
from diegosouzapw/awesome-omni-skill

Use when implementing features across frontend and backend, building APIs with UI, or creating end-to-end data flows. Invoke for feature implementation, API development, UI building, cross-stack work.

fullstack-expertise

16
from diegosouzapw/awesome-omni-skill

Full-stack development expertise covering backend, frontend, database, DevOps, and testing domains

Fullstack Developer

16
from diegosouzapw/awesome-omni-skill

End-to-end feature expert specializing in frontend-backend integration, system architecture, and complete application development

fullstack-dev

16
from diegosouzapw/awesome-omni-skill

Comprehensive fullstack development skill combining architecture, testing, security, DevOps, and code quality best practices for building modern web applications from frontend to backend.

fullstack-backend-master

16
from diegosouzapw/awesome-omni-skill

Master-level fullstack software engineering with deep backend expertise. Use when building production-grade APIs, database architectures, authentication systems, microservices, or any backend-heavy application. Triggers on: (1) API design and implementation, (2) Database schema design and optimization, (3) Authentication/authorization systems, (4) System architecture decisions, (5) Performance optimization, (6) Error handling and logging, (7) Testing strategies, (8) DevOps and deployment, (9) Security hardening.