Unity Inject le dipendenze nella class di filtro MVC con i parametri

Sto usando un’iniezione di dipendenza di Unity.MVC4 per accedere ai miei servizi. Tutto funziona come dovrebbe quando si esegue l’iniezione nel mio costruttore di Controller, ma quello che vorrei fare ora è usare l’ iniezione di proprietà nella mia class filtro in modo da poter accedere al mio database dall’interno.

Prima di iniziare questa domanda ho cercato su Google e ho provato diversi esempi ma non sono riuscito a trovare una soluzione che funzionasse per 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(); container.RegisterType(); container.RegisterType(); container.RegisterType(); container.RegisterType(); container.RegisterType(); // register all your components with the container here // it is NOT necessary to register your controllers // eg container.RegisterType(); 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(); } } 

Esempio di lavoro

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

Il seguente codice che sto per mostrarti riguarda l’attributo filter che vorrei usare sulle mie azioni. Voglio passare un parametro di tipo string array in modo da poter verificare se l’utente corrente è autorizzato ad accedere all’azione.

Nella mia applicazione ci sono due tipi di utenti, proprietario del conto e ospite. Tutte le azioni sono completamente aperte per i proprietari di account, ma per gli ospiti varia da azione ad azione. Ad esempio, un’azione può richiedere di avere almeno una delle tre autorizzazioni, (leggi, scrivi e modifica).

Filtro:

 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 accountLinkPermissions = new List(); 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 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" }}); } } } } 

Se dovessi usare questo filtro sembrerebbe qualcosa di simile ..

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

Tuttavia questo non funziona perché il filtro si aspetta due parametri, (IRepository e string []). Inoltre, non è ansible utilizzare l’iniezione del costruttore qui, ovviamente.

Ho quindi provato a implementare la soluzione John Allers che può essere trovata qui . Sembrava promettente ma mi ha dato questo errore:

Un’eccezione di tipo “Microsoft.Practices.Unity.ResolutionFailedException” si è verificata in Microsoft.Practices.Unity.dll ma non è stata gestita nel codice utente

Informazioni aggiuntive: risoluzione della dipendenza non riuscita, digitare = “Fildela.ClaimsAuthorizeAccountAccess”, name = “(none)”.

Si è verificata un’eccezione mentre: mentre si risolveva.

L’eccezione è: InvalidOperationException – La proprietà _accountRepository sul tipo Fildela.ClaimsAuthorizeAccountAccess non è impostabile.


Al momento dell’eccezione, il contenitore era:

Risolvere Fildela.ClaimsAuthorizeAccountAccess, (nessuno)

Qualche suggerimento su come risolvere questo cattivo ragazzo?

Grazie!

Prima installa il pacchetto ufficiale, Unity.Mvc anziché Unity.MVC4 . Questo pacchetto installa e registra automaticamente UnityFilterAttributeFilterProvider cui abbiamo bisogno per l’iniezione delle dipendenze dell’attributo. È ansible verificare se l’unità è stata configurata correttamente osservando il metodo di Start App_Start > UnityMvcActivator . Devi vedere le seguenti due righe:

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

Ora puoi aggiungere l’attributo [Dependency] alle proprietà pubbliche del filtro.

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

Come per gli attributi post passivi , la soluzione DI-friendly consiste nel separare AuthorizeAttribute in 2 parti:

  1. Un attributo che non contiene alcun comportamento per contrassegnare i controller e i metodi di azione con.
  2. Una class DI-friendly che implementa IAuthorizationFilter e contiene il comportamento desiderato.

Per i nostri scopi, ereditiamo semplicemente AuthorizeAttribute per sfruttare alcune delle sue funzionalità incorporate.

Si noti che se si utilizza questo approccio, non ha molto senso utilizzare l’iniezione di proprietà per le dipendenze del database. In ogni caso, l’iniezione del costruttore è sempre una scelta migliore.

ClaimsIdentityAuthorizeAttribute

Prima di tutto, abbiamo il nostro attributo che non ha alcun comportamento per contrassegnare i nostri controller e azioni con. Aggiungiamo un po ‘di intelligenza per analizzare le autorizzazioni in un array in modo che non debba essere fatto su ogni controllo di authorization.

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

ClaimsIdentityAuthorizationFilter

Successivamente, abbiamo il nostro filtro di authorization che fungerà da filtro globale.

Aggiungiamo un WhiteListMode che è vero per impostazione predefinita perché questo è il modo consigliato per configurare la sicurezza (i controllori e le azioni richiedono un accesso a meno che non venga loro fornito un AllowAnonymousAttribute ). Fortunatamente, il framework per questo è incorporato in AuthorizeAttribute quindi lo usiamo solo come flag indipendentemente dal fatto che lo si possa verificare globalmente.

Aggiungiamo anche un punto di estensione in cui è ansible iniettare il nostro servizio di authorization personalizzato. Le 2 cose più probabili da cambiare sono:

  1. Il test per determinare se l’azione è autorizzata.
  2. L’azione da intraprendere quando l’utente non è autorizzato.

Quindi quelle sono le cose che aggiungiamo al nostro servizio. Si potrebbe refactoring in 2 servizi separati, se lo si desidera.

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

Quindi ora facciamo la personalizzazione avanzata per supportare le richieste. Separiamo questo in modo che ci sia una giunzione che possiamo usare per iniettare un’altra istanza se la logica aziendale cambia in futuro.

 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 accountLinkPermissions = new List(); 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 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" } }); } } 

uso

Registra il tuo filtro a livello globale e inietta le sue dipendenze con il tuo contenitore.

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

NOTA: se si ha bisogno di una delle dipendenze del filtro per avere una durata inferiore al singleton, è necessario utilizzare un GlobalFilterProvider come in questa risposta .

Avviare

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

Bootstrapper

 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(); container.RegisterType(); container.RegisterType(); container.RegisterType(); container.RegisterType(); container.RegisterType(); // Register the types for the authorization filter container.RegisterType( // 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(); // register all your components with the container here // it is NOT necessary to register your controllers // eg container.RegisterType(); RegisterTypes(container); return container; } public static void RegisterTypes(IUnityContainer container) { } } 

E poi nel tuo controller, per la sicurezza della lista nera, dovrai decorare ogni azione (o controller) per bloccarla.

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

Per la sicurezza della white list, è sufficiente decorare le azioni a cui tutti hanno accesso AllowAnonymous o aggiungere ClaimsIdentityAuthorizeAttribute con ClaimsIdentityAuthorizeAttribute più o meno restrittive rispetto al livello globale o del controller.

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

Non è ansible iniettare le dipendenze come parametri del costruttore nei filtri di azione perché sono implementati come attributi in C #. Devi risolverli usando DependencyResolver.Current . È una specie di Localizzatore di servizi e non è bello ma non hai una scelta davvero. ASP.NET MVC non utilizza il contenitore DI per creare istanze filtro azione.

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