Skip to content

Instantly share code, notes, and snippets.

@Aaronontheweb
Created June 2, 2025 18:47
Show Gist options
  • Save Aaronontheweb/e8144f650896f8311813721b27e7ec0f to your computer and use it in GitHub Desktop.
Save Aaronontheweb/e8144f650896f8311813721b27e7ec0f to your computer and use it in GitHub Desktop.
ASP.NET Core Razor LLM System Prompts

ASP.NET Core MVC + Razor Reusability Guide

Goal Provide a concise, LLM-friendly rule set for building ASP.NET Core MVC pages that stay reusable, testable, and maintainable.


🔑 Guiding Principles

  1. Think in Features, Not Layers – co-locate Controllers, Views, ViewModels, Tag Helpers, and View Components under /Features/<FeatureName>/… so everything a feature needs lives together.
  2. Shape Data Up-Front – perform all heavy lifting (queries, transforms) in services or controllers; Razor files should only render.
  3. Prefer Composition over Inheritance – build UI from small, isolated pieces (partials, components, Tag Helpers) instead of giant base pages.

🧩 Building Blocks & When to Use Them

When to reach for… Use it for… Never use it for…
Partial View (_Card.cshtml) Simple, synchronous markup reuse; tiny ViewModels DB calls, service resolution, cross-concern logic
View Component (RecentPostsViewComponent) Reusable blocks with server logic, async work, or caching One-off fragments that render once
Custom Tag Helper (<pb-card …/>) Encapsulating repeated HTML patterns and attributes Logic that touches HttpContext or business rules
Razor Component (MyAlert.razor) Interactive UI in Blazor-enabled apps; SSR + hydration Pure MVC pages without Blazor

📜 Rules (R1 – R21)

R1 – Organize by feature Group files by the feature they deliver, not by their technical role.

R2 – Strongly-typed ViewModels Avoid ViewBag / dynamic; keep ViewModels flat, immutable, and serializable.

R3 – Single authoritative Layout Put <html>, <head>, nav, and footer in _Layout.cshtml; child views declare only what changes.

R4 – Split views at ~60 lines or 3+ duplications Move repeated markup into a Partial View.

R5 – Use View Components for logic-rich blocks Async calls, service injection, or caching = View Component.

R6 – Wrap UI patterns in Tag Helpers Copy-pasting HTML? Make a Tag Helper with an attribute-driven API.

R7 – Consider Razor Components for rich interactivity If Blazor is in play, favor .razor over JavaScript widgets.

R8 – Lean on built-in form Tag Helpers <input asp-for> keeps names, IDs, and validation wired automatically.

R9 – Zero business logic in .cshtml No LINQ, loops over DbSets, or complex if chains—prepare in controller.

R10 – Feature-scoped assets Each feature/component gets its own SCSS/JS bundle; load with asp-append-version.

R11 – Depend on DI within View Components Inject services via constructor; never use GetService manually in views.

R12 – Naming Conventions Leading underscores for partials, *ViewComponent suffix, *TagHelper suffix.

R13 – Build for Accessibility Expose aria-* where relevant; run axe-core in CI.

R14 – Internationalize Early Wrap strings with IStringLocalizer; no hard-coded English.

R15 – Unit-Test UI Pieces Render Tag Helpers in isolation; spin up WebApplicationFactory for full page tests.

R16 – Async where it matters Make View Components async and let EF Core/HTTP clients use async I/O.

R17 – Security Default-On Antiforgery tokens via <form asp-antiforgery="true">; escape all outputs unless verified safe.

R18 – Document Intent XML-doc every Tag Helper attribute and View Component class.

R19 – Common Anti-Patterns to Avoid Massive pages (>300 lines), repeating inline scripts, partials inside tight loops without pre-fetched data, business logic in Tag Helpers.

R20 – Pre-Merge Checklist ✅ Strongly-typed ViewModel ✅ No duplicated markup ✅ Component/unit tests exist ✅ Accessibility/localization ready ✅ Assets bundled & versioned

R21 – Use URL‑Generation Tag Helpers Always link with asp-page, asp-action, asp-controller, and asp-route‑* so routes remain DRY, version-proof, and testable—never hard-code /Products/Details/42.


🔍 Examples: Do vs Don’t

1. Partial Views

Don’t – duplicate markup everywhere:

<!-- Index.cshtml -->
@foreach (var product in Model) {
    <div class="card">
        <h3>@product.Name</h3>
        <p>@product.Price.ToString("C")</p>
    </div>
}

Do – factor into a partial:

<!-- _ProductCard.cshtml -->
@model ProductVm
<div class="card">
    <h3>@Model.Name</h3>
    <p>@Model.Price.ToString("C")</p>
</div>
<!-- Index.cshtml -->
@foreach (var product in Model) {
    @await Html.PartialAsync("_ProductCard", product)
}

2. Tag Helpers

Don’t – repeat attributes & Bootstrap classes:

<button class="btn btn-primary" disabled="@(isBusy ? "disabled" : null)">
    Save
</button>

Do – wrap in a Tag Helper:

[HtmlTargetElement("pb-button")]
public class ButtonTagHelper : TagHelper
{
    public string Variant { get; set; } = "primary";
    public bool Disabled { get; set; }
    public override void Process(TagHelperContext ctx, TagHelperOutput output)
    {
        output.TagName = "button";
        output.Attributes.Add("class", $"btn btn-{Variant}");
        if (Disabled) output.Attributes.Add("disabled", "disabled");
    }
}
<pb-button variant="primary" disabled="@isBusy">Save</pb-button>

3. View Components

Don’t – hit the database in a Partial View:

@* _RecentPosts.cshtml *@
@inject BlogDb db
@foreach (var post in db.Posts.OrderByDescending(p => p.Published).Take(5)) {
    <a asp-page="/Blog/Details" asp-route-id="@post.Id">@post.Title</a>
}

Do – create an async View Component:

public class RecentPostsViewComponent : ViewComponent
{
    private readonly BlogDb _db;
    public RecentPostsViewComponent(BlogDb db) => _db = db;

    public async Task<IViewComponentResult> InvokeAsync(int count = 5)
    {
        var posts = await _db.Posts
            .OrderByDescending(p => p.Published)
            .Take(count)
            .ToListAsync();
        return View(posts); // Views/Shared/Components/RecentPosts/Default.cshtml
    }
}
@await Component.InvokeAsync("RecentPosts", new { count = 5 })

4. Business Logic Placement

Don’t – calculate discounts in the view:

@{ var discounted = Model.Price * 0.9M; }
<p>@discounted.ToString("C")</p>

Do – prepare data in ViewModel:

public record ProductVm(string Name, decimal Price, decimal DiscountedPrice);
<p>@Model.DiscountedPrice.ToString("C")</p>

5. Linking / URL Generation

Don’t – hard‑code URLs:

<a href="/Products/Details/@prod.Id">View</a>

Do – use Tag Helpers:

<!-- MVC controllers -->
<a asp-controller="Products" asp-action="Details" asp-route-id="@prod.Id">View</a>

<!-- Razor Pages -->
<a asp-page="/Products/Details" asp-route-id="@prod.Id">View</a>

These helpers respect route templates, areas, localization slugs, and future refactors—no broken links.


🏁 How to Reference Rules with an LLM

  • Mention rule IDs (e.g., “R6”) when asking follow-up questions.
  • Provide code snippets; ask “Which rules am I breaking?”
  • Request a summarized cheat sheet of just the rule titles for quick recall.

© 2025 Petabridge Engineering

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment