Webb-MVC — ASP.NET MVC
Del 2 av UI-arkitektur. Bygger på: MVC — men på webben finns ingen vy som kan lyssna på modellen. Modellen
Kundvagnsom används här finns i översikten.
När du läst detta ska du kunna
- Förklara varför webben inte kan använda klassisk MVC rakt av
- Följa en förfrågan genom routing, controller och vy
- Skilja på ASP.NET MVC och Razor Pages och välja mellan dem
Bakgrund och idé
När webben kom försökte man återanvända MVC, men webben fungerar helt annorlunda: det finns ingen vy som ligger och lyssnar. Varje klick är en ny HTTP-förfrågan, servern bygger en ny HTML-sida och glömmer sedan allt. Javas “Model 2” (1999) och sedan Ruby on Rails (2004) och ASP.NET MVC (2009) anpassade mönstret till request/response.
graph LR
B((Webbläsare)) -->|HTTP POST /kundvagn/lagg-till| R[Routing]
R --> C[Controller]
C -->|anropar| M[Model / tjänst]
C -->|ViewModel| V[View .cshtml]
V -->|HTML| B
Så läser du diagrammet
- Webbläsaren skickar en förfrågan. Routingen väljer controller och action utifrån URL:en.
- Controllern (ett nytt objekt för varje förfrågan!) anropar modellen eller en tjänst.
- Controllern packar det vyn behöver i en ViewModel och väljer vilken View som ska renderas.
- Vyn blir HTML som skickas tillbaka. Sedan är förfrågan över — inget lever kvar i controllern.
Den stora skillnaden mot Smalltalk-MVC: pilen från modellen till vyn är borta. Vyn lyssnar inte på något — den renderas en gång per förfrågan. Därför är det controllern, inte modellen, som “äger” flödet.
using Microsoft.AspNetCore.Mvc;
// Kundvagn är registrerad i DI — se Program.cs nedanpublic class KundvagnController(Kundvagn kundvagn) : Controller{ public IActionResult Index() => View(new KundvagnViewModel(kundvagn.Varor));
[HttpPost] public IActionResult LäggTill(string vara) { kundvagn.LäggTill(vara); return RedirectToAction(nameof(Index)); // Post-Redirect-Get: F5 skickar inte formuläret igen }}
// Vyn får exakt det den behöver — inte hela domänmodellenpublic record KundvagnViewModel(IReadOnlyList<string> Varor);var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();// Singleton = en kundvagn för ALLA besökare. Bara för demo — på riktigt sparas den per användare.builder.Services.AddSingleton<Kundvagn>();
var app = builder.Build();app.MapDefaultControllerRoute(); // /{controller=Home}/{action=Index}/{id?}app.Run();Vyn (Views/Kundvagn/Index.cshtml) är Razor — HTML med C# insprängt:
@model KundvagnViewModel
<ul> @foreach (var vara in Model.Varor) { <li>@vara</li> }</ul>
<form asp-action="LäggTill" method="post"> <input name="vara" /> <button>Lägg till</button></form>Mappstruktur
det här är vad mallen dotnet new mvc ger dig. ASP.NET hittar vyer via konvention: KundvagnController.Index() letar efter Views/Kundvagn/Index.cshtml.
KundvagnWebb/├── Controllers/│ └── KundvagnController.cs├── Models/│ ├── Kundvagn.cs│ └── KundvagnViewModel.cs├── Views/│ ├── Kundvagn/│ │ └── Index.cshtml ← namnet matchar controller + action│ ├── Shared/│ │ └── _Layout.cshtml ← gemensam ram för alla sidor│ └── _ViewImports.cshtml├── wwwroot/ ← css, js, bilder└── Program.csEtt komplett exempel med ett riktigt externt API hittar du i MVC och API — den här sidan upprepar inte det.
Razor Pages — Page Controller
Razor Pages (ASP.NET Core 2.0, 2017) är Microsofts svar på att MVC ofta blir mycket ceremoni för enkla sidor: en controller i en mapp, en vy i en annan, en ViewModel i en tredje. I Razor Pages hör en sida och dess logik ihop — mönstret heter Page Controller (Fowler, 2002).
graph LR
B((Webbläsare)) -->|GET /Kundvagn| P[Kundvagn.cshtml.cs<br/>PageModel]
B -->|POST /Kundvagn| P
P -->|anropar| M[Model / tjänst]
P -->|egenskaper| V[Kundvagn.cshtml]
V -->|HTML| B
Så läser du diagrammet
URL:en pekar direkt på en sida — ingen separat routing till en controller. GET-förfrågan kör OnGet(), POST kör OnPost(). PageModel-klassen är både controller och ViewModel: vyn läser dess egenskaper direkt.
using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.RazorPages;
public class KundvagnModel(Kundvagn kundvagn) : PageModel{ public IReadOnlyList<string> Varor => kundvagn.Varor;
[BindProperty] // fylls automatiskt från formuläret vid POST public string NyVara { get; set; } = "";
public void OnGet() { }
public IActionResult OnPost() { if (!string.IsNullOrWhiteSpace(NyVara)) kundvagn.LäggTill(NyVara.Trim());
return RedirectToPage(); }}Mappstruktur
sidan och dess kod ligger bredvid varandra:
KundvagnPages/├── Pages/│ ├── Kundvagn.cshtml ← HTML/Razor│ ├── Kundvagn.cshtml.cs ← PageModel (logiken)│ └── Shared/│ └── _Layout.cshtml├── Models/│ └── Kundvagn.cs└── Program.cs ← AddRazorPages() + MapRazorPages()