Request/Response Cycle
🎯 What You'll Learn
- Complete request/response lifecycle in ASP.NET Core
- How requests flow through the pipeline
- Routing and endpoint selection
- Model binding
- Action execution
- Response generation
- Practical InvenTrack examples
The Complete Lifecycle
1. HTTP Request arrives
↓
2. Middleware Pipeline
├─ Exception Handler
├─ HTTPS Redirection
├─ Static Files (may short-circuit)
├─ Routing
├─ CORS
├─ Authentication
└─ Authorization
↓
3. Endpoint Routing
├─ Match URL to endpoint
└─ Select controller/action
↓
4. Model Binding
├─ Extract data from request
├─ Convert to C# objects
└─ Validate data
↓
5. Action Execution
├─ Execute controller method
├─ Business logic
└─ Generate result
↓
6. Result Execution
├─ Convert result to HTTP response
├─ Serialize data (JSON, XML)
└─ Set status code & headers
↓
7. Response flows back through middleware
↓
8. HTTP Response sent to client
Step-by-Step Example
Let's trace a request through InvenTrack:
GET /api/products/123 HTTP/1.1
Host: inventrackapp.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Step 1: Request Arrives
The HTTP request enters ASP.NET Core's web server (Kestrel).
Step 2: Middleware Pipeline
app.UseExceptionHandler("/error"); // 1. Exception handling
app.UseHttpsRedirection(); // 2. Redirect to HTTPS
app.UseStaticFiles(); // 3. Check for static files
app.UseRouting(); // 4. Match endpoint
app.UseCors(); // 5. CORS headers
app.UseAuthentication(); // 6. Identify user
app.UseAuthorization(); // 7. Check permissions
app.MapControllers(); // 8. Execute endpoint
Step 3: Routing
ASP.NET Core matches the URL /api/products/123 to a controller action:
[ApiController]
[Route("api/[controller]")] // Matches "api/products"
public class ProductsController : ControllerBase
{
[HttpGet("{id}")] // Matches "123"
public async Task<IActionResult> GetProduct(int id)
{
// This method will be executed!
}
}
Step 4: Model Binding
ASP.NET Core extracts 123 from the URL and binds it to the id parameter:
// URL: /api/products/123
// Route template: {id}
// Result: id = 123
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id) // id = 123
{
// ...
}
Step 5: Action Execution
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
// 1. Retrieve from database
var product = await _productService.GetByIdAsync(id);
// 2. Check if found
if (product == null)
return NotFound();
// 3. Return result
return Ok(product);
}
Step 6: Result Execution
ASP.NET Core converts the result to an HTTP response:
// Ok(product) becomes:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 145
{
"id": 123,
"name": "Laptop",
"price": 999.99,
"quantityInStock": 50
}
Step 7: Response Through Middleware
The response flows back through the middleware pipeline in reverse order.
Step 8: Response Sent
The HTTP response is sent back to the client.
Model Binding in Detail
Model binding extracts data from various sources:
1. Route Parameters
// URL: /api/products/123
[HttpGet("{id}")]
public IActionResult GetProduct(int id) // id = 123
{
return Ok();
}
2. Query String
// URL: /api/products?page=2&pageSize=10
[HttpGet]
public IActionResult GetProducts(int page, int pageSize)
{
// page = 2, pageSize = 10
return Ok();
}
3. Request Body
// POST /api/products
// Body: { "name": "Laptop", "price": 999.99 }
[HttpPost]
public IActionResult CreateProduct([FromBody] ProductDto product)
{
// product.Name = "Laptop"
// product.Price = 999.99
return Created();
}
4. Headers
// Header: X-API-Key: secret123
[HttpGet]
public IActionResult GetProducts([FromHeader(Name = "X-API-Key")] string apiKey)
{
// apiKey = "secret123"
return Ok();
}
5. Complex Objects
// URL: /api/products/123?includeDetails=true
// Body: { "quantity": 10 }
[HttpPut("{id}")]
public IActionResult UpdateStock(
int id, // From route
bool includeDetails, // From query
[FromBody] StockUpdateDto update) // From body
{
return Ok();
}
Response Generation
ASP.NET Core provides helper methods to generate responses:
| Method | Status Code | Use Case |
|---|---|---|
Ok(data) |
200 | Successful GET request |
Created(uri, data) |
201 | Resource created (POST) |
NoContent() |
204 | Successful DELETE/PUT |
BadRequest() |
400 | Invalid request data |
Unauthorized() |
401 | Not authenticated |
Forbid() |
403 | Not authorized |
NotFound() |
404 | Resource not found |
Conflict() |
409 | Resource conflict |
Complete InvenTrack Example
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
private readonly ILogger<ProductsController> _logger;
public ProductsController(
IProductService productService,
ILogger<ProductsController> logger)
{
_productService = productService;
_logger = logger;
}
// GET /api/products
[HttpGet]
public async Task<IActionResult> GetProducts(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10)
{
_logger.LogInformation("Getting products - Page: {Page}, PageSize: {PageSize}", page, pageSize);
var products = await _productService.GetPagedAsync(page, pageSize);
return Ok(products);
}
// GET /api/products/123
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
_logger.LogInformation("Getting product with ID: {Id}", id);
var product = await _productService.GetByIdAsync(id);
if (product == null)
{
_logger.LogWarning("Product not found: {Id}", id);
return NotFound(new { message = $"Product with ID {id} not found" });
}
return Ok(product);
}
// POST /api/products
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto dto)
{
_logger.LogInformation("Creating product: {Name}", dto.Name);
var product = await _productService.CreateAsync(dto);
return CreatedAtAction(
nameof(GetProduct),
new { id = product.Id },
product);
}
// PUT /api/products/123
[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(
int id,
[FromBody] UpdateProductDto dto)
{
_logger.LogInformation("Updating product: {Id}", id);
var success = await _productService.UpdateAsync(id, dto);
if (!success)
return NotFound();
return NoContent();
}
// DELETE /api/products/123
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProduct(int id)
{
_logger.LogInformation("Deleting product: {Id}", id);
var success = await _productService.DeleteAsync(id);
if (!success)
return NotFound();
return NoContent();
}
}
Key Takeaways
- 8-step lifecycle: Request → Middleware → Routing → Binding → Execution → Result → Middleware → Response
- Middleware pipeline: Processes request before reaching controller
- Routing: Matches URL to controller/action
- Model binding: Extracts data from route, query, body, headers
- Action execution: Business logic runs
- Result execution: Converts result to HTTP response
- Use
Ok(),Created(),NotFound(), etc. for responses - Model binding sources:
[FromRoute],[FromQuery],[FromBody],[FromHeader] - Response flows back through middleware in reverse
- Logging helps trace request flow
You now understand the complete request/response cycle! In the next section, we'll explore HTTP Methods—GET, POST, PUT, DELETE, PATCH, and when to use each one.