18

I searched a long time for a solution for my problem. I have a custom AuthorizeAttribute that needs a Dependency to a "Service" that has access to a DbContext. Sadly the Dependency Injection did not work in the custom AuthorizeAttribute and was always null.

I came up with an (for me) acceptable solution. Now I want to know if my solution can cause unforeseen behaviour?

Global.asax.cs

 CustomAuthorizeAttribute.AuthorizeServiceFactory = () => unityContainer.Resolve<AuthorizeService>();

CustomAuthorizeAttribute.cs

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class CustomAuthorizeAttribute : AuthorizeAttribute
    {
        public static Func<AuthorizeService> AuthorizeServiceFactory { get; set; }

        public Privilege Privilege { get; set; }

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            bool authorized = base.AuthorizeCore(httpContext);
            if (!authorized)
            {
                return false;
            }

            return AuthorizeServiceFactory().AuthorizeCore(httpContext, Privilege);
        }
    }

AuthorizeService.cs

public class AuthorizeService
{
    [Dependency]
    public UserService UserService { private get; set; }

    public bool AuthorizeCore(HttpContextBase httpContext, Privilege privilege)
    {
        ApplicationUser user = UserService.FindByName(httpContext.User.Identity.Name);
        return UserService.UserHasPrivilege(user.Id, privilege.ToString());
    }
}

Is this an acceptable solution? Will I run into nasty problems in the future or is there maybe a better way to use Dependency Injection in a custom AuthorizeAttribute?

Shamshiel
  • 2,051
  • 3
  • 31
  • 50

3 Answers3

28

I have a custom AuthorizeAttribute that needs a Dependency to a "Service" that has access to a DbContext. Sadly the Dependency Injection did not work in the custom AuthorizeAttribute and was always null.

An implementation of IControllerFactory in the System.Web.Mvc namespace creates instances your Controllers for web requests. The Controller Factory uses System.Web.Mvc.DependencyResolver to resolve dependencies in each controller.

However, ActionFilters/Attributes in the MVC pipeline are not created from the Controller Factory so dependencies are not resolved using System.Web.Mvc.DependencyResolver. This is why your dependency was always null.

Now, System.Web.Mvc.DependencyResolver is public and static so you can access it yourself.

    public class AuthorizeService : AuthorizeAttribute
    {
        private UserService UserService
        {
            get
            {
                return DependencyResolver.Current.GetService<UserService>();
            }
        }

        public bool AuthorizeCore(HttpContextBase httpContext, Privilege privilege)
        {
            ApplicationUser user = UserService.FindByName(httpContext.User.Identity.Name);
            return UserService.UserHasPrivilege(user.Id, privilege.ToString());
        }
    }

Assuming your UserServicehas a dependency scope of WebRequest, i.e. its lifetime is One Per web request and tied to the lifetime of HttpContext of a web request this will not construct a new UserService if one has been resolved previously or after if this is the first time UserService has been resolved for the given web request.

Anish Patel
  • 4,332
  • 1
  • 30
  • 45
  • I agree with this solution – Sajithd Jun 06 '19 at 10:54
  • I had the same issue and above answer was helpful. But since I'm using `Autofac`, they provide a way to resolve dependency in action filter through provider https://autofaccn.readthedocs.io/en/latest/integration/mvc.html?highlight=filter#enable-property-injection-for-action-filters" but the problem I was running into when using the above provider, my `UserService` has a dependency on `DbContext`, it throws an error `The operation cannot be completed because the DbContext has been disposed error r' only when I hit browser refresh button before letting the previous request to finish – Ammar Khan Jul 23 '20 at 16:58
17

In ASP.NET Core you can request services easily as below:

public class CustomAuthAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public async void OnAuthorization(AuthorizationFilterContext context)
    {
        // var user = context.HttpContext.User;

        // if (!user.Identity.IsAuthenticated)
        // {
        //     context.Result = new UnauthorizedResult();
        //     return;
        // }

        var userService = context.HttpContext.RequestServices.GetService(typeof(UserService)) as UserService;
    }
}
Majid
  • 3,128
  • 1
  • 26
  • 31
  • 1
    The question is not asp net core. The differences in architecture are too deep. – Ernesto Jul 03 '20 at 22:11
  • 11
    That's why I mentioned "ASP.NET Core". I added this answer here because I guessed many people like me would end up here for similar problem in "ASP.NET Core" :-) – Majid Jul 04 '20 at 18:57
  • Thanks Majid. I'm wondering is there some downside to getting services this way? If you could shed some light on what's happening here I'd appreciate it. I don't feel confident using it without understanding even though the result is what I want it to be. – Lukas Sep 03 '20 at 19:18
  • @Lukas whats happening here is that `RequestServices` is a property of type `IServiceProvider` which is automagically configured and set to the `IServiceProvider` that was constructed from the `IServiceCollection` defined and registered in your DI set up. (Startup.cs for ASP.NET Core). – Kyle L. Jan 06 '21 at 18:22
5

You can also try this:

ASP.NET Web API and dependencies in request scope

public override void OnAuthorization(HttpActionContext filterContext)
{
    var requestScope = filterContext.Request.GetDependencyScope();
    _userService = requestScope.GetService(typeof(IUserService)) as IUserService;
}

//
// Summary:
//     Retrieves the System.Web.Http.Dependencies.IDependencyScope for the given request
//     or null if not available.
//
// Parameters:
//   request:
//     The HTTP request.
//
// Returns:
//     The System.Web.Http.Dependencies.IDependencyScope for the given request or null
//     if not available.
public static IDependencyScope GetDependencyScope(this HttpRequestMessage request);
Dragos Durlut
  • 8,018
  • 10
  • 47
  • 62