Welcome back. In the previous lesson, you traced how ASP.NET Core’s DI container creates a controller’s dependency graph and how scoped services such as DbContext live for a request. Now widen the lens: follow that request from the moment it enters the application until ASP.NET Core sends an HTTP response.
Priority: Must know. Interviewers often ask some version of: “What happens when a request reaches an ASP.NET Core API?” A senior-level answer should distinguish the outer middleware pipeline from the inner MVC/controller execution, explain where routing and validation occur, and identify the points at which a request can end early.
By the end, you should be able to narrate a POST /api/orders request clearly, including why an invalid JSON payload may result in a 400 without the controller action running.
The two layers: pipeline first, endpoint execution second
A useful mental model is that ASP.NET Core receives an HTTP request as an HttpContext, then passes that context through an ordered pipeline of middleware. Middleware handles cross-cutting concerns: exception handling, HTTPS enforcement, CORS, authentication, logging, and authorization are typical examples.
If the request survives this pipeline and routing finds a matching endpoint, the endpoint executes. For a controller API, that endpoint hands work to MVC, which activates the controller, binds parameters, validates the model, invokes the action, and turns the action result into an HTTP response.
For an API such as:
POST /api/orders?dryRun=false
Content-Type: application/json
Authorization: Bearer <token>
{
"customerId": 42,
"items": [
{ "productId": 10, "quantity": 2 }
]
}
the trace usually has these stages:
- The web server creates an
HttpContextfor the request. - Middleware runs in the order configured in
Program.cs. - Routing identifies the endpoint that matches the HTTP method and URL.
- Authentication establishes a caller identity when configured.
- Authorization checks whether that caller may invoke the selected endpoint.
- MVC creates the controller through DI.
- Model binding obtains action parameters from the route, query string, headers, form, or request body.
- Model validation records binding and validation problems in
ModelState. - If valid, the controller action executes and returns a result.
- ASP.NET Core serializes that result, writes the response, and control returns outward through earlier middleware.
The sequence is not guaranteed to reach step 9. A static-file middleware, failed authorization check, invalid model, unmatched route, or exception can end processing before the action runs.
Middleware behaves like nested request handling
Each Use middleware can perform work both before and after invoking the next component. This makes the request path and response path run in opposite orders.
app.Use(async (context, next) =>
{
logger.LogInformation("Request started: {Path}", context.Request.Path);
await next();
logger.LogInformation(
"Request completed with {StatusCode}",
context.Response.StatusCode);
});
On the way in, this middleware logs “started,” then calls next(). On the way out, after later middleware and the endpoint have completed, it logs the final status code.
A Run delegate is terminal: it does not receive or call next, so later middleware and endpoints do not execute. Middleware may also deliberately short-circuit. For example, static-file middleware can serve /assets/logo.svg without involving routing or an API controller. Authorization middleware can produce 401 Unauthorized or 403 Forbidden without invoking the controller action.
This is why a precise interview answer should say:
Middleware is an ordered, nested pipeline. Each component may inspect or modify the request before calling the next component, inspect or modify the response afterward, or short-circuit the request entirely.
The position of a middleware component is therefore a correctness and security decision, not just formatting in Program.cs.
DEEP DIVE: ASP.NET CORE Middleware Pipeline in .NET 9 🚀 1.5 HOURS
Watch DEEP DIVE: ASP.NET CORE Middleware Pipeline in .NET 9 by Frank Liu for a visual explanation of nested middleware, short-circuiting, and the significance of middleware order.
Watch pipeline basics to understand the request and response paths through a series of delegates. Then watch built in ordering for the practical purpose and typical placement of HTTPS, routing, and CORS middleware. Focus on why a component that does not call the next delegate prevents later stages from running.
A representative controller API pipeline
For interview discussion, it is helpful to show an explicit pipeline, even though modern WebApplication hosting can automatically add some routing and endpoint middleware when it is omitted.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("angular-client");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
This is a representative order for an API. A real application may add rate limiting, request logging, localization, output caching, health checks, or custom correlation middleware, but the core reasoning stays the same.
| Pipeline concern | What it does | Why placement matters |
|---|---|---|
| Exception handling | Converts unhandled downstream exceptions into a safe error response and logs them | It must be early enough to surround components that might throw. |
| HTTPS redirection | Redirects insecure HTTP requests to HTTPS | It should act before code depends on the request URL scheme. |
| Routing | Matches the request to endpoint candidates and selects one | Later middleware can inspect selected endpoint metadata. |
| CORS | Adds or validates cross-origin policy behavior | It must run before components that rely on CORS behavior; commonly before authentication and authorization. |
| Authentication | Attempts to establish HttpContext.User from credentials such as a bearer token | Authorization needs an authenticated principal to evaluate. |
| Authorization | Applies endpoint authorization metadata and policies | It must occur after routing has selected the endpoint and after authentication. |
| Endpoint execution | Executes the controller endpoint selected by routing | This is where MVC binding, validation, and the action occur. |
MapControllers() registers controller actions as endpoint candidates. In the modern hosting model, ASP.NET Core can add endpoint execution middleware automatically. For an interview, it is accurate to explain the conceptual distinction: routing selects an endpoint; endpoint execution invokes it.
One important nuance: authentication does not necessarily reject an anonymous request by itself. It tries to establish an identity. Authorization makes the access decision based on the selected endpoint’s [Authorize], policy, or anonymous-access metadata.
Routing selects an endpoint, not a controller instance
Consider this controller:
[ApiController]
[Route("api/orders")]
public sealed class OrdersController : ControllerBase
{
[HttpGet("{id:int}")]
public async Task<ActionResult<OrderDto>> GetById(int id)
{
// ...
}
[HttpPost]
public async Task<ActionResult<OrderDto>> Create(
CreateOrderRequest request,
[FromQuery] bool dryRun = false)
{
// ...
}
}
Routing uses endpoint metadata, including route templates and HTTP method constraints.
| Request | Selected action | Important result |
|---|---|---|
GET /api/orders/42 | GetById(int id) | 42 satisfies the integer route constraint. |
POST /api/orders?dryRun=true | Create(CreateOrderRequest, bool) | The path and HTTP method match HttpPost. |
GET /api/orders/not-a-number | No GetById match | The {id:int} constraint does not match; the eventual result is normally 404. |
DELETE /api/orders/42 | No action in this example | The URL may resemble a route, but no endpoint accepts DELETE; the result is normally 405 Method Not Allowed when another endpoint matches the route pattern. |
Routing does not parse JSON and does not construct a controller merely to see whether an action can handle the request. It chooses an endpoint from route and method metadata. Once selected, that endpoint’s metadata becomes available to middleware in the routing zone. Authorization relies on this: it needs to know which endpoint and policy apply before making an access decision.
ASP.NET Core Series: Endpoint Routing
Watch ASP.NET Core Series: Endpoint Routing by the official dotnet channel to sharpen the distinction between route selection and endpoint execution.
Watch routing zone for the reason authentication and authorization are placed after routing but before endpoint execution. Then watch endpoint metadata to see how the selected endpoint carries metadata that later middleware can use.
Model binding: where action arguments come from
After MVC begins executing a controller endpoint, it must supply values for the action’s parameters. Model binding is the process that obtains request data, converts it to .NET types, and records problems when conversion or input formatting fails.
For the Create action above:
public async Task<ActionResult<OrderDto>> Create(
CreateOrderRequest request,
[FromQuery] bool dryRun = false)
the framework commonly obtains:
requestfrom the JSON request body.dryRunfrom?dryRun=truein the query string.- A parameter named
id, when it appears in{id}in the route template, from route data.
[ApiController] provides useful binding-source inference rules. A complex type that is not a registered service is inferred as [FromBody]; a parameter whose name matches a route placeholder is inferred as [FromRoute]; remaining simple parameters are normally inferred as [FromQuery].
For clarity at public API boundaries, many teams still write the source explicitly when it prevents ambiguity:
[HttpGet("{id:int}")]
public async Task<ActionResult<OrderDto>> GetById(
[FromRoute] int id,
[FromQuery] bool includeItems = false)
{
// ...
}
[HttpPost]
public async Task<ActionResult<OrderDto>> Create(
[FromBody] CreateOrderRequest request,
[FromQuery] bool dryRun = false)
{
// ...
}
The most common model-binding misconception is treating it as only JSON deserialization. It also covers route values, query strings, headers, form data, and conversion to action parameter types.
A body is generally read once. Therefore, a controller action should not have two unrelated parameters both expected to come from the request body. Instead, define one request DTO that represents the input contract.
public sealed class CreateOrderRequest
{
[Required]
public int? CustomerId { get; init; }
[Required]
[MinLength(1)]
public List<CreateOrderItemRequest> Items { get; init; } = [];
}
public sealed class CreateOrderItemRequest
{
[Range(1, int.MaxValue)]
public int ProductId { get; init; }
[Range(1, 100)]
public int Quantity { get; init; }
}
Create web APIs with ASP.NET Core
Read Microsoft Learn’s Create web APIs with ASP.NET Core as the reference for API-controller conventions, automatic validation responses, and binding-source inference.
In “ApiController attribute” and “Automatic HTTP 400 responses,” read from automatic validation through the explanation of ValidationProblemDetails. Notice that the framework’s automatic 400 behavior is tied to [ApiController]. Then read “Binding source parameter inference.” Begin with the binding-source table. Continue through the inference rules, focusing on why sources are inferred and why two body-bound parameters are invalid.
Validation occurs after binding and can prevent action execution
Binding asks, “Can ASP.NET Core create a .NET value from the request?”
Validation asks, “Does that created value meet the API contract?”
Examples:
"quantity": "two"for an integer property is primarily a binding/conversion failure."quantity": 0with[Range(1, 100)]is a validation failure.- A missing body, malformed JSON, or missing required value can also produce model-state errors.
MVC stores these errors in ModelState. With [ApiController], an invalid model automatically produces 400 Bad Request before the action body executes.
That means this explicit check is normally redundant in an API controller:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
A clearer modern default is to allow the framework to generate the validation response. When custom validation logic needs to return an error in the same format, use ValidationProblem(...) rather than inventing a second error shape.
The response uses ValidationProblemDetails, a structured, machine-readable object. Clients can identify errors per field rather than parsing an English sentence.

For the CreateOrderRequest example, this invalid request:
{
"customerId": null,
"items": []
}
could generate a response shaped like:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-...",
"errors": {
"CustomerId": [
"The CustomerId field is required."
],
"Items": [
"The field Items must be a string or array type with a minimum length of '1'."
]
}
}
The precise messages can vary with framework version and configuration. The interview-relevant point is that the response has a stable envelope, a 400 status, per-field errors, and a trace identifier that helps correlate client reports with server telemetry.
Do not confuse this with business-rule validation. “The item quantity must be between 1 and 100” is input validation. “The requested quantity exceeds available inventory” may require a database lookup and often belongs in application/domain logic. It might produce a different response, such as 409 Conflict for a state conflict, depending on the API’s contract.
Interview rehearsal: trace an invalid request
Answer aloud before reading the model answer.
The Angular client sends
POST /api/orders?dryRun=falsewith a valid bearer token. The selected controller action takesCreateOrderRequest request, and the body is{ "customerId": 42, "items": [] }.Itemshas[MinLength(1)], and the controller has[ApiController]. Explain the request path and whether the action body executes.
Model answer
The request first enters the middleware pipeline. Exception handling and HTTPS behavior run according to their configured position. Routing matches POST /api/orders to the Create action. Authentication validates the bearer token and establishes the caller identity; authorization evaluates the selected endpoint’s authorization metadata.
MVC then activates the controller through DI and model binding deserializes the JSON body into CreateOrderRequest. Validation evaluates [MinLength(1)] and records an error in ModelState because items is empty.
Because the controller uses [ApiController], MVC’s automatic model-state filter returns a 400 Bad Request with a ValidationProblemDetails response. The action method body does not execute, so no order service or database write should occur. The response then passes back outward through upstream middleware, allowing request logging or response headers to be added if the response has not already started.
A concise version for an interview is:
Routing selects the action, authentication and authorization run before MVC executes it, then MVC binds and validates the request DTO. With
[ApiController], invalid model state short-circuits action execution and returns a structured400response automatically.
From action result to HTTP response
If validation succeeds, MVC invokes the action. Assume it calls an application service and returns:
[HttpPost]
public async Task<ActionResult<OrderDto>> Create(
[FromBody] CreateOrderRequest request,
[FromQuery] bool dryRun = false)
{
OrderDto created = await _orders.CreateAsync(request, dryRun);
return CreatedAtAction(
nameof(GetById),
new { id = created.Id },
created);
}
CreatedAtAction communicates an HTTP decision, not just a C# return value:
- It sets the status code to
201 Created. - It serializes
created, commonly as JSON. - It generates a
Locationheader pointing to the resource’s retrieval endpoint.
MVC uses output formatters and content negotiation to produce the response representation. In a JSON API, the normal result is JSON, but the request’s Accept header and configured formatters can affect the representation.
At this stage, response headers and body are being written. Middleware that resumes after await next() can observe the result, log duration and status, or add headers if the response has not started. It must not try to change status code or headers after the response has already begun.
A complete successful trace is therefore:
- Middleware establishes cross-cutting handling.
- Routing selects the
Createendpoint. - Authentication identifies the caller.
- Authorization evaluates permission for this endpoint.
- MVC creates the controller using the request DI scope.
- Model binding supplies the request DTO and query parameter.
- Validation confirms the request contract.
- The action invokes application logic and returns
CreatedAtAction. - MVC serializes the DTO and writes
201 Created. - Control returns through earlier middleware, which can log the final result.
Failure points and how to reason about them
When debugging, first ask which stage ended the request. This avoids putting breakpoints only in the controller when the request never reaches it.
| Symptom | Likely stage | What to inspect |
|---|---|---|
404 Not Found | Routing or no matching endpoint | URL, route template, route constraints, controller discovery, HTTP method |
405 Method Not Allowed | Routing found a path but not a compatible verb | HttpGet, HttpPost, HttpPut, or other action constraints |
400 Bad Request with field errors | Model binding or validation | Request body, Content-Type, DTO shape, conversion errors, validation attributes |
401 Unauthorized | Authentication or challenge | Missing, expired, malformed, or invalid credentials |
403 Forbidden | Authorization | Authenticated caller lacks the required role, claim, policy, or resource permission |
415 Unsupported Media Type | Input formatter/content type negotiation | Content-Type, expected request format, formatter configuration |
500 Internal Server Error | Unhandled exception | Exception logs, trace ID, exception middleware, downstream dependencies |
There are two senior-level cautions here:
- Do not expose raw exceptions or stack traces to clients in production. Exception-handling middleware should log the detailed exception internally and return a safe, consistent problem response externally.
- Do not treat every
400as a controller bug. An automatic validation400proves the request probably reached MVC and was rejected before the action. Route mismatch and authorization failures occur earlier.
A 90-second interview answer
For “Walk me through an ASP.NET Core Web API request,” you can answer:
ASP.NET Core receives the request as an
HttpContextand runs it through the middleware pipeline in the order configured inProgram.cs. Middleware can perform work before and after the next component, or short-circuit, so exception handling should be early and static files or authorization can end requests before a controller runs.Routing matches the request path and HTTP method to an endpoint and exposes its metadata. Authentication then establishes
HttpContext.User, and authorization evaluates the policy for that selected endpoint. For a controller endpoint, MVC activates the controller through DI, model-binds action parameters from sources such as route values, query strings, and JSON body, and validates the resulting model. With[ApiController], invalid model state automatically returns a structured400 ValidationProblemDetailsresponse without executing the action.If validation succeeds, the action calls application logic and returns an
IActionResultor value. MVC uses an output formatter, usually JSON, to write the status code, headers, and response body. Finally, the response unwinds through earlier middleware, allowing logging and other post-processing.
Key takeaways
- Middleware is an ordered, nested pipeline. A component can act before and after
next(), or terminate the request early. - Routing selects an endpoint using URL, HTTP method, and route constraints; it does not deserialize the body or execute the controller.
- Authentication establishes an identity; authorization uses the selected endpoint’s metadata to make an access decision.
- MVC then creates the controller through DI, binds input from request sources, validates it, and invokes the action only if processing has not already been short-circuited.
- With
[ApiController], invalid model state automatically produces a structured400 ValidationProblemDetailsresponse before the action body runs. - A returned action result is converted to HTTP status, headers, and a serialized response; earlier middleware then resumes on the response path.
Next, you will begin the Angular fundamentals module by explaining how components, templates, services, and Angular DI work together, including the difference between standalone and NgModule-based application bootstrap.
Can't find a good explanation? Sign up and we'll make it for you
Sign up