Roadmap › ASP.NET Core MVC (and Razor Pages) › Module: MVC Basics
Lessons in this course (15)

2. How ASP.NET Core MVC works inside — from Kestrel to HTML

Intermediate★ Must knowLesson 2 of 15

Phase 1 — Startup (once):

  1. WebApplication.CreateBuilder creates the generic host: loads configuration (appsettings.json → appsettings.{Env}.json → user secrets → environment variables → command line), sets up logging, creates the IServiceCollection and configures Kestrel as the IServer.
  2. You register services; builder.Build() creates the root IServiceProvider.
  3. Use... calls compose the middleware pipeline into a single RequestDelegate (each middleware wraps the next).
  4. MapControllerRoute/MapControllers registers endpoint data sources: at startup MVC discovers controllers (public classes ending in Controller in the app's assemblies — the application parts), builds an ActionDescriptor for every action (routes, HTTP methods, filters, parameters) and turns them into endpoints.
  5. app.Run() starts hosted services and Kestrel, which listens on the configured ports.

Phase 2 — Each request:

text
Kestrel (socket → HTTP parser) ──► HttpContext created (Request, Response, Features, RequestServices = new DI SCOPE)
   │
   ▼ Middleware pipeline (RequestDelegate chain)
 ExceptionHandler ─► HSTS/HttpsRedirection ─► Static assets? (serve & short-circuit)
   ─► UseRouting: EndpointRoutingMiddleware matches URL + HTTP method against all endpoints
        (a compiled DFA matcher), stores the chosen Endpoint + route values in HttpContext
   ─► UseAuthentication: reads cookie/JWT → HttpContext.User
   ─► UseAuthorization: reads [Authorize] metadata from the selected endpoint → allow / 401 / 403
   ─► Endpoint middleware: invokes the endpoint's RequestDelegate
        │
        ▼ MVC: ControllerActionInvoker (ResourceInvoker) — the filter pipeline
        1. Authorization filters
        2. Resource filters (before)      ← e.g. caching can short-circuit here
        3. Controller created by the controller activator (constructor dependencies resolved from the request scope)
        4. Model binding: value providers (route, query, form, headers, body via input formatters e.g. System.Text.Json)
           + model binders → action parameters; then validation (DataAnnotations/IValidatableObject) → ModelState
        5. Action filters (OnActionExecuting)
        6. ACTION METHOD executes → returns IActionResult (ViewResult, JsonResult, RedirectResult...)
        7. Action filters (OnActionExecuted)
        8. Exception filters (if the action/filters threw)
        9. Result filters (OnResultExecuting)
       10. Result execution:
             ViewResult → IViewEngine (RazorViewEngine) finds the compiled view → creates ViewContext
             → runs _ViewStart → view's ExecuteAsync writes HTML (tag helpers run, partials/view components render)
             → layout renders with @RenderBody → HTML written to the response body
             ObjectResult/JsonResult → output formatter (System.Text.Json) serialises to the body
       11. Result filters (OnResultExecuted), Resource filters (after)
   ◄─ response flows back through middleware (they can still act after next()) ◄─
Kestrel writes the response bytes; the request's DI scope is disposed (scoped services like DbContext disposed)

Key internals to remember:

PieceWhat it really is
HttpContextPer-request object: Request, Response, User, Items, Features, RequestServices
MiddlewareFunc<RequestDelegate, RequestDelegate> — each calls next(context) or short-circuits
EndpointA RequestDelegate + metadata (route pattern, [Authorize], CORS policy, action descriptor)
RoutingTwo steps: match in UseRouting, execute in the endpoint middleware — middleware between them (auth, CORS) can read the selected endpoint
Controller lifetimeA new controller instance per request (transient), created from the request's DI scope
Scoped servicesOne instance per request (DbContext) — disposed at the end of the request
Model bindingValue providers + model binder providers ([FromRoute], [FromQuery], [FromForm], [FromBody], [FromServices], [FromHeader])
Razor viewsCompiled at build time by the Razor SDK into classes deriving from RazorPage<TModel> inside the app assembly (runtime compilation only if you add the package for development)
Tag helpersClasses executed while the view renders, turning <input asp-for> into HTML
OutputIActionResult.ExecuteResultAsync writes to HttpContext.Response.Body (a pipe to Kestrel)

Writing your own middleware shows the pipeline idea:

csharp
app.Use(async (context, next) =>
{
    var sw = Stopwatch.StartNew();                     // runs on the way IN
    await next(context);                               // everything after this middleware runs here
    sw.Stop();                                         // runs on the way OUT
    app.Logger.LogInformation("{Path} took {Ms} ms → {Status}", context.Request.Path, sw.ElapsedMilliseconds, context.Response.StatusCode);
});

Interview tip: Asked "explain the request life cycle", answer in this order: Kestrel → HttpContext + DI scope → middleware (exception, HTTPS, static files, routing, authentication, authorization) → endpoint → MVC filters (authorization, resource, model binding + validation, action, exception, result) → action → result execution (view engine / formatter) → back out through middleware → scope disposed.