fullstack-classic

Classic Fullstack Integration Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "fullstack-classic" with this command: npx skills add twofoldtech-dakota/claude-marketplace/twofoldtech-dakota-claude-marketplace-fullstack-classic

Classic Fullstack Integration Patterns

Form Handling

Server-Side Form Processing

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

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

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

[HttpPost]
[ValidateAntiForgeryToken]
public async Task&#x3C;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

@model ContactFormModel

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

<form asp-action="Index" method="post"> @Html.AntiForgeryToken()

&#x3C;div asp-validation-summary="ModelOnly" class="text-danger">&#x3C;/div>

&#x3C;div class="form-group">
    &#x3C;label asp-for="Name">&#x3C;/label>
    &#x3C;input asp-for="Name" class="form-control" />
    &#x3C;span asp-validation-for="Name" class="text-danger">&#x3C;/span>
&#x3C;/div>

&#x3C;div class="form-group">
    &#x3C;label asp-for="Email">&#x3C;/label>
    &#x3C;input asp-for="Email" class="form-control" type="email" />
    &#x3C;span asp-validation-for="Email" class="text-danger">&#x3C;/span>
&#x3C;/div>

&#x3C;div class="form-group">
    &#x3C;label asp-for="Message">&#x3C;/label>
    &#x3C;textarea asp-for="Message" class="form-control" rows="5">&#x3C;/textarea>
    &#x3C;span asp-validation-for="Message" class="text-danger">&#x3C;/span>
&#x3C;/div>

&#x3C;button type="submit" class="btn btn-primary">Send Message&#x3C;/button>

</form>

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

jQuery AJAX Integration

AJAX Form Submission

// 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('&#x3C;div class="alert alert-success">' + response.message + '&#x3C;/div>');
                $form[0].reset();
            } else {
                showValidationErrors(response.errors);
            }
        },
        error: function(xhr, status, error) {
            $result.html('&#x3C;div class="alert alert-danger">An error occurred. Please try again.&#x3C;/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

[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

// 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

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

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

Loading Content Dynamically

Partial View Loading

// 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); }

// 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

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 &#x3C; 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('&#x3C;div class="search-loading">Searching...&#x3C;/div>').show();
    
    $.get('/Search/Results', { q: query })
        .done(function(html) {
            $results.html(html).show();
        })
        .fail(function() {
            $results.html('&#x3C;div class="search-error">Search failed&#x3C;/div>');
        });
}

return { init: init };

})(jQuery);

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

Controller

[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

[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 &#x3C; result.TotalPages;

return View(result.Items);

}

Razor Partial

@* _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 &#x3C;= totalPages; i++)
        {
            &#x3C;li class="page-item @(i == currentPage ? "active" : "")">
                &#x3C;a class="page-link" asp-action="Index" asp-route-page="@i">@i&#x3C;/a>
            &#x3C;/li>
        }
        
        &#x3C;li class="page-item @(!hasNext ? "disabled" : "")">
            &#x3C;a class="page-link" 
               asp-action="Index" 
               asp-route-page="@(currentPage + 1)"
               aria-label="Next">
                &#x3C;span aria-hidden="true">&#x26;raquo;&#x3C;/span>
            &#x3C;/a>
        &#x3C;/li>
    &#x3C;/ul>
&#x3C;/nav>

}

AJAX Pagination

$(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

<form asp-action="Upload" method="post" enctype="multipart/form-data"> @Html.AntiForgeryToken()

&#x3C;div class="form-group">
    &#x3C;label for="file">Select file&#x3C;/label>
    &#x3C;input type="file" name="file" id="file" class="form-control-file" accept=".jpg,.png,.pdf" />
    &#x3C;small class="form-text text-muted">Max size: 5MB. Allowed: JPG, PNG, PDF&#x3C;/small>
&#x3C;/div>

&#x3C;button type="submit" class="btn btn-primary">Upload&#x3C;/button>

</form>

Controller

[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

$('#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

$(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

// 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" }); } }

$.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

// 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

// 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

var MYAPP = MYAPP || {};

MYAPP.toast = (function($) { var $container;

function init() {
    $container = $('&#x3C;div id="toast-container">&#x3C;/div>').appendTo('body');
}

function show(message, type, duration) {
    type = type || 'info';
    duration = duration || 5000;
    
    var $toast = $('&#x3C;div class="toast toast-' + type + '">' + 
        '&#x3C;span class="toast-message">' + message + '&#x3C;/span>' +
        '&#x3C;button class="toast-close">&#x26;times;&#x3C;/button>' +
        '&#x3C;/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

#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;

&#x26;.show {
    opacity: 1;
    transform: translateX(0);
}

&#x26;-success { background: #28a745; color: white; }
&#x26;-error { background: #dc3545; color: white; }
&#x26;-warning { background: #ffc107; color: #333; }
&#x26;-info { background: #17a2b8; color: white; }

&#x26;-close {
    position: absolute;
    top: 10px;
    right: 10px;
    background: none;
    border: none;
    color: inherit;
    font-size: 20px;
    cursor: pointer;
    opacity: 0.7;
    
    &#x26;:hover { opacity: 1; }
}

}

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

frontend-razor

No summary provided by upstream source.

Repository SourceNeeds Review
General

optimizely-content-cloud

No summary provided by upstream source.

Repository SourceNeeds Review
General

optimizely-experimentation

No summary provided by upstream source.

Repository SourceNeeds Review
General

optimizely-web

No summary provided by upstream source.

Repository SourceNeeds Review