ASP.NET Core  

ASP.NET Core Endpoint Filters: Complete Guide with Examples

Introduction

ASP.NET Core Minimal APIs have become increasingly popular because they allow developers to build APIs with less code and better performance. However, as applications grow, developers often need to perform common tasks before or after an endpoint executes. Examples include request validation, logging, authorization checks, performance monitoring, and modifying responses.

Before ASP.NET Core Endpoint Filters were introduced, developers typically relied on middleware, action filters, or repetitive code inside endpoints. Endpoint Filters provide a cleaner and more focused way to handle cross-cutting concerns specifically for Minimal APIs.

In this article, you'll learn what Endpoint Filters are, how they work, when to use them, and how to implement them with practical examples.

What Are Endpoint Filters?

Endpoint Filters are components that run before and after a Minimal API endpoint executes.

They act like a pipeline around an endpoint, allowing developers to:

  • Validate incoming requests

  • Log request and response information

  • Modify request data

  • Modify response data

  • Handle exceptions

  • Measure execution time

  • Apply custom business rules

Think of Endpoint Filters as a security guard standing at the entrance of a building.

Before someone enters:

  • The guard checks their identity.

  • The guard verifies permissions.

  • The guard records visitor information.

After the visitor leaves:

  • The guard logs departure details.

  • The guard records the outcome.

Endpoint Filters work similarly around your API endpoints.

Why Were Endpoint Filters Introduced?

Minimal APIs are intentionally lightweight. However, developers quickly discovered that repeating validation and logging code inside every endpoint made applications harder to maintain.

Consider this example:

app.MapPost("/products", (Product product) =>
{
    if (string.IsNullOrEmpty(product.Name))
    {
        return Results.BadRequest("Product name is required.");
    }

    Console.WriteLine("Product created");

    return Results.Ok(product);
});

Now imagine hundreds of endpoints requiring similar validation and logging.

This creates:

  • Duplicate code

  • Maintenance challenges

  • Inconsistent implementations

Endpoint Filters solve this problem by centralizing such logic.

How Endpoint Filters Work

The execution flow looks like this:

Request
   ↓
Endpoint Filter
   ↓
Endpoint Logic
   ↓
Endpoint Filter
   ↓
Response

The filter can execute code:

  • Before the endpoint runs

  • After the endpoint runs

This gives developers full control over the request and response lifecycle.

Creating Your First Endpoint Filter

Let's build a simple logging filter.

Create a new class:

using Microsoft.AspNetCore.Http;

public class LoggingFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        Console.WriteLine($"Request started at {DateTime.UtcNow}");

        var result = await next(context);

        Console.WriteLine($"Request completed at {DateTime.UtcNow}");

        return result;
    }
}

Registering the Filter

Attach the filter to a Minimal API endpoint:

app.MapGet("/hello", () =>
{
    return "Hello World";
})
.AddEndpointFilter<LoggingFilter>();

When the endpoint is called:

  1. LoggingFilter executes first.

  2. Endpoint logic executes.

  3. LoggingFilter executes again after completion.

Understanding the Key Components

EndpointFilterInvocationContext

This object contains information about the current request.

Example:

var httpContext = context.HttpContext;

You can access:

  • Request headers

  • Query parameters

  • Route values

  • User information

  • Services

EndpointFilterDelegate

The next parameter represents the next step in the pipeline.

var result = await next(context);

Calling next() allows execution to continue.

If next() is not called, endpoint execution stops.

Request Validation Example

One of the most common uses of Endpoint Filters is validation.

Suppose we have a Product model:

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Create a validation filter:

public class ProductValidationFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var product = context.GetArgument<Product>(0);

        if (string.IsNullOrWhiteSpace(product.Name))
        {
            return Results.BadRequest("Product name is required.");
        }

        if (product.Price <= 0)
        {
            return Results.BadRequest("Price must be greater than zero.");
        }

        return await next(context);
    }
}

Apply the filter:

app.MapPost("/products", (Product product) =>
{
    return Results.Ok(product);
})
.AddEndpointFilter<ProductValidationFilter>();

Now validation logic remains separate from business logic.

Measuring API Performance

Performance monitoring is another useful scenario.

using System.Diagnostics;

public class PerformanceFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var stopwatch = Stopwatch.StartNew();

        var result = await next(context);

        stopwatch.Stop();

        Console.WriteLine(
            $"Execution Time: {stopwatch.ElapsedMilliseconds} ms");

        return result;
    }
}

This helps identify slow endpoints in production environments.

Modifying Responses

Endpoint Filters can also modify responses.

public class ResponseWrapperFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var result = await next(context);

        return Results.Ok(new
        {
            Success = true,
            Data = result
        });
    }
}

This creates a consistent API response structure.

Example output:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "Laptop"
  }
}

Authorization Example

You can implement custom authorization checks.

public class AdminOnlyFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var user = context.HttpContext.User;

        if (!user.IsInRole("Admin"))
        {
            return Results.Unauthorized();
        }

        return await next(context);
    }
}

Apply it:

app.MapDelete("/products/{id}", (int id) =>
{
    return Results.Ok();
})
.AddEndpointFilter<AdminOnlyFilter>();

Only administrators can access the endpoint.

Handling Exceptions

Centralized exception handling improves application reliability.

public class ExceptionFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        try
        {
            return await next(context);
        }
        catch (Exception ex)
        {
            return Results.Problem(
                title: "Application Error",
                detail: ex.Message);
        }
    }
}

This prevents unhandled exceptions from exposing internal application details.

Applying Multiple Endpoint Filters

You can chain multiple filters together.

app.MapPost("/products", (Product product) =>
{
    return Results.Ok(product);
})
.AddEndpointFilter<LoggingFilter>()
.AddEndpointFilter<ProductValidationFilter>()
.AddEndpointFilter<PerformanceFilter>();

Execution order:

Logging Filter
    ↓
Validation Filter
    ↓
Performance Filter
    ↓
Endpoint
    ↓
Performance Filter
    ↓
Validation Filter
    ↓
Logging Filter

This behavior is similar to middleware pipelines.

Endpoint Filters vs Middleware

Many developers wonder when to use Middleware and when to use Endpoint Filters.

FeatureMiddlewareEndpoint Filter
ScopeEntire ApplicationSpecific Endpoint
Minimal API SupportYesYes
Access Endpoint ParametersNoYes
Request ValidationLimitedExcellent
Response ModificationYesYes
LoggingExcellentExcellent

Use Middleware when:

  • Logic applies globally.

  • Authentication is required.

  • Request logging is application-wide.

Use Endpoint Filters when:

  • Logic applies to specific endpoints.

  • Request validation is required.

  • Endpoint-specific business rules are needed.

Endpoint Filters vs MVC Action Filters

FeatureMVC Action FiltersEndpoint Filters
MVC ControllersSupportedNot Applicable
Minimal APIsNot SupportedSupported
LightweightModerateHigh
PerformanceGoodExcellent

If you are using Minimal APIs, Endpoint Filters are usually the preferred solution.

Real-World Use Cases

Organizations commonly use Endpoint Filters for:

  • API request validation

  • Audit logging

  • Execution time monitoring

  • User activity tracking

  • Rate limiting checks

  • Data transformation

  • Feature flag validation

  • Tenant validation in multi-tenant systems

  • Request enrichment

  • Security enforcement

For example, in an e-commerce application:

  • Validation Filter checks product data.

  • Logging Filter records transactions.

  • Performance Filter measures execution time.

  • Authorization Filter verifies user permissions.

Each responsibility remains separate and maintainable.

Advantages of Endpoint Filters

Endpoint Filters provide several benefits:

  • Cleaner endpoint code

  • Better code reusability

  • Reduced duplication

  • Easier maintenance

  • Improved testing

  • Better separation of concerns

  • Strong support for Minimal APIs

  • High performance

Limitations of Endpoint Filters

While powerful, Endpoint Filters have some limitations:

  • Only work with Minimal APIs

  • Not intended to replace middleware entirely

  • Complex application-wide concerns still belong in middleware

  • Can become difficult to manage if too many filters are chained

Understanding when to use them is important.

Best Practices

When working with Endpoint Filters:

  • Keep filters focused on a single responsibility.

  • Avoid placing business logic inside filters.

  • Use filters for validation, logging, and cross-cutting concerns.

  • Reuse filters across endpoints whenever possible.

  • Keep execution lightweight.

  • Combine filters thoughtfully to avoid complexity.

Conclusion

ASP.NET Core Endpoint Filters are a powerful feature designed specifically for Minimal APIs. They provide a clean and reusable way to implement validation, logging, authorization, performance monitoring, exception handling, and response transformation without cluttering endpoint code.

By separating cross-cutting concerns from business logic, Endpoint Filters make applications easier to maintain, test, and scale. Whether you're building a small API or a large enterprise application, understanding Endpoint Filters can significantly improve the structure and quality of your ASP.NET Core projects.

As Minimal APIs continue to gain popularity, Endpoint Filters have become an essential tool that every ASP.NET Core developer should understand and use effectively.