简体   繁体   中英

Custom filter attributes inject dependency

I'musing ASP.NET Web API and I need to have authorization so I've created custom authorization attribute

public class CustomAuthorizationAttribute : AuthorizeAttribute

In order to inject dependency inside constructor I have following :

        public CustomAuthorizationAttribute(IAccountBL accountBl)
    {
        _accountBL = accountBl;
    }

In IAccountBL I have method which interacts with database checking if user is authorized to make request. Inside Member API controller I've register that attribute

    [CustomAuthorization]
public class MemberController : ApiController

But I get following error

Project.Account.AccountBL' does not contain a constructor that takes 0 arguments

And if I register it like

[CustomAuthorization(IAccountBL)]

在此处输入图片说明

Thank you

Action filters are just attributes. You do not have control over when those attributes are instantiated by the CLR. One possibility is to write a marker attribute:

public class CustomAuthorizationAttribute : Attribute { }

and then the actual action filter:

public class CustomAuthorizationFilter : ActionFilterAttribute
{
    private readonly IAccountBL accountBL;
    public CustomAuthorizationFilter(IAccountBL accountBL)
    {
        this.accountBL = accountBL;
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<CustomAuthorizationAttribute>().Any() || 
            actionContext.ActionDescriptor.GetCustomAttributes<CustomAuthorizationAttribute>().Any())
        {
            // here you know that the controller or action is decorated 
            // with the marker attribute so that you could put your code
        }
    }
}

and finally register it as a global action filter:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ...

        IAccountBL accountBL = ...
        config.Filters.Add(new CustomAuthorizationFilter(accountBL));
    }
}

and finally you could use the marker attribute:

[CustomAuthorization]
public class MemberController : ApiController
{
    ...
}

You can get dependency in your filter by using extension method GetDependencyScope for class HttpRequestMessage . It's not a canonical way for dependency injection, but can be used as workaround. A basic example may look like this:

    public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        var dependencyScope = context.Request.GetDependencyScope();
        var dependency = dependencyScope.GetService(typeof (MyDependencyType));
        //use your dependency here
    }

This method may be used with constructor injection to simplify unit testing:

public class MyAuthenticationFilter : Attribute, IAuthenticationFilter
{
    private Func<HttpRequestMessage, MyDependencyType> _dependencyFactory;

    public MyAuthenticationFilter() :
        this(request => (MyDependencyType)request.GetDependencyScope().GetService(typeof(MyDependencyType)))
    {
    }

    public MyAuthenticationFilter(Func<HttpRequestMessage, MyDependencyType> dependencyFactory)
    {
        _dependencyFactory = dependencyFactory;
    }

    public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        var dependencyScope = context.Request.GetDependencyScope();
        var dependency = dependencyFactory.Invoke(context.Request);
        //use your dependency here
    }

    public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }

    public bool AllowMultiple { get; private set; }
}

If anyone finds similar issue here's how I manage to solve it.

My custom filter inherits IAutofacAuthorizationFilter . Besides this one you can also inherit IAutofacExceptionFilter and IAutofacActionFilter . And inside my DI container I've register this filter for each controller I want to use like this

        builder.Register(c => new CustomAuthorizationAttribute(c.Resolve<IAccountBL>()))
               .AsWebApiAuthorizationFilterFor<MemberController>()
               .InstancePerApiRequest();

If you registered your service on the application using any container, it's very easy to get the instance of your service from anywhere in the scope. Just follow the below code to get your service.

var myService = DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService;

Please make sure you have included System.Web.Mvc in the file.

Happy coding!!!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM