Unity Inyectar dependencias en la clase de filtro MVC con parámetros

Estoy usando la dependency injection Unity.MVC4 para acceder a mis servicios. Todo funciona como debería cuando se inyecta en mi constructor de Controlador, pero lo que me gustaría hacer ahora es usar la inyección de propiedades en mi clase de filtro para poder acceder a mi base de datos desde adentro.

Antes de comenzar esta pregunta busqué en Google e intenté con diferentes ejemplos, pero no pude encontrar una solución que funcionara para mí.

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

Ejemplo de trabajo

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

El siguiente código que estoy a punto de mostrarle es para el atributo de filtro que me gustaría usar en mis acciones. Quiero pasar un parámetro de tipo matriz de cadena para que pueda validar si el usuario actual tiene permiso para acceder a la acción.

En mi aplicación hay dos tipos de usuarios, propietario de la cuenta e invitado. Todas las acciones están totalmente abiertas para los propietarios de la cuenta, pero para los invitados varía de una acción a otra. Por ejemplo, una acción puede requerir que tenga al menos uno de los tres permisos (leer, escribir y editar).

Filtrar:

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

Si tuviera que usar este filtro, se vería algo así como …

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

Sin embargo, esto no funciona porque el filtro espera dos parámetros, (IRepository y string []). Tampoco es posible usar la inyección de constructor aquí, obviamente.

Luego intenté implementar la solución de John Allers que se puede encontrar aquí . Parecía prometedor, pero me dio este error:

Se produjo una excepción de tipo ‘Microsoft.Practices.Unity.ResolutionFailedException’ en Microsoft.Practices.Unity.dll pero no se manejó en el código de usuario

Información adicional: Falló la resolución de la dependencia, type = “Fildela.ClaimsAuthorizeAccountAccess”, name = “(none)”.

La excepción ocurrió mientras: mientras se resolvía.

La excepción es: InvalidOperationException – La propiedad _accountRepository en tipo Fildela.ClaimsAuthorizeAccountAccess no se puede configurar.


En el momento de la excepción, el contenedor era:

Resolviendo Fildela.ClaimsAuthorizeAccountAccess, (ninguno)

¿Alguna sugerencia sobre cómo resolver este chico malo?

¡Gracias!

Primero instale el paquete oficial, Unity.Mvc lugar de Unity.MVC4 . Este paquete instala y registra automáticamente UnityFilterAttributeFilterProvider que necesitamos para la dependency injection de los atributos. Puede verificar si su unidad está bien configurada al buscar el método de Start App_Start > UnityMvcActivator . Debes ver las siguientes dos líneas:

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

Ahora puede agregar el atributo [Dependency] a las propiedades públicas del filtro.

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

Según los Atributos pasivos de publicación, la solución compatible con DI es separar AuthorizeAttribute en 2 partes:

  1. Un atributo que no contiene ningún comportamiento para marcar sus controladores y métodos de acción.
  2. Una clase DI-friendly que implementa IAuthorizationFilter y contiene el comportamiento deseado.

Para nuestros propósitos, solo heredamos AuthorizeAttribute para aprovechar algunas de sus funciones integradas.

Tenga en cuenta que si toma este enfoque, no tiene mucho sentido utilizar la inyección de propiedades para las dependencias de su base de datos. La inyección de constructores siempre es una mejor opción, de todos modos.

ClaimsIdentityAuthorizeAttribute

En primer lugar, tenemos nuestro atributo que no tiene ningún comportamiento para marcar nuestros controladores y acciones. Agregamos un poco de inteligencia para analizar los permisos en una matriz para que no tenga que hacerse en cada comprobación de autorización.

 [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

A continuación, tenemos nuestro filtro de autorización que actuará como un filtro global.

WhiteListMode un WhiteListMode que es verdadero de manera predeterminada porque esa es la forma recomendada de configurar la seguridad (los controladores y las acciones requieren un inicio de sesión a menos que se les AllowAnonymousAttribute ). Afortunadamente, el marco para eso está integrado en AuthorizeAttribute así que solo lo usamos como indicador para verificar o no globalmente.

También agregamos un punto de extensión donde se puede inyectar nuestro servicio de autorización personalizado. Las 2 cosas más probables de cambiar son:

  1. La prueba para determinar si la acción está autorizada.
  2. La acción a tomar cuando el usuario no está autorizado.

Entonces esas son las cosas que agregamos a nuestro servicio. Podría refactorizar esto en 2 servicios separados, si lo desea.

 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

Entonces ahora hacemos la personalización avanzada para admitir reclamos. Separamos esto por lo que hay una costura que podemos usar para inyectar otra instancia si la lógica de negocios cambia en el 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

Registre su filtro globalmente e inyecte sus dependencias con su contenedor.

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

NOTA: si necesita que alguna de las dependencias del filtro tenga una vida útil más corta que el singleton, necesitará usar un GlobalFilterProvider como en esta respuesta .

Puesta en marcha

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

Y luego, en su controlador, para la seguridad de la lista negra, deberá decorar cada acción (o controlador) para bloquearla.

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

Para la seguridad de la lista blanca, solo necesita decorar las acciones a las que todos tienen acceso con AllowAnonymous o agregar un ClaimsIdentityAuthorizeAttribute con ClaimsIdentityAuthorizeAttribute más o menos restrictivos que el nivel global o de controlador.

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

No puede inyectar dependencias como parámetros de constructor a filtros de acción porque se implementan como atributos en C #. Debes resolverlos usando DependencyResolver.Current . Es una especie de Localizador de servicios y no es genial, pero realmente no tienes opción. ASP.NET MVC no utiliza el contenedor DI para crear instancias de filtro de acción.

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