tutorial pagina net mvc establecer entre ejemplos diferencias asp asp.net-mvc asp.net-mvc-4 dependency-injection unity-container action-filter

asp.net-mvc - pagina - web forms c# ejemplos



Unity Inject dependencias en la clase de filtro MVC con parĂ¡metros (3)

Estoy usando la inyección de dependencia Unity.MVC4 para acceder a mis servicios. Todo funciona como debería cuando se inyecta en el constructor de mi 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é diferentes ejemplos, pero no pude encontrar una solución que me funcionó.

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

Aplicación_Inicio

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 voy a mostrar es para el atributo de filtro que me gustaría usar en mis acciones. Quiero pasar un parámetro de tipo string array para poder validar si el usuario actual tiene permitido 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 completamente abiertas para los propietarios de cuentas, 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<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" }}); } } } }

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 inyección de constructor aquí, obviamente.

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

Se produjo una excepción del 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 el tipo Fildela.ClaimsAuthorizeAccountAccess no es configurable.

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

Resolviendo Fildela.ClaimsAuthorizeAccountAccess, (ninguno)

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

¡Gracias!


No puede inyectar dependencias como parámetros de constructor en filtros de acción porque se implementan como atributos en C #. Debe resolverlos utilizando DependencyResolver.Current . Es un tipo de localizador de servicios y no es genial, pero realmente no tienes otra 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<IAccountRepository>(); }


Primero instale el paquete oficial, Unity.Mvc lugar de Unity.MVC4 . Este paquete se instala y registra automáticamente UnityFilterAttributeFilterProvider que necesitamos para la inyección de dependencia del atributo. Puede verificar si su Unity se configuró bien mirando App_Start > UnityMvcActivator de Start UnityMvcActivator . Debe ver las siguientes dos líneas:

public static void Start() { // other codes FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().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 blog.ploeh.dk/2014/06/13/passive-attributes posteriores, la solución amigable para DI es separar el blog.ploeh.dk/2014/06/13/passive-attributes 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 IAuthorizationFilter DI que implementa IAuthorizationFilter y contiene el comportamiento deseado.

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

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

ClaimsIdentityAuthorizeAttribute

En primer lugar, tenemos nuestro atributo que no tiene 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 verificació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<string>(); } }

ReclamacionesIdentidadAutorizaciónFiltro

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

WhiteListMode un WhiteListMode que es verdadero de forma 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 un AllowAnonymousAttribute ). Afortunadamente, el marco para eso está integrado en AuthorizeAttribute por lo que solo lo usamos como una marca para verificar o no a nivel mundial.

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.

Esas son las cosas que agregamos a nuestro servicio. Puede 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<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); }

ReclamacionesIdentidadAutorizaciónServicio

Así que ahora hacemos la personalización avanzada para respaldar las reclamaciones. Separamos esto para que haya una costura que podamos 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<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" } }); } }

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

NOTA: Si necesita que cualquiera de las dependencias del filtro tenga una vida útil más corta que singleton, deberá 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<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) { } }

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

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