90

AuthenticationRequiredAttribute Class

public class AuthenticationRequiredAttribute : ActionFilterAttribute
{
    ILoginTokenKeyApi _loginTokenKeyApi;
    IMemoryCache _memoryCache;

    public AuthenticationRequiredAttribute(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;

        _loginTokenKeyApi = new LoginTokenKeyController(new UnitOfWork());
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var memory = _memoryCache.Get(Constants.KEYNAME_FOR_AUTHENTICATED_PAGES);

        string requestedPath = filterContext.HttpContext.Request.Path;

        string tokenKey = filterContext.HttpContext.Session.GetString("TokenKey")?.ToString();

        bool? isLoggedIn = _loginTokenKeyApi.IsLoggedInByTokenKey(tokenKey).Data;

        if (isLoggedIn == null ||
            !((bool)isLoggedIn) ||
            !Constants.AUTHENTICATED_PAGES_FOR_NORMAL_USERS.Contains(requestedPath))
        {
            filterContext.Result = new JsonResult(new { HttpStatusCode.Unauthorized });
        }
    }
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
    }
}

HomeController

public class HomeController : Controller
{
    IUserApi _userApi;
    ILoginTokenKeyApi _loginTokenKey;
    IMemoryCache _memoryCache;

    public HomeController(IUserApi userApi, ILoginTokenKeyApi loginTokenKey, IMemoryCache memoryCache)
    {
        _loginTokenKey = loginTokenKey;
        _userApi = userApi;

        _memoryCache = memoryCache;
    }

    [AuthenticationRequired] // There is AN ERROR !!
    public IActionResult Example()
    {
        return View();
    }
}

ERROR :

Error CS7036 There is no argument given that corresponds to the required formal parameter 'memoryCache' of 'AuthenticationRequiredAttribute.AuthenticationRequiredAttribute(IMemoryCache)' Project.Ground.WebUI

My problem is actually : I cant use dependency injection in attribute classes.

I want to use that attribute without any parameter. Is there any solution to solve it? I use dependency injection but it cant be used for attributes. How I can use it?

0

3 Answers 3

122

As per the documentation, you have a few options here:

If your filters have dependencies that you need to access from DI, there are several supported approaches. You can apply your filter to a class or action method using one of the following:

ServiceFilter or TypeFilter attributes

If you just want to get this working quickly, you can just use one of the first two options to apply your filter to a controller or a controller action. When doing this, your filter does not need to be an attribute itself:

[TypeFilter(typeof(ExampleActionFilter))]
public IActionResult Example()
    => View();

The ExampleActionFilter can then just implement e.g. IAsyncActionFilter and you can directly depend on things using constructor injection:

public class ExampleActionFilter : IAsyncActionFilter
{
    private readonly IMemoryCache _memoryCache;
    public ExampleActionFilter(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    { … }
}

You can also use the [ServiceFilter] attribute instead to get the same effect but then you will also need to register your ExampleActionFilter with the dependency injection container in your Startup.

Filter factory

If you need more flexibility, you can implement your own filter factory. This allows you to write the factory code to create the actual filter instance yourself. A possible implementation for the above ExampleActionFilter could look like this:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ExampleActionFilterAttribute : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return serviceProvider.GetService<ExampleActionFilter>();
    }
}

You can then use that [ExampleActionFilter] attribute to make the MVC framework create an instance of the ExampleActionFilter for you, using the DI container.

Note that this implementation is basically the same thing that ServiceFilterAttribute does. It’s just that implementing it yourself avoids having to use the ServiceFilterAttribute directly and allows you to have your own attribute.

Using service locator

Finally, there is another quick option that allows you to avoid constructor injection completely. This uses the service locator pattern to resolve services dynamically when your filter actually runs. So instead of injecting the dependency and using it directly, you retrieve it explicitly from the context:

public class ExampleActionFilter : ActionFilterAttribute
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var memoryCache = context.HttpContext.RequestServices.GetService<IMemoryCache>();

        // …
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

The generic GetService< T> method is an extension method. This means you need to have a : using Microsoft.Extensions.DependencyInjection;
85

Instead of resolving at construction, ActionExecutingContext.HttpContext.RequestServices should give you a reference to the request's service container at the time of the request.

So:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var svc = filterContext.HttpContext.RequestServices;
    var memCache = svc.GetService<IMemoryCache>();
    //..etc

6 Comments

using Microsoft.Extensions.DependencyInjection;
using Memcache in action filter and trying to get values it always returns null
The purpose of dependency injection is that the dependencies should be injected from the outside, to e.g. make it easier to test. This is the other way around, which is an anti-pattern or at least an abuse of the dependency injection implementation. It should be your last resort when nothing else works.
@ChristianDavén isn't the benefit of DI that you use interfaces and only need to declare the concrete class in one place? By using HttpContext.RequestServices, you're still only referring to the interface just as you would if you had an interface type as your ctor param. In either case, if you want to change the concrete class, you only change it in once place.
@David Klempfner the neat thing about dependency injection is that your code doesn't have to know how or where its dependencies are created. It just gets an object in the constructor -- it could come from a unit testing construct or wherever. The suggested solution actually adds a dependency from the code to the dependency injection mechanism itself, which of course works, but could make testing harder. And maybe it will cause problems if you want to switch to Autofac, Ninject, Castle Windsor or any other dependency injector?
|
-7

For .Net Core 5, Below syntax worked for me.

 IAppUserService _appUserService = (IAppUserService)context.HttpContext.RequestServices.GetService(typeof(IAppUserService));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.