Server-Rendered Overview
Key Points
- MVC: classic model-view-controller. Controller actions return views. Strong for complex page flows, multi-form pages.
- Razor Pages: page-centric (each page =
.cshtml+.cshtml.cs). Simpler for CRUD; less ceremony than MVC. - Static SSR Blazor (covered in Render Modes) is the modern alternative — same mental model with components.
- For new projects: prefer Blazor Static SSR or Razor Pages. MVC for legacy maintenance / specific patterns.
- See MVC & Razor Pages for detailed mechanics.
Concepts (deep dive)
MVC
public class HomeController : Controller
{
public IActionResult Index() => View();
public IActionResult About() => View();
[HttpPost]
public async Task<IActionResult> Submit(MyModel model)
{
if (!ModelState.IsValid) return View(model);
await _svc.SaveAsync(model);
return RedirectToAction(nameof(Index));
}
}
Controller maps URLs to actions; actions return IActionResult.
Razor Pages
// Pages/Index.cshtml.cs
public class IndexModel : PageModel
{
[BindProperty] public MyModel Input { get; set; } = new();
public void OnGet() { /* ... */ }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) return Page();
await _svc.SaveAsync(Input);
return RedirectToPage("/Confirm");
}
}
<!-- Pages/Index.cshtml -->
@page
@model IndexModel
<form method="post">
<input asp-for="Input.Name" />
<button>Submit</button>
</form>
Page-centric: URL maps directly to file. Less navigation indirection.
When MVC vs Razor Pages
| Need | Pick |
|---|---|
| Complex multi-action flows | MVC |
| API-style server actions | MVC |
| Page-per-view CRUD | Razor Pages |
| Beginners | Razor Pages |
| Legacy WebForms migration | Razor Pages |
In practice many apps mix.
Static SSR Blazor as alternative
Same mental model as MVC but using component model. Future-aligned: Microsoft is investing here.
Routing
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
Conventional MVC routing or attribute routing per action.
View resolution
Layouts and partials
<!-- _Layout.cshtml -->
<!DOCTYPE html>
<html>
<head><title>@ViewData["Title"]</title></head>
<body>
@RenderBody()
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
<!-- View -->
@{ Layout = "_Layout"; }
<h1>@ViewData["Title"]</h1>
@section Scripts { <script>console.log('done');</script> }
View Components
public class CartViewComponent : ViewComponent
{
public IViewComponentResult Invoke()
{
var cart = /* load */;
return View(cart);
}
}
Reusable rendered components. More logic-friendly than partials.
Tag Helpers
<form asp-action="Submit" method="post">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email"></span>
<button type="submit">Save</button>
</form>
C#-aware HTML helpers. Generate URLs, anti-forgery tokens, validation attributes.
ViewBag, ViewData, TempData
- ViewBag / ViewData: per-request. Same data; different APIs.
- TempData: across one redirect.
TempData["Message"] = "Saved";
return RedirectToAction(nameof(Index));
// Index view:
@TempData["Message"] // "Saved"; cleared after read
Model binding
Comes from form, route, query, body. Customize with [FromBody], [FromQuery], etc.
Validation
public class MyModel
{
[Required, StringLength(100)]
public string? Name { get; set; }
}
if (!ModelState.IsValid) return View(model);
Filters
Action filters, result filters, exception filters, authorization filters. See Filters & Action Pipeline.
Areas
Sub-applications within. Routing: /Admin/Users/Index.
When server-rendered vs SPA
- Server-rendered: SEO; quick first load; minimal JS; admin panels; CRUD.
- SPA: highly interactive; offline; real-time; mobile-app-like UX.
For most apps: server-rendered with sprinkles of JS/HTMX is faster to ship.
htmx
For interactive server-rendered without SPA:
POST returns HTML fragment; htmx swaps. Lightweight.
For .NET, htmx pairs well with Razor Pages or Static SSR Blazor.
Performance
- Output caching for static or rarely-changing pages.
- Response compression (gzip, brotli).
- MapStaticAssets for hashed static assets.
- Minimize DB calls per request.
Modern .NET MVC project
Boilerplate is minimal:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
app.Run();
Code: correct vs wrong
❌ Wrong: business logic in controller
public IActionResult Save(MyModel m)
{
using var conn = new SqlConnection(...);
conn.Open();
/* SQL, validation, mapping, all here */
return View();
}
✅ Correct: thin controller, service for logic
public IActionResult Save(MyModel m)
{
if (!ModelState.IsValid) return View(m);
return _svc.SaveAsync(m).Result.Match(
_ => RedirectToAction(nameof(Index)),
err => { ModelState.AddModelError("", err.Message); return View(m); });
}
❌ Wrong: ViewBag with magic strings
✅ Correct: strongly-typed ViewModel
public class IndexVm { public int UserCount { get; set; } }
return View(new IndexVm { UserCount = 42 });
Design patterns for this topic
Pattern 1 — "Strongly-typed ViewModels"
- Intent: compile-time safety in views.
Pattern 2 — "Razor Pages for CRUD; MVC for flows"
- Intent: match style to pattern.
Pattern 3 — "Static SSR Blazor for new"
- Intent: future-aligned.
Pattern 4 — "View Components for reusable logic+UI"
- Intent: smarter than partials.
Pattern 5 — "PRG (Post/Redirect/Get) for forms"
- Intent: avoid duplicate submits.
Pros & cons / trade-offs
| Style | Pros | Cons |
|---|---|---|
| MVC | Familiar; powerful | More ceremony |
| Razor Pages | Page-centric; less code | Pattern mismatch for complex |
| Static SSR Blazor | Component model | Newer |
| htmx | Server-driven interactivity | Less SPA-like |
When to use / when to avoid
- Use Razor Pages for new CRUD apps.
- Use Static SSR Blazor for new component-style.
- Use MVC for complex flows / legacy.
- Avoid mixing MVC and Razor Pages without need.
Interview Q&A
Q1. MVC vs Razor Pages? MVC: controller actions for many views. RP: each page is a self-contained file pair.
Q2. Static SSR Blazor advantage? Component model + server-rendered. Forward-looking.
Q3. PRG pattern? POST → redirect → GET. Avoid double submits on refresh.
Q4. ViewModel vs DTO? ViewModel: shape for view. DTO: data transfer. Often same; distinct concepts.
Q5. ViewBag vs ViewData vs TempData? ViewBag/ViewData: per-request. TempData: across one redirect.
Q6. Tag Helpers? HTML elements that bind to C# (asp-for, asp-action). Strongly typed.
Q7. View Component? Reusable widget with code + view. More than a partial.
Q8. Areas? Sub-app within. Used to organize large apps (Admin, Public).
Q9. htmx? HTML-driven interactivity. POST returns fragment; client swaps. Less JS than SPA.
Q10. Performance levers? Output cache, compression, minimal DB per req, MapStaticAssets.
Q11. Razor compilation? Compiled to code at runtime (default) or pre-compiled at build (faster startup).
Q12. When SPA over server-rendered? Real-time, offline, app-like UX. Otherwise server-rendered ships faster.
Gotchas / common mistakes
- ⚠️ Logic in controllers.
- ⚠️ ViewBag for typed data.
- ⚠️ No PRG — duplicate POSTs on refresh.
- ⚠️ Mixing MVC and Razor Pages chaos.
- ⚠️ Direct DB calls in views.