Inject service into Action Filter

52,936

Solution 1

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

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

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

Example

private ILoginService _loginService;

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

Hope it helps.

Solution 5

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

Share:
52,936
hyde
Author by

hyde

Dust

Updated on July 05, 2022

Comments

  • hyde
    hyde almost 2 years

    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>();
    
  • hyde
    hyde about 8 years
    Yup, this worked. I was unfamiliar with the ServiceFilter attribute before. That was the piece missing in my code. Thank you.
  • Tseng
    Tseng about 8 years
    You shouldn't do authorization policy checks this way, it's not the way in was intended to. You are supposed to use i.e. Cookie Authorization or some other authorization type (jwt for example) and then use AuthorizeAttribute with policies you set up on application startup. Check my comment above
  • Stefan Hendriks
    Stefan Hendriks about 8 years
    Interestingly a global filter does not seem to a allow for DI, see: github.com/damienbod/AspNet5Filters/blob/… - the author of the blogs actually hard-wires dependencies himself there.
  • Stefan Hendriks
    Stefan Hendriks about 8 years
    Found a blog that actually covers Global filters + dependency injection: weblogs.asp.net/ricardoperes/…
  • dave.8bit
    dave.8bit almost 8 years
    Thanks for that! This was not an obvious solution I'd have found easily on my own.
  • Andrei
    Andrei almost 8 years
    @StephenM.Redd make sure it is not Reusable. Otherwise it will be using disposed context.
  • Jeremy Holovacs
    Jeremy Holovacs over 7 years
    How does this work if you need to specify properties on the attribute?
  • SventoryMang
    SventoryMang almost 7 years
    I can't use GetService<IMyService> . It tells me to use Microsoft.Extensions.DependencyInjection.ServerProviderServi‌​ceExtensions.GetServ‌​ice, but I can't seem to find that available.
  • BrokeMyLegBiking
    BrokeMyLegBiking over 6 years
    This looks like the best solution for me with this small adjustment: var service = context.HttpContext.RequestServices.GetService(typeof(IMySer‌​vice)) as IMyService;
  • Anjani
    Anjani about 6 years
    This may work but it is using Service Locator pattern which are sometimes considered anti pattern
  • Jerameel Resco
    Jerameel Resco over 3 years
    Simple sample but straight forward. The right one that I was looking for. By using (T)context.HttpContext.RequestServices.GetService(typeof(T)) it resolves the dependency. Good job.