How To Create Custom Action Filter In ASP.NET 6 & Above
Filters in ASP.NET Core allow code to run before or after specific stages in the request processing pipeline.
Inbuild Filters
- Authorization prevents access to resources a user isn’t authorized for.
- Response caching, short-circuiting the request pipeline to return a cached response.
Custom Filters
Custom filters can be created to handle cross-cutting concerns. Examples of cross-cutting concerns include error handling, caching, configuration, authorization, and logging. Filters avoid duplicating code. For example, an error-handling exception filter could consolidate error handling.
There are many filters in ASP.NET Core, but we will focus on Action Filters
- Run immediately before and after an action method is called.
- Can change the arguments passed into action.
- Can change the result returned from the action.
How Filters Works
Filters run within the ASP.NET Core action invocation pipeline, sometimes referred to as the filter pipeline. The filter pipeline runs after ASP.NET Core selects the action to execute:
We can implement Action Filters as synchronous and asynchronous through different interface definitions
We are going to build a SingleDeviceSessionCheck Filter for example
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace MyService.UserApi.Filters
{
public class SingleDeviceCheck : IActionFilter
{
private readonly IDeviceAccessRepository deviceAccessRepository;
private readonly ILogger<SingleDeviceCheck> logger;
private readonly IConfiguration configuration;
/* Constructor for DI */
public SingleDeviceCheck(
/* This Respository Containts logic for checking session in DB */
IDeviceAccessRepository deviceAccessRepository,
ILogger<SingleDeviceCheck> logger,
IConfiguration configuration)
{
this.deviceAccessRepository = deviceAccessRepository;
this.logger = logger;
this.configuration = configuration;
}
}
// our code before action executes
public void OnActionExecuting(ActionExecutingContext context){}
// our code after action executes
public void OnActionExecuted(ActionExecutedContext context){}
}
We need to implement our logic in these two methods, for my use case I required the onActionExecuting method.
public void OnActionExecuting(ActionExecutingContext context)
{
// Checking single device access ON or OFF (Feature Flag For Dev/Prod Test
if (this.configuration["SD_ON_OFF"].Equals("OFF")) { return; }
// our code before action executes
var deviceId = context.HttpContext.Request?.Headers["Device-Id"];
if (string.IsNullOrEmpty(deviceId.ToString()))
{
context.Result = new BadRequestObjectResult("Device-Id is empty");
return;
}
// if same device login then permission granted
// Read USER INFO From JWT Claims
var email = context.HttpContext.User.FindFirst("https://xyz.com/email")?.Value;
var deviceAccessData = deviceAccessRepository.GetActiveDeviceByEmailAsync(email).Result;
if (deviceAccessData == null)
{
context.Result = new UnauthorizedObjectResult(new { status = false, message = "Couldn't Find Your Device. Please Logout & Login Again." })
{
StatusCode = 403
};
return;
}
if (!(deviceAccessData.DeviceId == deviceId.ToString() && deviceAccessData?.IsActive == true))
{
logger.LogInformation("Device Failed To Pass {device} and {email}", deviceId, email);
context.Result = new UnauthorizedObjectResult(new { status = false, message = "You have already active in another device.\n Access Denied." })
{
StatusCode = 403
};
return;
}
else
{
logger.LogInformation("Device Pass {device} and {email}", deviceId, email);
}
}
Now we will register our Action Filter in Program. cs or Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("sqlConString")));
//Filters
services.AddScoped<SingleDeviceCheck>();
services.AddControllers();
}
Now we will use This Filter In Our Controller
//Dev Test Only
[HttpGet("device")]
//Filter Code Run in Request Pipeline
[ServiceFilter(typeof(SingleDeviceCheck))]
public IActionResult CheckDevieID() {
return Ok("Single Device ID Endpint Check");
}
Summary
- Action Filter helps us to reuse code.
- Run Some Common Logic In SPecific Controller or Methods In the Request Pipeline
- A middleware can run for all requests while filters will only run for requests that reach the EndpointMiddleware
- filters will only run on specified actions and controllers unless you register the filter globally in the startup. Since you have full access to the context you can also access the controller and action itself.
I always recommend using Action Filters because they give us reusability in our code and cleaner code in our actions as well.
Software Developer | Rajdeep Software Engineering & Consulting Services
Thank You, happy reading 📖 , 😊
Top comments (0)