The middleware pipeline in ASP.NET Core is a series of components that handle HTTP requests and responses. Each middleware component can:
- Process an incoming request before passing it to the next component
- Process the outgoing response after the next component has executed
- Short-circuit the pipeline by not calling the next middleware
Key characteristics:
- Middleware components are executed in the order they are added
- Each component can perform operations before and after the next component
- Built using the
Use,Run, andMapextension methods
Example:
public void Configure(IApplicationBuilder app)
{
// Middleware 1
app.Use(async (context, next) =>
{
// Do work before next middleware
await next.Invoke();
// Do work after next middleware
});
// Middleware 2
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// Terminal middleware
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Common middleware order:
- Exception handling
- HTTPS redirection
- Static files
- Routing
- Authentication
- Authorization
- Endpoints
Related Resources
| Feature | ASP.NET | ASP.NET Core |
|---|---|---|
| Platform | Windows only | Cross-platform (Windows, Linux, macOS) |
| Framework | Built on .NET Framework | Built on .NET Core/.NET 5+ |
| Performance | Good | Significantly faster and more efficient |
| Hosting | IIS only | IIS, Kestrel, Nginx, Apache, Docker |
| Open Source | Partially | Fully open source |
| Configuration | Web.config (XML) | appsettings.json, environment variables |
| Dependency Injection | Not built-in (needs third-party) | Built-in DI container |
| Web Forms | Supported | Not supported |
| Project Structure | Complex, tightly coupled | Modular, lightweight |
| Side-by-side deployment | Not supported | Supported |
| Cloud optimization | Limited | Highly optimized for cloud |
Key advantages of ASP.NET Core:
- Better performance and scalability
- Modern architecture with cleaner separation of concerns
- Built-in dependency injection
- Unified programming model for web UI and web APIs
- Can run on multiple platforms
Related Resources
Dependency Injection (DI) is a built-in design pattern in ASP.NET Core that achieves Inversion of Control (IoC) between classes and their dependencies. Services are registered with specific lifetimes.
Service Lifetimes:
4.3.1. Transient
- A new instance is created every time the service is requested
- Best for lightweight, stateless services
- Registered using
AddTransient<TService, TImplementation>()
services.AddTransient();
Use case: Operations that don't maintain state, like sending emails or generating random numbers.
4.3.2. Scoped
- A single instance is created per client request (HTTP request)
- The same instance is used throughout the entire request
- Registered using
AddScoped<TService, TImplementation>()
services.AddScoped();
Use case: Database contexts (Entity Framework), repository patterns, services that need to maintain state during a request.
4.3.3. Singleton
- A single instance is created for the entire application lifetime
- The same instance is shared across all requests
- Registered using
AddSingleton<TService, TImplementation>()
services.AddSingleton();
Use case: Configuration, logging, caching services, thread-safe services.
Registration example in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Transient
builder.Services.AddTransient();
// Scoped
builder.Services.AddScoped();
// Singleton
builder.Services.AddSingleton(builder.Configuration);
var app = builder.Build();
Important considerations:
- Avoid injecting scoped or transient services into singleton services (causes memory leaks)
- Singleton services must be thread-safe
- Scoped is the most commonly used lifetime for business logic
Related Resources
Action filters are attributes that add extra processing logic before or after specific stages in the request processing pipeline. They allow cross-cutting concerns like logging, caching, authorization, and exception handling.
Filter types and execution order:
- Authorization filters - Run first, verify authorization
- Resource filters - Run after authorization, good for caching
- Action filters - Run before and after action method execution
- Exception filters - Handle exceptions
- Result filters - Run before and after result execution
Creating a custom action filter:
// Method 1: Inherit from ActionFilterAttribute
public class LogActivityFilter : ActionFilterAttribute
{
private readonly ILogger _logger;
public LogActivityFilter(ILogger logger)
{
_logger = logger;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation($"Executing action: {context.ActionDescriptor.DisplayName}");
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation($"Executed action: {context.ActionDescriptor.DisplayName}");
base.OnActionExecuted(context);
}
}
// Method 2: Implement IActionFilter interface
public class ValidateModelFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Logic after action execution
}
}
// Async version
public class AsyncLoggingFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// Before action execution
await next(); // Execute the action
// After action execution
}
}
Using filters:
// Apply to specific action
[LogActivityFilter]
public IActionResult GetUser(int id)
{
return Ok();
}
// Apply to controller
[ValidateModelFilter]
public class UsersController : ControllerBase
{
}
// Register globally in Program.cs
builder.Services.AddControllers(options =>
{
options.Filters.Add();
});
Related Resources
These are different return types for controller actions in ASP.NET Core, each with specific use cases.
4.5.1. IActionResult
- Non-generic interface
- Can return any type of action result
- No compile-time type safety for the return value
- Requires runtime type checking
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
var user = _userService.GetUser(id);
if (user == null)
return NotFound(); // Returns 404
return Ok(user); // Returns 200 with user object
}
Pros: Flexible, can return different result types Cons: No automatic OpenAPI/Swagger documentation for response type
4.5.2. ActionResult<T>
- Generic wrapper combining IActionResult flexibility with type safety
- Best of both worlds approach
- Enables automatic API documentation
- Implicit conversion from T or ActionResult
[HttpGet("{id}")]
public ActionResult GetUser(int id)
{
var user = _userService.GetUser(id);
if (user == null)
return NotFound(); // Returns ActionResult
return user; // Implicitly converted to ActionResult
}
Pros:
- Type safety
- Automatic OpenAPI/Swagger documentation
- Can still return status codes like NotFound(), BadRequest()
Cons: Can only specify one success return type
4.5.3. Concrete Type
- Returns the actual object type directly
- Simplest approach
- Always returns 200 OK status
- Cannot return different status codes
[HttpGet]
public List GetAllUsers()
{
return _userService.GetAllUsers();
}
[HttpGet("{id}")]
public User GetUser(int id)
{
return _userService.GetUser(id); // Always 200 OK
}
Pros: Simple, clean code for straightforward scenarios Cons:
- Cannot return different HTTP status codes
- No error handling at the action level
- Limited flexibility
Comparison table:
| Feature | IActionResult | ActionResult<T> | Concrete Type |
|---|---|---|---|
| Multiple return types | ✅ Yes | ✅ Yes | ❌ No |
| Type safety | ❌ No | ✅ Yes | ✅ Yes |
| OpenAPI documentation | ⚠️ Manual | ✅ Automatic | ✅ Automatic |
| HTTP status control | ✅ Full | ✅ Full | ❌ Always 200 |
| Recommended for APIs | ⚠️ Legacy | ✅ Best choice | ❌ Limited use |
Best practice: Use ActionResult<T> for modern ASP.NET Core Web APIs as it provides the best balance of flexibility and type safety.