Inject service into Action Filter

C#asp.net CoreDependency Injection.Net Core

C# Problem Overview


I am trying to inject a service into my action filter but I am not getting the required service injected in the constructor. Here is what I have:

public class EnsureUserLoggedIn : ActionFilterAttribute
{
    private readonly ISessionService _sessionService;

    public EnsureUserLoggedIn()
    {
        // I was unable able to remove the default ctor 
        // because of compilation error while using the 
        // attribute in my controller
    }

    public EnsureUserLoggedIn(ISessionService sessionService)
    {
        _sessionService = sessionService;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Problem: _sessionService is null here
        if (_sessionService.LoggedInUser == null)
        {
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            context.Result = new JsonResult("Unauthorized");
        }
    }
}

And I am decorating my controller like so:

[Route("api/issues"), EnsureUserLoggedIn]
public class IssueController : Controller
{
}

Startup.cs

services.AddScoped<ISessionService, SessionService>();

C# Solutions


Solution 1 - C#

Using these articles as reference:

ASP.NET Core Action Filters

Action filters, service filters and type filters in ASP.NET 5 and MVC 6

Using the filter as a ServiceFilter

Because the filter will be used as a ServiceType, it needs to be registered with the framework IoC. If the action filters were used directly, this would not be required.

Startup.cs

public void ConfigureServices(IServiceCollection services) {
    services.AddMvc();

    services.AddScoped<ISessionService, SessionService>();
    services.AddScoped<EnsureUserLoggedIn>();
    
    ...
}

Custom filters are added to the MVC controller method and the controller class using the ServiceFilter attribute like so:

[ServiceFilter(typeof(EnsureUserLoggedIn))]
[Route("api/issues")]
public class IssueController : Controller {
    // GET: api/issues
    [HttpGet]
    [ServiceFilter(typeof(EnsureUserLoggedIn))]
    public IEnumerable<string> Get(){...}
}

There were other examples of

  • Using the filter as a global filter

  • Using the filter with base controllers

  • Using the filter with an order

Take a look, give them a try and see if that resolves your issue.

Hope this helps.

Solution 2 - C#

Global filters

You need to implement IFilterFactory:

public class AuthorizationFilterFactory : IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        // manually find and inject necessary dependencies.
        var context = (IMyContext)serviceProvider.GetService(typeof(IMyContext));
        return new AuthorizationFilter(context);
    }
}

In Startup class instead of registering an actual filter you register your filter factory:

services.AddMvc(options =>
{
    options.Filters.Add(new AuthorizationFilterFactory());
});

Solution 3 - C#

One more way for resolving this problem. You can get your service via Context as in the following code:

public override void OnActionExecuting(ActionExecutingContext context)
{
    _sessionService = context.HttpContext.RequestServices.GetService<ISessionService>();
    if (_sessionService.LoggedInUser == null)
    {
        context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        context.Result = new JsonResult("Unauthorized");
    }
}

Please note that you have to register this service in Startup.cs

services.AddTransient<ISessionService, SessionService>();

Solution 4 - C#

Example

private ILoginService _loginService;
 
public override void OnActionExecuting(ActionExecutingContext context)
        {
            _loginService = (ILoginService)context.HttpContext.RequestServices.GetService(typeof(ILoginService));
        }

Hope it helps.

Solution 5 - C#

After reading this article ASP.NET Core - Real-World ASP.NET Core MVC Filters (Aug 2016) I implemented it like this:

In Starup.cs / ConfigureServices:

services.AddScoped<MyService>();

In MyFilterAttribute.cs:

public class MyFilterAttribute : TypeFilterAttribute
{        
    public MyFilterAttribute() : base(typeof (MyFilterAttributeImpl))
    {

    }

    private class MyFilterAttributeImpl : IActionFilter
    {
        private readonly MyService _sv;

        public MyFilterAttributeImpl(MyService sv)
        {
            _sv = sv;
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {                
			_sv.MyServiceMethod1();
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _sv.MyServiceMethod2();
        }
    }
}

In MyFooController.cs :

[MyFilter]
public IActionResult MyAction()
{
}

Edit: Passing arguments like [MyFilter("Something")] can be done using the Arguments property of the TypeFilterAttribute class: How do I add a parameter to an action filter in asp.net? (rboe's code also shows how to inject things (the same way))

Solution 6 - C#

While the question implicitly refers to "filters via attributes", it is still worth highlighting that adding filters "globally by type" supports DI out-of-the-box:

> [For global filters added by type] any constructor dependencies will be populated by dependency injection (DI). Adding a filter by type is equivalent to filters.Add(new TypeFilterAttribute(typeof(MyFilter))). > https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection

With regards to attribute-based filters:

> Filters that are implemented as attributes and added directly to controller classes or action methods cannot have constructor dependencies provided by dependency injection (DI). This is because attributes must have their constructor parameters supplied where they're applied. This is a limitation of how attributes work. > https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection

However, as mentioned in the previous answers to the OP, there are ways of indirection that can be used to achieve DI. For the sake of completeness, here are the links to the official docs:

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionhydeView Question on Stackoverflow
Solution 1 - C#NkosiView Answer on Stackoverflow
Solution 2 - C#AndreiView Answer on Stackoverflow
Solution 3 - C#Igor ValikovskyView Answer on Stackoverflow
Solution 4 - C#Bukunmi View Answer on Stackoverflow
Solution 5 - C#A.J.BauerView Answer on Stackoverflow
Solution 6 - C#Felix K.View Answer on Stackoverflow