Unity 使用參數將依賴項注入 MVC 過濾器類
我正在使用Unity.MVC4依賴注入來訪問我的服務。當注入到我的 Controller 建構子中時,一切都正常工作,但我現在想做的是在我的過濾器類中使用屬性注入,這樣我就可以從內部訪問我的數據庫。
在我開始這個問題之前,我用Google搜尋並嘗試了不同的例子,但我找不到適合我的解決方案..
引導程序.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) { } }應用程序_開始
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解決方案。它看起來很有希望,但它給了我這個錯誤:
Microsoft.Practices.Unity.dll 中出現“Microsoft.Practices.Unity.ResolutionFailedException”類型的異常,但未在使用者程式碼中處理
附加資訊:依賴項解析失敗,type = “Fildela.ClaimsAuthorizeAccountAccess”,name = “(none)"。
異常發生時:解決時。
例外情況是:InvalidOperationException - Fildela.ClaimsAuthorizeAccountAccess 類型的屬性 _accountRepository 不可設置。
在異常發生時,容器是:
解決 Fildela.ClaimsAuthorizeAccountAccess,(無)
關於如何解決這個壞男孩的任何建議?
謝謝!
根據文章Passive Attributes,對 DI 友好的解決方案是將其
AuthorizeAttribute分為兩部分:
- 一個不包含任何行為來標記您的控制器和操作方法的屬性。
- 實現IAuthorizationFilter並包含所需行為的 DI 友好類。
出於我們的目的,我們只是繼承
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預設為 true 的 a,因為這是配置安全性的推薦方式(控制器和操作需要登錄,除非它們被賦予AllowAnonymousAttribute)。幸運的是,該框架是內置的,AuthorizeAttribute因此我們只需將其用作是否全域檢查的標誌。我們還添加了一個擴展點,可以在其中註入我們的自定義授權服務。最有可能改變的兩件事是:
- 用於確定操作是否被授權的測試。
- 當使用者未獲得授權時要執行的操作。
所以這些是我們添加到服務中的東西。如果需要,您可以將其重構為 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); } }授權服務
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>()); } }**注意:**如果您需要過濾器的任何依賴項的生命週期短於單例,則需要使用 a
GlobalFilterProvideras in this answer。啟動
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或添加限制性權限。ClaimsIdentityAuthorizeAttributepublic 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(); } }
您不能將依賴項作為建構子參數注入到操作過濾器,因為它們在 C# 中作為屬性實現。您需要使用
DependencyResolver.Current. 這是一種服務定位器,它並不酷,但你真的別無選擇。ASP.NET MVC 不使用 DI 容器來創建操作篩選器實例。public ClaimsAuthorizeAccountAccess(params string[] permissions) { _permissions = permissions; _accountRepository = DependencyResolver.Current.GetService<IAccountRepository>(); }