Description
When building APIs, sometimes you need to apply restrictions before executing your business logic. For example, you may want to block underage customers from accessing certain resources or prevent blacklisted customers from calling any API at all. ASP.NET Core provides two powerful approaches to handle these scenarios: Action Filters and Middleware.
In this article, we’ll explore both techniques with real-world examples (age-based restrictions and blacklist checks) and see how they compare.
Approach 1. Restricting Calls with an Action Filter (Age Filter)
Action Filters let you run custom logic before or after a controller action executes. They’re ideal when the restriction applies to specific APIs only.
Example. Age-based Access Restriction
Imagine we have an API endpoint that fetches products for a customer:
GET /api/product/byCustomer/{customerId}
We want to ensure only customers 18 years or older can access this endpoint.
AgeFilter Attribute
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace ProductFilteringExample.Api.Filters
{
public class AgeFilterAttribute : Attribute, IAsyncActionFilter
{
private readonly int _minAge;
public AgeFilterAttribute(int minAge)
{
_minAge = minAge;
}
// Mock data: customer ages
private readonly Dictionary<string, int> _customerAges = new()
{
{ "cust123", 25 },
{ "cust456", 16 },
{ "cust789", 40 }
};
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.ActionArguments.TryGetValue("customerId", out var custIdObj))
{
var customerId = custIdObj?.ToString();
if (!string.IsNullOrEmpty(customerId) && _customerAges.TryGetValue(customerId, out var age))
{
if (age < _minAge)
{
context.Result = new ObjectResult(new
{
Status = 403,
Message = $"Customer {customerId} is under age. Must be at least {_minAge}."
})
{
StatusCode = StatusCodes.Status403Forbidden
};
return; // stop pipeline
}
}
else
{
context.Result = new NotFoundObjectResult(new
{
Status = 404,
Message = $"Customer {customerId} not found."
});
return;
}
}
await next();
}
}
}
Controller Usage
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
private readonly IProductService _service;
public ProductController(IProductService service)
{
_service = service;
}
[HttpGet("byCustomer/{customerId}")]
[AgeFilter(18)] // Restrict access for under-18 customers
public async Task<IActionResult> GetProductsByCustomer(string customerId)
{
var products = await _service.GetAllProductsAsync();
return Ok(products);
}
}
Result
![Age_Filter]()
Approach 2. Restricting Calls with Middleware (Blacklist Customer)
Middleware runs before controllers and can be used for application-wide restrictions.
Example. Blacklisted Customers
Suppose some customers are blacklisted and must be blocked from calling any API.
Blacklist Middleware
using Microsoft.AspNetCore.Http;
namespace ProductFilteringExample.Api.Middleware
{
public class BlacklistMiddleware
{
private readonly RequestDelegate _next;
private static readonly HashSet<string> _blacklistedCustomers = new()
{
"bad-customer", "blocked-user"
};
public BlacklistMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
string? customerId = null;
// Try to resolve from route first
if (context.Request.RouteValues.TryGetValue("customerId", out var routeCust))
{
customerId = routeCust?.ToString();
}
else if (context.Request.Headers.TryGetValue("CustomerId", out var headerCust))
{
customerId = headerCust.ToString();
}
if (!string.IsNullOrEmpty(customerId) && _blacklistedCustomers.Contains(customerId))
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsJsonAsync(new
{
Status = 403,
Message = $"Access denied: Customer {customerId} is blacklisted."
});
return;
}
await _next(context);
}
}
}
Register Middleware
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<BlacklistMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Result
![BlackList_Filter]()
Attribute Filter vs Middleware
| Feature | Attribute Filter (Action Filter) | Middleware |
|---|
| Scope | Specific APIs only | Global (all APIs) |
| Use Case | Business rules for certain endpoints | Cross-cutting concerns (logging, security, blacklist) |
| Execution Point | Just before/after controller action executes | Before controller routing |
| Granularity | Fine-grained (per-action/per-controller) | Broad (application-wide) |
| Configuration | Applied via attributes on actions/controllers | Registered once in pipeline |
Conclusion
ASP.NET Core makes it simple to filter and restrict API calls with Action Filters and Middleware :
Use Action Filters for fine-grained, per-endpoint rules (e.g., only customers aged 18+ can access a resource).
Use Middleware for global restrictions (e.g., blocking blacklisted customers across the entire application).
Combining both approaches allows you to build robust, secure, and flexible APIs that handle validation and restrictions cleanly.