When we send a request to a .NET Core Web API, it goes through series of steps before a response is returned. Understanding this lifecycle is important in order to built what we need and how to use some of the prebuilt functionalities. Diagram bellow shows the entire request - response pipeline.
Middleware Request Processing
When HTTP request hits .NET Core Web API, it first goes trough pipeline of asynchronous delegates RequestDelegate that can intercept and process the HttpContext before routing occurs. Each of them can be configured in Program.cs and each middleware executes short-lived logic before invoking await next(context) to pass the request down the execution chain.
Key things happening at this phase are:
-
Authentication/Authorization: Validates incoming JWT or cookie tokens to populate the
HttpContext.Useridentity principal. - CORS Policy Evaluation: Inspects origin headers and injects cross-origin access rules into the request context.
- Global Error Interception: Wraps the downstream pipeline in a try-catch block to catch unhandled runtime exceptions early.
An important mechanism here is pipeline short-circuiting. If a middleware component detects a validation failure (such as a missing access token), it skips invoking next() and populates the response payload, sets the termination HTTP status code (e.g., 401 Unauthorized), and immediately returns the result, completely bypassing downstream routing and controller actions.
Routing and Endpoint Matching
Routing determines which controller and action should handle the request. This process is split into a two-step middleware mechanism:
-
Endpoint Routing (
UseRouting): This step parses the incoming URL path, query string, and HTTP verb (GET, POST, etc.). It matches these values against the application’s compiled routing tree, selects the single best-matching route candidate, and attaches that endpoint metadata directly to the HttpContext. -
Endpoint Execution (
UseEndpoints): After intermediate middleware (such as Authorization) evaluates the selected endpoint’s policies, this component triggers the endpoint’s underlying RequestDelegate, triggering the controller action to execute.
Model Binding and Validation
After the endpoint is matched, the raw data is taken out of the HTTP request and being converted into strongly typed C# objects before passing them to the controller action. It maps data from the route path [FromRoute], query string [FromQuery], request headers [FromHeader], or parses the JSON payload from the request body [FromBody].
Once the C# object is populated, the framework evaluates data annotations applied to the target class ([Required], [StringLength], [EmailAddress], etc.) or custom validators. If validation fails, the framework can automatically return a 400 Bad Request.
Controller Action Execution
Now the actual controller method (action) is invoked. Here’s what happens:
- Dependency Injection: The controller is instantiated and required services are injected (such as database contexts, logging instances, or custom services)
-
Asynchronous Execution: The framework invokes the matched action method asynchronously, and returning a
Task<IActionResult>. This keeps threads free to be able to handle other traffic and not be blocked by the code that waits on heavy tasks, like database queries or external API calls. -
The Return Result: The controller’s primary job is not to build the actual HTTP response payload, but simply to return an object that implements
IActionResult(such asOk(),NotFound(), orBadRequest()).
Result Execution
This step marks the entry point into the response pipeline. Its job is to take the abstract IActionResult returned by the controller and translate it into a concrete action plan for the network response. The framework executes this transformation using these steps:
-
Result Processing: The runtime matches the type of
IActionResultto an executive class (for example, anOkObjectResulttriggers anObjectResultExecutor). This executor analyzes the rawC#object data contained inside the result. -
Content Negotiation: The executor inspects the incoming request’s Accept headers to determine what format the client prefers (such as
application/jsonorapplication/xml). It then selects the correct output formatter based on those preferences. - Serialization Preparation: The data object is serialized into the chosen format. While formatting happens here, the raw bytes are held in a memory buffer rather than being dumped straight onto the live network stream. This buffer allows later response middleware to inspect or alter headers safely before transmission.
Middleware Response Processing
After result execution, the control is returned to the exact same middleware components initialized during the request phase, and to the code written after their await next(context) statements. At this stage, middleware can:
- Modify or inspect the response (e.g., add custom headers).
- Log the outcome (status code, response time, etc.).
- Handle exceptions globally (via custom exception-handling middleware).
This is the final point where behavior can be influenced before the response goes back to the client. Before the first byte of data is sent over the network, response headers are permanently locked and they can no longer be modified.
Response Sent to Client
Finally, the HTTP response is sent back to the client. The web server flushes the buffered response body and HTTP headers directly into the network socket stream. The connection is then cleaned up and resources are disposed via the dependency injection container. At this point, the request lifecycle is complete.
The .NET Core Web API request lifecycle is powerful and highly extensible. If we correctly understand each step, we can design better APIs and create a maintainable architecture that scales well.
Happy coding!

Top comments (0)