Unity将依赖关系注入到具有参数的MVCfilter类中

我正在使用Unity.MVC4dependency injection来访问我的服务。 一切工作,因为它应该注入到我的控制器构造函数时,但我想现在要做的是在我的filter类中使用属性注入 ,所以我可以从内部访问我的数据库。

在我开始这个问题之前,我search了一遍,尝试了不同的例子,但我找不到解决scheme,为我工作。

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

下面的代码,我要告诉你是我想在我的行动中使用的filter属性。 我想传入一个string数组types的参数,所以我可以validation是否允许当前用户访问该操作。

在我的应用程序中,有两种types的用户,帐户所有者和访客。 所有操作都完全开放给帐户所有者,但对于客人而言,每个操作都有所不同。 举个例子,一个动作可能要求你至less有三个权限之一(读,写和编辑)。

过滤:

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

如果我要使用这个filter,它会看起来像..

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

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

然后我尝试实施John Allers解决scheme,可以在这里find。 它看起来很有希望,但它给了我这个错误:

在Microsoft.Practices.Unity.dll中发生types为“Microsoft.Practices.Unity.ResolutionFailedException”的exception,但未在用户代码中处理

附加信息:parsing依赖失败,type =“Fildela.ClaimsAuthorizeAccountAccess”,name =“(none)”。

exception发生时:解决时。

exception是:InvalidOperationException – types为Fildela.ClaimsAuthorizeAccountAccess的属性_accountRepository不可设置。


在例外的时候,货柜是:

解决Fildela.ClaimsAuthorizeAccountAccess,(无)

任何关于如何解决这个坏男孩的build议?

谢谢!

首先安装官方软件包Unity.Mvc而不是Unity.MVC4 。 这个包会自动安装并注册我们需要的属性dependency injection的UnityFilterAttributeFilterProvider 。 你可以通过查看App_Start > UnityMvcActivator的Start方法来检查你的UnityconfigurationApp_Start UnityMvcActivator 。 你必须看到以下两行:

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

现在,您可以将[Dependency]属性添加到filter的公共属性中。

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

根据后被动属性 ,DI友好的解决scheme是将AuthorizeAttribute分成两部分:

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

出于我们的目的,我们只是inheritance了AuthorizeAttribute来利用其内置的一些function。

请注意,如果采用这种方法,那么使用属性注入来处理数据库依赖关系并不合理。 无论如何,构造器注入总是一个更好的select。

ClaimsIdentityAuthorizeAttribute

首先,我们有没有行为来标记我们的控制器和行动的属性。 我们添加一点智能来将权限parsing到一个数组中,这样就不必在每次授权检查时完成。

 [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

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

我们添加一个默认为true的WhiteListMode ,因为这是configuration安全性的推荐方式(控制器和操作需要login,除非它们被赋予AllowAnonymousAttribute )。 幸运的是,这个框架内置到了AuthorizeAttribute所以我们只是把它作为一个标志来检查是否全局检查。

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

  1. 确定动作是否被授权的testing。
  2. 用户未被授权时采取的操作。

所以这些是我们join到我们服务中的东西。 如果需要,您可以将其重构为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" } }); } } 

用法

在全球注册您的filter,并注入它的依赖与您的容器。

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

注意:如果您需要任何筛选器的依赖项的生命周期比singleton短,您将需要使用GlobalFilterProvider作为此答案 。

启动

 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 // eg container.RegisterType<ITestService, TestService>(); RegisterTypes(container); return container; } public static void RegisterTypes(IUnityContainer container) { } } 

然后在你的控制器中,为了黑名单的安全,你需要修饰每一个动作(或控制器)来locking它。

 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 ,其权限或多或less具有全局或控制器级别的限制权限。

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

您不能将依赖项作为构造函数参数注入到操作filter,因为它们是作为C#中的属性实现的。 您需要使用DependencyResolver.Current解决它们。 这是一种服务定位器,它不是很酷,但你真的没有select。 ASP.NET MVC不使用DI容器来创build操作filter实例。

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