Search code examples
asp.net-mvcasp.net-mvc-4dependency-injectionunity-containeraction-filter

Unity Inject dependencies into MVC filter class with parameters


I'm using Unity.MVC4 dependency injection for accessing my services. Everything works as it should when injecting into my Controller constructor, but what I would like to do now is to use property injection in my filter class so I can access my database from the inside.

Before I started this question I Googled around and tried different examples but I couldn't find a solution that worked for me..

Bootstrapper.cs

public static class Bootstrapper
{
    public static IUnityContainer Initialise()
    {
        var container = BuildUnityContainer();

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));

        return container;
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();
        container.RegisterType<IAccountRepository, AccountRepository>();
        container.RegisterType<IAdministrationRepository, AdministrationRepository>();
        container.RegisterType<IUploadDirectlyRepository, UploadDirectlyRepository>();
        container.RegisterType<IUserRepository, UserRepository>();
        container.RegisterType<INewsRepository, NewsRepository>();
        container.RegisterType<IContactRepository, ContactRepository>();

        // register all your components with the container here
        // it is NOT necessary to register your controllers

        // e.g. container.RegisterType<ITestService, TestService>();    
        RegisterTypes(container);

        return container;
    }

    public static void RegisterTypes(IUnityContainer container)
    {

    }
}

Application_Start

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            Bootstrapper.Initialise();
        }
    }

Working example

public class UserController : Controller
{
    private readonly IUserRepository _userRepository;

    public UserController(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public ActionResult GetUser(int userID)
    {
        var user = _userRepository.GetUser(userID)

        return View(user);
    }
}

The following code that I'm about to show you is for the filter attribute that I would like to use on my actions. I want to pass in a parameter of type string array so I can validate if the current user is allowed to access the action.

In my application there are two types of users, Account owner and Guest. All actions are fully open for account owners, but for guests it varies from action to action. For an example, an action can require you to have atleast one of three permissions, (read, write and edit).

Filter:

public class ClaimsAuthorizeAccountAccess : AuthorizeAttribute
{
    private IAccountRepository _accountRepository { get; set; }
    private String[] _permissions { get; set; }

    public ClaimsAuthorizeAccountAccess(IAccountRepository accountRepository, params String[] permissions)
    {
        _permissions = permissions;
        _accountRepository = accountRepository;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (HttpContext.Current.User.IsInRole("Account Owner"))
        {
            base.OnAuthorization(filterContext);
        }
        else
        {
            ClaimsIdentity claimsIdentity = (ClaimsIdentity)HttpContext.Current.User.Identity;
            List<AccountLinkPermissionDTO> accountLinkPermissions = new List<AccountLinkPermissionDTO>();

            int accountOwnerID = 0;
            Int32.TryParse(claimsIdentity.Claims.Where(c => c.Type == "AccountOwnerID").Select(c => c.Value).SingleOrDefault(), out accountOwnerID);
            int guestID = 0;
            Int32.TryParse(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.Sid).Select(c => c.Value).SingleOrDefault(), out guestID);

            //NULL
            accountLinkPermissions = _accountRepository.GetAccountLinkPermissions(accountOwnerID, guestID);

            if (accountLinkPermissions != null)
            {
                List<string> accountLinkPermissionsToString = accountLinkPermissions.Select(m => m.Permission.Name).ToList();
                int hits = accountLinkPermissionsToString.Where(m => _permissions.Contains(m)).Count();

                if (hits > 0)
                {
                    base.OnAuthorization(filterContext);
                }
            }
            else
            {
                //Guest doesnt have right permissions

                filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary {
                        { "action", "AccessDenied" },
                        { "controller", "Account" }});
            }
        }
    }
}

If I were to use this filter it would look something like..

[ClaimsAuthorizeAccountAccess("File read", "File write, File edit")]
public ActionResult Files()
{
    return View();
}

However this does not work because the filter expects two parameters, (IRepository and string[]). It's also not possible to use constructor injection here, obviously.

I then tried implementing John Allers solution that can be found here. It looked promising but it gave me this error:

An exception of type 'Microsoft.Practices.Unity.ResolutionFailedException' occurred in Microsoft.Practices.Unity.dll but was not handled in user code

Additional information: Resolution of the dependency failed, type = "Fildela.ClaimsAuthorizeAccountAccess", name = "(none)".

Exception occurred while: while resolving.

Exception is: InvalidOperationException - The property _accountRepository on type Fildela.ClaimsAuthorizeAccountAccess is not settable.


At the time of the exception, the container was:

Resolving Fildela.ClaimsAuthorizeAccountAccess,(none)

Any suggestion on how to solve this bad boy?

Thanks!


Solution

  • First install official package, Unity.Mvc instead of Unity.MVC4. This package automatically installs and register UnityFilterAttributeFilterProvider which we need it for attribute's dependency injection. You could check if your Unity configured well by looking App_Start > UnityMvcActivator's Start method. You must see following two line:

    public static void Start()
    {
        // other codes
    
        FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
        FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));
    }
    

    Now you could add [Dependency] attribute to filter's public properties.

    public class ClaimsAuthorizeAccountAccess : AuthorizeAttribute
    {
        [Dependency]
        public IAccountRepository AccountRepository { get; set; }
        private String[] _permissions { get; set; }
    
        public ClaimsAuthorizeAccountAccess(params String[] permissions)
        {
            _permissions = permissions;
        }
    }