Filters in .NET are a powerful mechanism for handling the request and response pipeline in ASP.NET Core applications. They enable developers to encapsulate logic that applies to multiple controllers or actions, making the codebase cleaner, more modular, and easier to maintain. This blog will explore the six types of filters in .NET: Authorization, Resource, Action, Exception, Result, and Endpoint filters.
Let’s dive deep into all types of action filters with an example.
Table of Contents
1. Authorization Filters
- Run first.
- Determine whether the user is authorized for the request.
- Short-circuit the pipeline if the request is not authorized.
Role: Ensure requests meet security requirements before proceeding.
Example Code:
public class CustomAuthorizationFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
context.Result = new UnauthorizedResult();
}
}
}
Integration: Register the filter globally in Program.cs
builder.Services.AddControllers(options =>
{
options.Filters.Add<CustomAuthorizationFilter>();
});
Use Case: Protect sensitive endpoints such as user profiles or payment systems.
2. Resource Filters
- Run after authorization.
- OnResourceExecuting runs code before the rest of the filter pipeline. For example, OnResourceExecuting runs code before model binding.
- OnResourceExecuted runs code after the rest of the pipeline has completed.
Role: Handle resource allocation and short-circuit requests before reaching the action.
Example Code:
public class CustomResourceFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
// Logic before the action executes
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
// Logic after the action executes
}
}
Integration: Apply at the controller level
[CustomResourceFilter]
public class ProductController : ControllerBase
{
// Actions
}
Use Case: Measure execution time for performance monitoring.
3. Action Filters
- Run immediately before and after an action method is called.
- Can change the arguments passed into an action.
- Can change the result returned from the action.
Role: Execute logic before and after action methods.
Example Code:
public class CustomActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Pre-processing logic
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Post-processing logic
}
}
Integration: Apply to specific actions
[CustomActionFilter]
public IActionResult GetProducts()
{
return Ok();
}
Use Case: Logging request and response details.
4. Endpoint Filters
- Run immediately before and after an action method is called.
- Can change the arguments passed into an action.
- Can change the result returned from the action.
Role: Apply logic at the endpoint level.
Example Code:
public class CustomEndpointFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
// Pre-endpoint logic
var result = await next(context);
// Post-endpoint logic
return result;
}
}
Integration: Use in minimal APIs
app.MapGet("/products", [CustomEndpointFilter] () => { return new { Message = "Hello" }; });
Use Case: Input validation and data transformation.
5. Exception Filters
Role: Centralized handling of unhandled exceptions.
Example Code:
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
context.Result = new ObjectResult(new { Error = context.Exception.Message })
{
StatusCode = 500
};
}
}
Integration: Register globally in Program.cs
builder.Services.AddControllers(options =>
{
options.Filters.Add<CustomExceptionFilter>();
});
Use Case: Log and return friendly error messages.
6. Result Filters
- Run immediately before and after the execution of action results.
- Run only when the action method executes successfully.
Role: Modify or format responses before they are sent to the client.
Example Code:
public class CustomResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
// Modify response headers or content
}
public void OnResultExecuted(ResultExecutedContext context)
{
// Logic after response is sent
}
}
Integration: Apply to specific controllers or globally.
[CustomResultFilter ]
public IActionResult GetAllProducts()
{
return Ok();
}
Use Case: Add custom headers or modify JSON responses.
Filter Scope: Global, Controller, and Action Levels
Filters can be applied at different levels:
Global: Applies to all controllers and actions.
builder.Services.AddControllers(options =>
{
options.Filters.Add<CustomAuthorizationFilter>();
});
Controller: This applies to all actions within a specific controller.
[CustomResourceFilter]
public class OrdersController : ControllerBase { }
Action: Applies to specific actions only.
[CustomActionFilter]
public IActionResult GetOrderDetails(int id) { }
Example Of All Filters in .NET Application
I am using .Net 8 and the API project to demonstrate all filters in the .Net application.
1. Create Models
Create an Employee model to represent the data.
// Models/Employee.cs
namespace WebApplication1.Models
{
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
2. Create Filters
Define filters for each type.
Authorization Filter
// Filters/EmpAuthorizationFilter.cs
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1.Filters
{
public class EmpAuthorizationFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var isAuthenticated = context.HttpContext.User.Identity?.IsAuthenticated ?? false;
if (!isAuthenticated)
{
context.Result = new UnauthorizedResult();
}
}
}
}
Resource Filter
// Filters/EmpResourceFilter.cs
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
namespace WebApplication1.Filters
{
public class EmpResourceFilter : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
Debug.WriteLine("Resource Filter - Before executing the resource");
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
Debug.WriteLine("Resource Filter - After executing the resource");
}
}
}
Action Filter
// Filters/EmpActionFilter.cs
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
namespace WebApplication1.Filters
{
public class EmpActionFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
Debug.WriteLine("Action Filter - Before executing the action");
}
public void OnActionExecuted(ActionExecutedContext context)
{
Debug.WriteLine("Action Filter - After executing the action");
}
}
}
Exception Filter
// Filters/EmpExceptionFilter.cs
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace WebApplication1.Filters
{
public class EmpExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
Debug.WriteLine($"Exception Filter - Exception occurred: {context.Exception.Message}");
context.Result = new ObjectResult("An error occurred while processing your request.")
{
StatusCode = 500
};
}
}
}
Result Filter
// Filters/EmpResultFilter.cs
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
namespace WebApplication1.Filters
{
public class EmpResultFilter : Attribute, IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
Debug.WriteLine("Result Filter - Before sending the result");
}
public void OnResultExecuted(ResultExecutedContext context)
{
Debug.WriteLine("Result Filter - After sending the result");
}
}
}
Endpoint Filter
// Filters/EmpEndpointFilter.cs
using System.Diagnostics;
namespace WebApplication1.Filters
{
public class EmpEndpointFilter : Attribute, IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
Debug.WriteLine("Endpoint Filter - Before processing the endpoint");
var result = await next(context);
Debug.WriteLine("Endpoint Filter - After processing the endpoint");
return result;
}
}
}
3. Register Filters in Program.cs
I added EmpExceptionFilter in the Program.cs File so the exception filter is applied globally.
builder.Services.AddControllers(options =>
{
// Global filters
options.Filters.Add<EmpExceptionFilter>(); // Applied globally
});
4. Create the Controller
Create an EmployeeController to manage employee records.
// Controllers/EmployeeController.cs
using Microsoft.AspNetCore.Mvc;
using WebApplication1.Filters;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
[Route("api/[controller]")]
[ApiController]
//[EmpAuthorizationFilter] // Controller-level filter
public class EmployeeController : ControllerBase
{
private static readonly List<Employee> EmpList = new List<Employee>
{
new Employee { Id = 1, FirstName = "John", LastName = "Doe", Email = "johndoe@gmail.com" },
new Employee { Id = 2, FirstName = "Jane", LastName = "Smith", Email = "johnsmith@gmail.com" }
};
[HttpGet]
[EmpActionFilter] // Action-level filter
[EmpResourceFilter] // Action-level filter
public IActionResult GetEmployeeList()
{
return Ok(EmpList);
}
[HttpGet("{id}")]
[EmpEndpointFilter] // Action-level filter
[EmpResultFilter] // Action-level filter
public IActionResult GetEmployee(int id)
{
var empDetail = EmpList.FirstOrDefault(b => b.Id == id);
if (empDetail == null)
{
return NotFound();
}
return Ok(empDetail);
}
}
}
Testing the API
You can test these APIs from Swagger or Postman. I run the project and hit the GetEmployeeList API. The filer execution is displayed in the output window.
Overview of Filters in .NET
Filters provide a way to execute code at different stages of the request and response lifecycle. Below is an overview of the six types:
- Authorization Filters: Handle role-based access and authentication.
- Resource Filters: Manage resources and short-circuit requests if needed.
- Action Filters: Preprocess or postprocess logic around action execution.
- Exception Filters: Centralized handling of exceptions and errors.
- Result Filters: Customize response generation.
- Endpoint Filters: Enable endpoint-level logic customization.
Real-World Application: Web API for Online Store
Imagine a Web API for managing an online store:
- Authorization Filter: Restrict access to admin endpoints.
- Resource Filter: Cache responses for performance.
- Action Filter: Log request payloads.
- Exception Filter: Handle product-not-found exceptions.
- Result Filter: Format responses with custom headers.
- Endpoint Filter: Validate product IDs in minimal APIs.
Best Practices and Performance Tips
- Avoid Overuse: Only use filters when necessary to prevent unnecessary complexity.
- Understand Execution Order: Ensure the correct sequence of filters for predictable behavior.
- Combine with Middleware: Leverage middleware for broader request processing needs.
- Debugging: Use logging to debug filter execution effectively.
Conclusion
Filters in .NET offer a powerful way to manage the request and response pipeline. By effectively understanding and leveraging these filters, you can write cleaner, more maintainable code while enhancing the functionality of your applications. Start experimenting with filters today and unlock their full potential in your .NET projects!
Also Read,
- Generating PDFs from Razor Views Using NReco PDF Generator
- Exporting Data to Excel in ASP.NET Core 8 Using EPPlus
- Reverse Engineering in Entity Framework Core