Dependency Injection with Ninject and Filter attribute for asp.net mvc
Asked Answered
R

4

56

I'm writing a custom Authorization Filter for asp.net mvc 3. I need to inject a userservice into the class but I have no idea how to do this.

public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
    private IUserService userService;
    private string[] roles;

    public AuthorizeAttribute(params string[] roles)
    {
        this.roles = roles;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        throw new NotImplementedException();
    }
}

I'm using ninject for dependency injection. I do not want to use a Factory or service locator pattern.

My bindings look like this in the global.acsx:

    internal class SiteModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IUserService>().To<UserService>();
        }
    }
Rhododendron answered 31/5, 2011 at 20:51 Comment(2)
Are you sure you don't want to use factory? In that case you have to keep a reference to kernel in your Application class, and manually Get<IUserService>() in controller constructor. In Mvc 3 factory really makes sense as you are overriding the original one. There are ninject nugets for this also.Merideth
Jakubmal, could you make an answer using factories then?Rhododendron
L
82

See this answer: Custom Authorization MVC 3 and Ninject IoC

If you want to use constructor injection then you need to create an attribute and a filter.

/// Marker attribute
public class MyAuthorizeAttribute : FilterAttribute { }

/// Filter
public class MyAuthorizeFilter : IAuthorizationFilter
{
      private readonly IUserService _userService;
      public MyAuthorizeFilter(IUserService userService)
      {
          _userService = userService;
      }

      public void OnAuthorization(AuthorizationContext filterContext)
      {
          var validUser = _userService.CheckIsValid();

          if (!validUser)
          {
              filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "action", "AccessDenied" }, { "controller", "Error" } });
          }
      }
}

Binding:

this.BindFilter<MyAuthorizeFilter>(System.Web.Mvc.FilterScope.Controller, 0).WhenControllerHas<MyAuthorizeAttribute>();

Controller:

[MyAuthorizeAttribute]
public class YourController : Controller
{
    // ...
}
Lasley answered 31/5, 2011 at 22:9 Comment(11)
How would you setup the implementation to accomodate: [MyAuthorizeAttribute("Admin", "Contributer")]? Passing parameters.Rhododendron
github.com/ninject/ninject.web.mvc/wiki/Filter-configurations +1 For giving the answer that supports Constructor instead of Property InjectionMelvinamelvyn
@Lol coder - see Remo's link above for how to configure filters.Lasley
I still cant figure out how to setup the attribute constructor to accept both params and the service.Rhododendron
I read the config... still can't figure out how to get attribute constructor arguments to the filter's constructor arguments... I'm missing something. Similar question: #8305976Truce
@Truce #6205687Lasley
@B Z: Thanks, I did not find that Question when I search yesterday so I opened my own, and found the solution on my own as well... #8305976 - thanks for your help.Truce
Did they take out the BindFilter method on a recent version of Ninject? I can't seem to get it to recognize it and suggest a namespacce.Inerrant
@Inerrant make sure you have Ninject.Web.Mvc referencedLasley
I have a dumb question. Do I perform the binding in RegisterServices (in NinjectWebCommon.cs)?Ctn
@BZ: What's wrong in Wolfgang solution given below. Taking the instance from current session?Mickey
S
11

I would highly recommend B Z's answer. DO NOT use [Inject]!

I used an [Inject] like Darin Dimitrov said was possible and it actually caused threading issues under high load, high contention situations in conjunction with .InRequestScope.

B Z's way is also what is on the Wiki and I have seen many places where Remo Gloor (Ninject author) says this is the correct way to do it, e.g. https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations.

Downvote [Inject] answers in here because seriously you will get burned (probably in production if you don't load test properly beforehand!).

Stochmal answered 25/6, 2013 at 20:39 Comment(1)
I did and no one would see it because its hidden by default (too far down). Based on the fact I spent 3 days trying to figure out what went wrong with Darin's answer I figure it is VERY relevant and worthy of an official answer to avoid anyone using it by mistake and getting burnedStochmal
H
9

I found a simple solution for any occasion where construction is not handled by Ninject:

var session = (IMyUserService)DependencyResolver.Current.GetService(typeof (IMyUserService));

Actually this is exactly what I am using with my custom AuthorizeAttribute. Much easier than having to implement a separate FilterAttribute.

Headgear answered 19/11, 2014 at 7:31 Comment(0)
B
8

On way would be to use a property injection and decorate the property with the [Inject] attribute:

public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
    [Inject]
    public IUserService UserService { get; set; }

    private string[] roles;
  
    ...
}

Constructor injection doesn't work well with attributes as you will no longer be able to decorate controllers/actions with them. You could only use constructor injection with the filter binding syntax in Ninject:

public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
    private readonly IUserService userService;

    private string[] roles;

    public AuthorizeAttribute(IUserService userService, params string[] roles)
    {
        this.userService = userService;
        this.roles = roles;
    }
  
    ...
}

and then:

internal class SiteModule : Ninject.Modules.NinjectModule
{
    public override void Load()
    {
        Bind<IUserService>().To<UserService>();

        this.BindFilter<AuthorizeAttribute>(FilterScope.Controller, 0)
            .WhenControllerType<AdminController>();
    }
}

The BindFilter<> extension method is defined in the Ninject.Web.Mvc.FilterBindingSyntax namespace so make sure you have brought that into scope before calling it on a kernel.

Burleson answered 31/5, 2011 at 20:59 Comment(8)
What if I do not use constructor bindings and used the [Inject] attribute, will I be able to decorate the controllers/actions?Rhododendron
Is there additional code needed when I'm using he [Inject] property?Rhododendron
@Lol coder, no, you just need the Bind<IUserService>().To<UserService>(); part.Burleson
Any idea why UserService would still be null? I had this same problem with Providers too.Rhododendron
@Lol coder, no idea. Works fine for me. I've created a new ASP.NET MVC 3 application, installed the NInject.MVC3 NuGet package, declared a IUserService inteface and UserService implementation, declared a custom MyAuthorizeAttribute using property injection and used the generated App_Start/NinjectMVC3.cs to configure the kernel. Then I simply decorated my HomeController with the attribute and the injection worked fine.Burleson
Thank you for specifying the namespace!!! It's annoying to have to find the namespace that you need for extension methods!Banal
I would highly recommend B Z's answer. DO NOT use [Inject] it can create race conditions under high load!Stochmal
+1 for clarifying, "The BindFilter<> extension method is defined in the Ninject.Web.Mvc.FilterBindingSyntax namespace so make sure you have brought that into scope before calling it on a kernel." Saved me some time hunting.Unending

© 2022 - 2024 — McMap. All rights reserved.