ASP.NET Core

The ASP.NET Core request pipeline: middleware, dependency injection, filters, routing, model binding, auth, and Web API features.

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, and Map extension 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:

  1. Exception handling
  2. HTTPS redirection
  3. Static files
  4. Routing
  5. Authentication
  6. Authorization
  7. Endpoints

Related Resources

FeatureASP.NETASP.NET Core
PlatformWindows onlyCross-platform (Windows, Linux, macOS)
FrameworkBuilt on .NET FrameworkBuilt on .NET Core/.NET 5+
PerformanceGoodSignificantly faster and more efficient
HostingIIS onlyIIS, Kestrel, Nginx, Apache, Docker
Open SourcePartiallyFully open source
ConfigurationWeb.config (XML)appsettings.json, environment variables
Dependency InjectionNot built-in (needs third-party)Built-in DI container
Web FormsSupportedNot supported
Project StructureComplex, tightly coupledModular, lightweight
Side-by-side deploymentNot supportedSupported
Cloud optimizationLimitedHighly 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

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

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:

  1. Authorization filters - Run first, verify authorization
  2. Resource filters - Run after authorization, good for caching
  3. Action filters - Run before and after action method execution
  4. Exception filters - Handle exceptions
  5. 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:

FeatureIActionResultActionResult<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.