ASP.NET - ASP.NET Core Endpoint Filters

ASP.NET Core Endpoint Filters are a mechanism for executing custom logic immediately before and after an endpoint handler runs. They are particularly useful with Minimal APIs, where they provide a focused way to apply common behavior to selected endpoints without placing that logic directly inside every handler. Microsoft describes endpoint filters as being able to run code before and after a handler, inspect or modify handler parameters, and intercept the response produced by the handler. (Microsoft Learn)

1. What Is an Endpoint Filter?

An endpoint filter is a component that sits around an endpoint's execution. Instead of writing the same validation, logging, or request-processing logic inside several endpoint methods, you can place that common logic in a filter.

For example, consider an API that creates users. Without a filter, the endpoint might have to perform validation itself:

app.MapPost("/users", (User user) =>
{
    if (string.IsNullOrEmpty(user.Name))
    {
        return Results.BadRequest("Name is required");
    }

    // Create user
    return Results.Ok(user);
});

If many endpoints require similar validation, repeating this logic makes the application harder to maintain. An endpoint filter can handle the common operation before the actual endpoint executes.

The basic flow becomes:

HTTP Request
     |
     v
Endpoint Filter
     |
     |-- Validate / Log / Inspect
     |
     v
Endpoint Handler
     |
     |-- Business Logic
     |
     v
Endpoint Filter
     |
     |-- Inspect / Modify Response
     |
     v
HTTP Response

This separation keeps the endpoint handler focused on its primary business responsibility.

2. Why Endpoint Filters Are Useful

Endpoint filters are mainly useful for handling behavior that needs to be applied around endpoint execution.

Common examples include:

  • Validating incoming request data

  • Logging information about requests and responses

  • Checking whether an endpoint supports a particular API version

  • Modifying endpoint parameters

  • Modifying or replacing endpoint results

  • Stopping an endpoint from executing when a condition is not satisfied

Microsoft specifically identifies request validation, request/response logging, and API-version validation as scenarios where endpoint filters can be useful. (Microsoft Learn)

The major advantage is code reuse. Instead of putting the same code into ten different endpoint handlers, a filter can contain the shared behavior and be applied where required.

3. Endpoint Filters and Minimal APIs

Endpoint filters are especially important in Minimal API development.

A Minimal API endpoint might look like this:

app.MapGet("/products/{id}", (int id) =>
{
    return Results.Ok($"Product ID: {id}");
});

A filter can be attached to this endpoint:

app.MapGet("/products/{id}", (int id) =>
{
    return Results.Ok($"Product ID: {id}");
})
.AddEndpointFilter(async (context, next) =>
{
    Console.WriteLine("Before endpoint");

    var result = await next(context);

    Console.WriteLine("After endpoint");

    return result;
});

Here, the filter executes before the endpoint handler and then continues to the handler through next(context).

Once the handler finishes, execution returns to the filter, allowing additional processing to take place.

4. Understanding EndpointFilterInvocationContext

The EndpointFilterInvocationContext object provides information about the current endpoint invocation.

It provides access to the HttpContext and to the arguments supplied to the endpoint handler. Microsoft documents the arguments as being available through the invocation context, allowing a filter to inspect or modify parameters before the handler runs. (Microsoft Learn)

For example:

app.MapPost("/products", (Product product) =>
{
    return Results.Ok(product);
})
.AddEndpointFilter(async (context, next) =>
{
    var product = context.GetArgument<Product>(0);

    if (product == null)
    {
        return Results.BadRequest("Product is required");
    }

    return await next(context);
});

The expression:

context.GetArgument<Product>(0)

retrieves the first argument supplied to the endpoint.

This makes endpoint filters particularly useful for validating parameters before business logic begins.

5. The Role of EndpointFilterDelegate

The next parameter represents the next component in the endpoint-filter execution chain.

Consider:

.AddEndpointFilter(async (context, next) =>
{
    Console.WriteLine("Before");

    var result = await next(context);

    Console.WriteLine("After");

    return result;
});

The important statement is:

var result = await next(context);

Calling next(context) allows execution to continue.

If the filter does not call next(context), it can prevent the endpoint from executing.

For example:

.AddEndpointFilter(async (context, next) =>
{
    return Results.BadRequest("Request rejected");
});

In this situation, the endpoint handler is never reached. This is called short-circuiting.

This capability is useful when a request should be rejected before reaching the main business logic.

6. Using Endpoint Filters for Validation

One of the most practical applications is request validation.

Suppose an application has a Todo object:

public class Todo
{
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

An endpoint could receive it as follows:

app.MapPost("/todos", (Todo todo) =>
{
    return Results.Ok(todo);
});

A filter can check the object before the endpoint executes:

app.MapPost("/todos", (Todo todo) =>
{
    return Results.Ok(todo);
})
.AddEndpointFilter(async (context, next) =>
{
    var todo = context.GetArgument<Todo>(0);

    if (todo == null || string.IsNullOrWhiteSpace(todo.Name))
    {
        return Results.BadRequest("Todo name is required");
    }

    return await next(context);
});

If the validation succeeds, the endpoint runs.

If the validation fails, the filter returns a response immediately and prevents the endpoint from running.

Microsoft's documentation provides request-object validation as a practical endpoint-filter scenario. (Microsoft Learn)

7. Implementing IEndpointFilter

Instead of writing a filter directly as a delegate, developers can create a class implementing the IEndpointFilter interface.

For example:

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

        if (product == null)
        {
            return Results.BadRequest("Product is required");
        }

        return await next(context);
    }
}

The filter can then be attached to an endpoint:

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

This approach is useful when the filtering logic is substantial or needs to be reused across multiple endpoints.

Microsoft documents both delegate-based filters and filters implemented through IEndpointFilter. (Microsoft Learn)

8. Multiple Endpoint Filters

More than one endpoint filter can be applied to an endpoint.

For example:

app.MapGet("/products", () =>
{
    return Results.Ok("Products");
})
.AddEndpointFilter(async (context, next) =>
{
    Console.WriteLine("Filter 1 Before");

    var result = await next(context);

    Console.WriteLine("Filter 1 After");

    return result;
})
.AddEndpointFilter(async (context, next) =>
{
    Console.WriteLine("Filter 2 Before");

    var result = await next(context);

    Console.WriteLine("Filter 2 After");

    return result;
});

The filters form a chain around the endpoint.

Conceptually, execution looks like:

Filter 1 Before
    |
    v
Filter 2 Before
    |
    v
Endpoint
    |
    v
Filter 2 After
    |
    v
Filter 1 After

Microsoft describes the execution order as FIFO for code executed before next and FILO for code executed after next. (Microsoft Learn)

This is similar to nested processing, where the first filter becomes the outermost layer.

9. Endpoint Filters vs Middleware

Endpoint filters and middleware may appear similar because both can execute code before and after request processing. However, they operate at different levels.

Middleware generally works at the broader HTTP request pipeline level. It can process requests before endpoint selection and can apply broadly across the application.

Endpoint filters are much more closely associated with a selected endpoint. They can access endpoint-specific arguments and can be applied to particular route handlers.

For example, middleware is appropriate for application-wide concerns such as general request logging, while an endpoint filter may be more appropriate when only certain API endpoints require a particular validation rule.

This distinction is important when designing ASP.NET Core applications. Filters are intended for logic associated with endpoint execution rather than replacing middleware throughout the entire HTTP pipeline.

10. Endpoint Filters vs MVC Action Filters

ASP.NET Core also has traditional MVC filters, including authorization, resource, action, exception, and result filters. These operate within the MVC/Razor Pages filter pipeline. (Microsoft Learn)

Endpoint filters are different because they can be used with route-handler-based endpoints, including Minimal APIs. Microsoft also documents their use with controller actions in certain scenarios. (Microsoft Learn)

Therefore, when working primarily with Minimal APIs, endpoint filters provide a natural mechanism for adding reusable logic around endpoint handlers.

11. Modifying Parameters

Endpoint filters are not limited to checking parameters. They can also inspect and modify the arguments associated with an endpoint invocation.

This can be useful when an application needs to normalize or transform incoming data before the endpoint processes it.

For example, a filter could examine a string parameter and normalize its value before passing execution to the endpoint.

The ability to access endpoint arguments through EndpointFilterInvocationContext is one of the features that distinguishes endpoint filters from more general request-processing mechanisms. (Microsoft Learn)

12. Modifying the Response

Endpoint filters can also inspect the result returned by an endpoint.

For example:

.AddEndpointFilter(async (context, next) =>
{
    var result = await next(context);

    Console.WriteLine("Endpoint completed");

    return result;
});

The filter receives the result after the endpoint has executed.

This allows applications to perform additional processing around the endpoint response when appropriate.

A filter can also return its own result instead of allowing the endpoint result to continue, which makes response interception another important capability.

13. Practical Applications

Endpoint filters can be useful in many real-world ASP.NET Core applications.

For example, an e-commerce API could use filters to validate product information before creating a product.

A booking application could use a filter to check whether required booking parameters are present.

An administrative API could use filters to record information about sensitive endpoint operations.

A versioned API could use filters to verify whether an incoming request targets a supported API version.

A business application could use filters to enforce common validation rules across several related endpoints.

These applications demonstrate the primary purpose of endpoint filters: centralizing reusable endpoint-level behavior without unnecessarily placing that behavior inside every handler.

14. Advantages of Endpoint Filters

The main advantages include:

Code reuse: Common logic can be written once and applied to multiple endpoints.

Separation of concerns: Endpoint handlers can concentrate on business logic while filters handle supporting operations.

Early validation: Invalid requests can be rejected before the main handler executes.

Parameter access: Filters can inspect endpoint arguments directly.

Response interception: Filters can examine or replace endpoint results.

Selective application: A filter can be attached to particular endpoints rather than affecting every request.

Improved maintainability: Changes to common validation or logging behavior can be made in one place.

15. Limitations and Considerations

Endpoint filters should not be used for every type of request processing.

If a concern needs to operate across virtually every HTTP request, middleware may be more appropriate.

Similarly, application business rules should generally remain in appropriate application or domain services rather than being hidden inside filters. Filters work best for cross-cutting behavior surrounding endpoint execution.

It is also important to avoid creating overly complicated filters. A filter that performs authentication, database operations, business calculations, validation, logging, and response transformation all at once can become difficult to understand and maintain.

A good filter should have a focused responsibility.

16. Summary

ASP.NET Core Endpoint Filters provide a structured way to execute reusable logic before and after endpoint handlers. They are particularly valuable in Minimal API applications because they can access endpoint-specific parameters, validate incoming data, modify arguments, intercept responses, and prevent an endpoint from executing when necessary. (Microsoft Learn)

The basic concept can be summarized as:

Request
   |
   v
Endpoint Filter
   |
   |-- Check or modify request
   |
   v
Endpoint Handler
   |
   |-- Execute business logic
   |
   v
Endpoint Filter
   |
   |-- Inspect or modify result
   |
   v
Response

The most important idea is that an endpoint filter provides a reusable layer around an endpoint, rather than forcing every endpoint to contain the same supporting logic. This makes it particularly useful for validation, logging, API-version checks, parameter processing, and other endpoint-level cross-cutting concerns. (Microsoft Learn)