DEV Community

Rajdeep Das
Rajdeep Das

Posted on • Originally published at rajdeep-das.Medium on

Filters in ASP.NET Core

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

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:


ASP.NET Filters

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){}
}

Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

Summary

  1. Action Filter helps us to reuse code.
  2. Run Some Common Logic In SPecific Controller or Methods In the Request Pipeline
  3. A middleware can run for all requests while filters will only run for requests that reach the EndpointMiddleware
  4. 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

Rajdeep Das | LinkedIn

Thank You, happy reading 📖 , 😊

Top comments (0)