Unity 使用参数将依赖项注入 MVC 过滤器 class

Unity Inject dependencies into MVC filter class with parameters

我正在使用 Unity.MVC4 依赖注入来访问我的服务。注入我的 Controller 构造函数时一切正常,但我现在想做的是在我的过滤器 class 中使用 属性 injection 这样我就可以从内部访问我的数据库。

在我开始这个问题之前,我用谷歌搜索并尝试了不同的例子,但我找不到适合我的解决方案..

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();
        }
    }

工作示例

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);
    }
}

我将要向您展示的以下代码用于我想在我的操作中使用的过滤器属性。 我想传入一个字符串数组类型的参数,这样我就可以验证是否允许当前用户访问该操作。

在我的应用程序中有两种类型的用户,帐户所有者和来宾。所有操作都对帐户所有者完全开放,但对于访客来说,它因操作而异。例如,一个操作可能要求您至少拥有三种权限之一(读取、写入和编辑)。

过滤器:

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" }});
            }
        }
    }
}

如果我使用这个过滤器,它会看起来像..

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

但是这不起作用,因为过滤器需要两个参数(IRepository 和 string[])。显然,这里也不可能使用构造函数注入。

然后我尝试实施 John Allers 可以找到的解决方案 here。它看起来很有希望,但它给了我这个错误:

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)

关于如何解决这个坏男孩有什么建议吗?

谢谢!

您不能将依赖项作为构造函数参数注入操作过滤器,因为它们在 C# 中作为属性实现。您需要使用 DependencyResolver.Current 解决它们。它是一种服务定位器,它并不酷,但你真的别无选择。 ASP.NET MVC 不使用 DI 容器来创建操作过滤器实例。

public ClaimsAuthorizeAccountAccess(params string[] permissions)
{
    _permissions = permissions;
    _accountRepository = DependencyResolver.Current.GetService<IAccountRepository>();
} 

首先安装官方包,Unity.Mvc而不是Unity.MVC4。这个包自动安装和注册 UnityFilterAttributeFilterProvider 我们需要它来进行属性的依赖注入。您可以通过查看 App_Start > UnityMvcActivatorStart 方法来检查您的 Unity 配置是否正确。您必须看到以下两行:

public static void Start()
{
    // other codes

    FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
    FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));
}

现在您可以将 [Dependency] 属性添加到过滤器的 public 属性。

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

    public ClaimsAuthorizeAccountAccess(params String[] permissions)
    {
        _permissions = permissions;
    }
}

根据 post Passive Attributes,DI 友好的解决方案是将 AuthorizeAttribute 分成两部分:

  1. 一个不包含用于标记控制器和操作方法的行为的属性。
  2. 实现 IAuthorizationFilter 并包含所需行为的 DI 友好 class。

为了我们的目的,我们只是继承 AuthorizeAttribute 以利用它的一些内置功能。

请注意,如果您采用这种方法,则对数据库依赖项使用 属性 注入没有多大意义。无论如何,构造函数注入始终是更好的选择。

ClaimsIdentityAuthorizeAttribute

首先,我们的属性没有行为来标记我们的控制器和操作。我们添加了一点智能来将权限解析到一个数组中,这样就不必在每次授权检查时都执行此操作。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class ClaimsAuthorizeAccountAccess : Attribute
{
    private readonly string[] _permissionsSplit;

    public ClaimsAuthorizeAccountAccess(string permissions)
    {
        _permissionsSplit = SplitString(value);
    }

    internal string[] PermissionsSplit
    {
        get { return this._permissionsSplit; }
    }

    internal static string[] SplitString(string original)
    {
        if (string.IsNullOrEmpty(original))
        {
            return new string[0];
        }
        return (from piece in original.Split(new char[] { ',' })
                let trimmed = piece.Trim()
                where !string.IsNullOrEmpty(trimmed)
                select trimmed).ToArray<string>();
    }
}

ClaimsIdentityAuthorizationFilter

接下来,我们有我们的授权过滤器,它将充当全局过滤器。

我们添加了一个 WhiteListMode,默认情况下为真,因为这是配置安全性的推荐方式(控制器和操作需要登录,除非它们被赋予 AllowAnonymousAttribute)。幸运的是,AuthorizeAttribute 中内置了该框架,因此我们仅将其用作是否进行全局检查的标志。

我们还添加了一个扩展点,可以在其中注入我们的自定义授权服务。最有可能改变的 2 件事是:

  1. 判断动作是否被授权的测试。
  2. 当用户未被授权时采取的行动。

这些就是我们添加到我们服务中的东西。如果需要,您可以将其重构为 2 个独立的服务。

public class ClaimsIdentityAuthorizationFilter : AuthorizeAttribute
{
    private readonly IAuthorizationService _authorizationService;
    private string _permissions;
    private string[] _permissionsSplit = new string[0];
    private bool _whiteListMode = true;

    public ClaimsIdentityAuthorizationFilter(IAuthorizationService authorizationService)
    {
        if (authorizationService == null)
            throw new ArgumentNullException("authorizationService");

        this._authorizationService = authorizationService;
    }

    // Hide users and roles, since we aren't using them.
    [Obsolete("Not applicable in this class.")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    new public string Roles { get; set; }

    [Obsolete("Not applicable in this class.")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    new public string Users { get; set; }

    public string Permissions
    {
        get
        {
            return (this._permissions ?? string.Empty);
        }
        set
        {
            this._permissions = value;
            this._permissionsSplit = SplitString(value);
        }
    }

    public bool WhiteListMode
    {
        get { return this._whiteListMode; }
        set { this._whiteListMode = value; }
    }

    internal static string[] SplitString(string original)
    {
        if (string.IsNullOrEmpty(original))
        {
            return new string[0];
        }
        return (from piece in original.Split(new char[] { ',' })
                let trimmed = piece.Trim()
                where !string.IsNullOrEmpty(trimmed)
                select trimmed).ToArray<string>();
    }

    private ClaimsAuthorizeAccountAccess GetClaimsAuthorizeAccountAccess(ActionDescriptor actionDescriptor)
    {
        ClaimsAuthorizeAccountAccess result = null;

        // Check if the attribute exists on the action method
        result = (ClaimsAuthorizeAccountAccess)actionDescriptor
            .GetCustomAttributes(attributeType: typeof(ClaimsAuthorizeAccountAccess), inherit: true)
            .SingleOrDefault();

        if (result != null)
        {
            return result;
        }

        // Check if the attribute exists on the controller
        result = (ClaimsAuthorizeAccountAccess)actionDescriptor
            .ControllerDescriptor
            .GetCustomAttributes(attributeType: typeof(ClaimsAuthorizeAccountAccess), inherit: true)
            .SingleOrDefault();

        return result;
    }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
        if (actionDescriptor != null)
        {
            var authorizeAttribute = this.GetClaimsAuthorizeAccountAccess(actionDescriptor);

            // If the authorization attribute exists
            if (authorizeAttribute != null)
            {
                // Run the authorization based on the attribute
                return this._authorizationService.HasPermission(
                    httpContext,
                    authorizeAttribute.PermissionsSplit);
            }
            else if (this.WhiteListMode)
            {
                // Run the global authorization
                return this._authorizationService.HasPermission(
                    httpContext,
                    this._permissionsSplit);
            }
        }

        return true;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        // Pass the current action descriptor to the AuthorizeCore
        // method on the same thread by using HttpContext.Items
        filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
        base.OnAuthorization(filterContext);
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        filterContext.Result = this._authorizationService.GetUnauthorizedHandler(filterContext);
    }
}

IAuthorizationService

public interface IAuthorizationService
{
    bool HasPermission(HttpContextBase httpContext, string[] permissions);
    ActionResult GetUnauthorizedHandler(AuthorizationContext filterContext);
}

ClaimsIdentityAuthorizationService

所以现在我们进行高级定制来支持索赔。我们将其分开,以便在未来业务逻辑发生变化时可以使用接缝注入另一个实例。

public class ClaimsIdentityAuthorizationService : IAuthorizationService
{
    private IAccountRepository _accountRepository { get; set; }

    public ClaimsIdentityAuthorizationService(IAccountRepository accountRepository)
    {
        if (accountRepository == null)
            throw new ArgumentNullException("accountRepository");

        _accountRepository = accountRepository;
    }

    public bool HasPermission(HttpContextBase httpContext, string[] permissions)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException("httpContext");
        }
        IPrincipal user = httpContext.User;
        if (!user.Identity.IsAuthenticated)
        {
            return false;
        }
        if (!user.IsInRole("Account Owner"))
        {
            ClaimsIdentity claimsIdentity = (ClaimsIdentity)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)
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
        return true;
    }

    public ActionResult GetUnauthorizedHandler(AuthorizationContext filterContext)
    {
        //Guest doesnt have right permissions
        return new RedirectToRouteResult(
            new RouteValueDictionary {
                    { "action", "AccessDenied" },
                    { "controller", "Account" }
            });
    }
}

用法

全局注册您的过滤器并将其依赖项注入您的容器。

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters, IUnityContainer container)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(container.Resolve<IAuthorizationFilter>());
    }
}

NOTE: If you need any of the filter's dependencies to have a lifetime shorter than singleton, you will need to use a GlobalFilterProvider as in .

启动

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        var container = Bootstrapper.Initialise();

        AreaRegistration.RegisterAllAreas();

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

引导程序

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 the types for the authorization filter
        container.RegisterType<IAuthorizationFilter, ClaimsIdentityAuthorizationFilter>(
            // Not sure whether you want white list or black list
            // but here is where it is set.
            new InjectionProperty("WhiteListMode", true),
            // For white list security, you can also set the default
            // permissions that every action gets if it is not overridden.
            new InjectionProperty("Permissions", "read"));
        container.RegisterType<IAuthorizationService, ClaimsIdentityAuthorizationService>();

        // 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)
    {

    }
}

然后在您的控制器中,为了黑名单安全,您需要装饰每个操作(或控制器)以将其锁定。

public class HomeController : Controller
{

    // This is not secured at all
    public ActionResult Index()
    {
        return View();
    }

    [ClaimsAuthorizeAccountAccess("read")]
    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";

        return View();
    }

    [ClaimsAuthorizeAccountAccess("read,edit")]
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}

为了白名单安全,你只需要用AllowAnonymous修饰每个人都可以访问的操作,或者添加一个ClaimsIdentityAuthorizeAttribute,比全局或控制器级别的限制更多或更少的权限。

public class HomeController : Controller
{
    // This is not secured at all
    [AllowAnonymous]
    public ActionResult Index()
    {
        return View();
    }

    // This is secured by ClaimsAuthorizeAccountAccess (read permission)
    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";

        return View();
    }

    [ClaimsAuthorizeAccountAccess("read,edit")]
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}