frontend-razor

Razor View Development 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 "frontend-razor" with this command: npx skills add twofoldtech-dakota/claude-marketplace/twofoldtech-dakota-claude-marketplace-frontend-razor

Razor View Development Patterns

View Structure

Project Layout

Views/ ├── Shared/ │ ├── _Layout.cshtml # Main layout template │ ├── _LayoutEmpty.cshtml # Minimal layout (no header/footer) │ ├── _Header.cshtml # Header partial │ ├── _Footer.cshtml # Footer partial │ ├── _Navigation.cshtml # Navigation partial │ ├── _Pagination.cshtml # Reusable pagination │ └── Components/ # View components │ └── SearchBox/ │ └── Default.cshtml ├── Home/ │ ├── Index.cshtml │ └── About.cshtml ├── Products/ │ ├── Index.cshtml │ ├── Details.cshtml │ └── _ProductCard.cshtml # Page-specific partial ├── _ViewImports.cshtml # Shared imports and tag helpers └── _ViewStart.cshtml # Default layout assignment

_ViewImports.cshtml

@using MyApp.Web @using MyApp.Web.Models @using MyApp.Core.Models @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, MyApp.Web

_ViewStart.cshtml

@{ Layout = "_Layout"; }

Layout Templates

Main Layout

@* Views/Shared/_Layout.cshtml *@ <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - My Application</title>

@* Head section for page-specific styles *@
@await RenderSectionAsync("Styles", required: false)

&#x3C;link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />

</head> <body class="@ViewData["BodyClass"]"> <header> @await Html.PartialAsync("_Header") @await Html.PartialAsync("_Navigation") </header>

&#x3C;main class="container">
    @RenderBody()
&#x3C;/main>

&#x3C;footer>
    @await Html.PartialAsync("_Footer")
&#x3C;/footer>

&#x3C;script src="~/lib/jquery/dist/jquery.min.js">&#x3C;/script>
&#x3C;script src="~/js/site.js" asp-append-version="true">&#x3C;/script>

@* Scripts section for page-specific JavaScript *@
@await RenderSectionAsync("Scripts", required: false)

</body> </html>

Page Using Layout

@model ProductListViewModel @{ ViewData["Title"] = "Products"; ViewData["BodyClass"] = "products-page"; }

@section Styles { <link rel="stylesheet" href="~/css/products.css" asp-append-version="true" /> }

<h1>@ViewData["Title"]</h1>

<div class="product-grid"> @foreach (var product in Model.Products) { @await Html.PartialAsync("_ProductCard", product) } </div>

@section Scripts { <script src="~/js/products.js" asp-append-version="true"></script> }

Nested Layouts

@* Views/Shared/_LayoutAdmin.cshtml *@ @{ Layout = "_Layout"; }

<div class="admin-container"> <aside class="admin-sidebar"> @await Html.PartialAsync("_AdminNav") </aside> <div class="admin-content"> @RenderBody() </div> </div>

@section Scripts { <script src="~/js/admin.js" asp-append-version="true"></script> @await RenderSectionAsync("AdminScripts", required: false) }

Partial Views

Rendering Partials

@* Async (recommended) *@ @await Html.PartialAsync("_ProductCard", product)

@* With explicit view path *@ @await Html.PartialAsync("~/Views/Shared/_Header.cshtml")

@* Synchronous (avoid for I/O operations) *@ @Html.Partial("_Sidebar")

@* As tag helper *@ <partial name="_ProductCard" model="product" />

@* With view-data *@ <partial name="_Pagination" model="Model.Pagination" view-data="ViewData" />

Partial View Example

@* Views/Products/_ProductCard.cshtml *@ @model Product

<article class="product-card"> <a asp-action="Details" asp-route-id="@Model.Id" class="product-card__link"> <img src="@Model.ImageUrl" alt="@Model.Name" class="product-card__image" /> <div class="product-card__content"> <h3 class="product-card__title">@Model.Name</h3> <p class="product-card__price">@Model.Price.ToString("C")</p> @if (Model.IsOnSale) { <span class="product-card__badge">Sale</span> } </div> </a> </article>

View Components

Component Class

// ViewComponents/RecentArticlesViewComponent.cs public class RecentArticlesViewComponent : ViewComponent { private readonly IArticleService _articleService;

public RecentArticlesViewComponent(IArticleService articleService)
{
    _articleService = articleService;
}

public async Task&#x3C;IViewComponentResult> InvokeAsync(int count = 5)
{
    var articles = await _articleService.GetRecentAsync(count);
    return View(articles);
}

}

Component View

@* Views/Shared/Components/RecentArticles/Default.cshtml *@ @model IEnumerable<Article>

<section class="recent-articles"> <h3>Recent Articles</h3> <ul> @foreach (var article in Model) { <li> <a asp-controller="Articles" asp-action="Details" asp-route-slug="@article.Slug"> @article.Title </a> <time datetime="@article.PublishedDate.ToString("yyyy-MM-dd")"> @article.PublishedDate.ToString("MMM dd, yyyy") </time> </li> } </ul> </section>

Invoking Components

@* Tag helper syntax (preferred) *@ <vc:recent-articles count="5" />

@* Async syntax *@ @await Component.InvokeAsync("RecentArticles", new { count = 5 })

@* With cache *@ <cache expires-after="@TimeSpan.FromMinutes(10)"> <vc:recent-articles count="5" /> </cache>

Tag Helpers

Built-in Tag Helpers

@* Anchor tag helper *@ <a asp-controller="Products" asp-action="Details" asp-route-id="@product.Id" asp-route-category="@product.Category"> View Details </a>

@* Form tag helpers *@ <form asp-controller="Contact" asp-action="Submit" method="post"> <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>

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

&#x3C;div class="form-group">
    &#x3C;label asp-for="Category">&#x3C;/label>
    &#x3C;select asp-for="Category" asp-items="Model.Categories" class="form-control">
        &#x3C;option value="">Select a category&#x3C;/option>
    &#x3C;/select>
    &#x3C;span asp-validation-for="Category" class="text-danger">&#x3C;/span>
&#x3C;/div>

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

</form>

@* Image with cache busting *@ <img src="~/images/logo.png" asp-append-version="true" alt="Logo" />

@* Environment-specific rendering *@ <environment include="Development"> <link rel="stylesheet" href="/css/site.css" /> </environment> <environment exclude="Development"> <link rel="stylesheet" href="/css/site.min.css" asp-append-version="true" /> </environment>

Custom Tag Helper

// TagHelpers/EmailTagHelper.cs [HtmlTargetElement("email")] public class EmailTagHelper : TagHelper { public string Address { get; set; } public string Subject { get; set; }

public override void Process(TagHelperContext context, TagHelperOutput output)
{
    output.TagName = "a";
    
    var href = $"mailto:{Address}";
    if (!string.IsNullOrEmpty(Subject))
    {
        href += $"?subject={Uri.EscapeDataString(Subject)}";
    }
    
    output.Attributes.SetAttribute("href", href);
    output.Content.SetContent(Address);
}

}

@* Usage *@ <email address="support@example.com" subject="Help Request" />

Model Binding

Strongly-Typed Views

@model ContactFormViewModel

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

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

&#x3C;div class="form-group">
    &#x3C;label asp-for="Email" class="form-label">&#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" class="form-label">&#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" /> }

ViewModel with Validation

public class ContactFormViewModel { [Required(ErrorMessage = "Name is required")] [StringLength(100, ErrorMessage = "Name cannot exceed 100 characters")] [Display(Name = "Full Name")] public string Name { get; set; }

[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Please enter a valid email address")]
public string Email { get; set; }

[Required(ErrorMessage = "Message is required")]
[StringLength(2000, MinimumLength = 10, 
    ErrorMessage = "Message must be between 10 and 2000 characters")]
public string Message { get; set; }

}

Conditional Rendering

If/Else Statements

@if (Model.Products.Any()) { <div class="product-grid"> @foreach (var product in Model.Products) { <partial name="_ProductCard" model="product" /> } </div> } else { <div class="empty-state"> <p>No products found.</p> <a asp-action="Index" asp-controller="Home" class="btn btn-link"> Return to Home </a> </div> }

Switch Statements

@switch (Model.Status) { case OrderStatus.Pending: <span class="badge badge-warning">Pending</span> break; case OrderStatus.Processing: <span class="badge badge-info">Processing</span> break; case OrderStatus.Shipped: <span class="badge badge-primary">Shipped</span> break; case OrderStatus.Delivered: <span class="badge badge-success">Delivered</span> break; default: <span class="badge badge-secondary">Unknown</span> break; }

Null Checking

@* Null conditional *@ <p>Author: @Model.Author?.Name ?? "Unknown"</p>

@* With fallback *@ @if (Model.ImageUrl != null) { <img src="@Model.ImageUrl" alt="@Model.Title" /> } else { <img src="~/images/placeholder.jpg" alt="No image available" /> }

@* Ternary operator *@ <div class="@(Model.IsActive ? "active" : "inactive")"> @Model.Title </div>

Loops and Collections

For Loop

@for (int i = 0; i < Model.Items.Count; i++) { <div class="item @(i % 2 == 0 ? "even" : "odd")"> <span class="item-number">@(i + 1)</span> <span class="item-name">@Model.Items[i].Name</span> </div> }

Foreach with Index

@{ var index = 0; } @foreach (var item in Model.Items) { <div class="item" data-index="@index"> @item.Name </div> index++; }

Rendering Lists

<ul class="breadcrumb"> @foreach (var crumb in Model.Breadcrumbs) { var isLast = crumb == Model.Breadcrumbs.Last(); <li class="breadcrumb-item @(isLast ? "active" : "")"> @if (isLast) { @crumb.Title } else { <a href="@crumb.Url">@crumb.Title</a> } </li> } </ul>

HTML Helpers vs Tag Helpers

Prefer Tag Helpers

@* HTML Helper (older approach) *@ @Html.ActionLink("Details", "Details", "Products", new { id = product.Id }, new { @class = "btn btn-link" })

@* Tag Helper (modern, preferred) *@ <a asp-controller="Products" asp-action="Details" asp-route-id="@product.Id" class="btn btn-link"> Details </a>

@* HTML Helper for form *@ @Html.TextBoxFor(m => m.Name, new { @class = "form-control", placeholder = "Enter name" })

@* Tag Helper (cleaner) *@ <input asp-for="Name" class="form-control" placeholder="Enter name" />

When to Use HTML Helpers

@* Complex dynamic attributes *@ @Html.TextBoxFor(m => m.Name, Model.GetInputAttributes())

@* Raw HTML content *@ @Html.Raw(Model.HtmlContent)

@* Display templates *@ @Html.DisplayFor(m => m.CreatedDate)

@* Editor templates *@ @Html.EditorFor(m => m.Address)

ViewData, ViewBag, and TempData

ViewData (Dictionary)

@* In Controller *@ ViewData["Title"] = "Product Details"; ViewData["ShowSidebar"] = true;

@* In View *@ <h1>@ViewData["Title"]</h1> @if ((bool?)ViewData["ShowSidebar"] == true) { <partial name="_Sidebar" /> }

ViewBag (Dynamic)

@* In Controller *@ ViewBag.Categories = await _categoryService.GetAllAsync();

@* In View *@ <select asp-items="@(new SelectList(ViewBag.Categories, "Id", "Name"))"> <option value="">All Categories</option> </select>

TempData (Survives Redirect)

// In Controller TempData["SuccessMessage"] = "Product saved successfully!"; return RedirectToAction("Index");

@* In View *@ @if (TempData["SuccessMessage"] != null) { <div class="alert alert-success alert-dismissible"> @TempData["SuccessMessage"] <button type="button" class="close" data-dismiss="alert">&times;</button> </div> }

AJAX and Partial Rendering

AJAX Form Submission

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

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

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

</form>

<div id="result"></div>

@section Scripts { <script> $('#contact-form').on('submit', function(e) { e.preventDefault();

    var $form = $(this);
    var $button = $form.find('button[type="submit"]');
    
    $button.prop('disabled', true).text('Sending...');
    
    $.ajax({
        url: $form.attr('action'),
        type: 'POST',
        data: $form.serialize(),
        success: function(response) {
            $('#result').html(response);
            $form[0].reset();
        },
        error: function(xhr) {
            $('#result').html('&#x3C;div class="alert alert-danger">An error occurred.&#x3C;/div>');
        },
        complete: function() {
            $button.prop('disabled', false).text('Submit');
        }
    });
});
&#x3C;/script>

}

Returning Partial Views from Controller

[HttpGet] public async Task<IActionResult> LoadMore(int page) { var products = await _productService.GetPagedAsync(page, 10); return PartialView("_ProductGrid", products); }

[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> SubmitAjax(ContactFormViewModel model) { if (!ModelState.IsValid) { return PartialView("_ContactFormErrors", model); }

await _contactService.ProcessAsync(model);
return PartialView("_ContactFormSuccess");

}

Accessibility Considerations

@* Semantic HTML with ARIA *@ <nav aria-label="Main navigation"> <ul class="nav" role="menubar"> @foreach (var item in Model.NavItems) { <li role="none"> <a href="@item.Url" role="menuitem" aria-current="@(item.IsActive ? "page" : null)"> @item.Title </a> </li> } </ul> </nav>

@* Form accessibility *@ <div class="form-group"> <label asp-for="Email" id="email-label"></label> <input asp-for="Email" aria-labelledby="email-label" aria-describedby="email-help email-error" /> <small id="email-help" class="form-text text-muted"> We'll never share your email. </small> <span asp-validation-for="Email" id="email-error" class="text-danger" role="alert"></span> </div>

@* Skip link for keyboard navigation *@ <a href="#main-content" class="skip-link">Skip to main content</a>

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.

Coding

umbraco-development

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