Pregunta

Tengo una situación en la que necesito inyectar algunas dependencias en un filtro de acción, a saber, mi proveedor de autorización personalizado en mi atributo de autorización personalizado. Me topé con mucha gente y a las publicaciones que decían que deberíamos estar separando los "metadatos de atributos" del "comportamiento". Esto tiene sentido y también existe el hecho de que los atributos del filtro no se instancian a través del 'dependencyResolver' por lo que es difícil inyectar las dependencias.

Así que hice un poco de refactorización de mi código y quería saber si lo tenía bien (estoy usando Castle Windsor como el marco DI).

primero apagé mi atributo para contener solo los datos en bruto que necesito

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : Attribute
{
    public string Code { get; set; }
}

He creado un filtro de autorización personalizado que contendría la lógica de determinar si el usuario actual tiene la autorización adecuada

public class MyAuthorizationFilter : IAuthorizationFilter
{
    private IAuthorizationProvider _authorizationProvider;
    private string _code;

    public MyAuthorizationFilter(IAuthorizationProvider authorizationProvider, string code)
    {
        Contract.Requires(authorizationProvider != null);
        Contract.Requires(!string.IsNullOrWhiteSpace(code));

        _authorizationProvider = authorizationProvider;
        _code = code;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            BaseController controller = filterContext.Controller as BaseController;
            if (controller != null)
            {
                if (!IsAuthorized(controller.CurrentUser, controller.GetCurrentSecurityContext()))
                {
                    // forbidden
                    filterContext.RequestContext.HttpContext.Response.StatusCode = 403;
                    if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
                    {
                        filterContext.Result = new RedirectToRouteResult("default", new RouteValueDictionary(new
                        {
                            action = "http403",
                            controller = "error"
                        }), false);
                    }
                    else
                    {
                        filterContext.Result = controller.InvokeHttp404(filterContext.HttpContext);
                    }
                }
            }
            else
            {

            }
        }
        else
        {
            filterContext.Result = new RedirectResult(FormsAuthentication.LoginUrl);
        }
    }

    private bool IsAuthorized(MyUser user, BaseSecurityContext securityContext)
    {
        bool has = false;
        if (_authorizationProvider != null && !string.IsNullOrWhiteSpace(_code))
        {
            if (user != null)
            {
                if (securityContext != null)
                {
                    has = _authorizationProvider.HasPermission(user, _code, securityContext);
                }
            }
        }
        else
        {
            has = true;
        }
        return has;
    }
}

La última parte fue crear un proveedor de filtro personalizado que obtendría este atributo específico e instancie mi filtro personalizado que pase sus dependencias y cualquier información que necesita, extraída del atributo.

public class MyAuthorizationFilterProvider : IFilterProvider
{
    private IWindsorContainer _container;

    public MyAuthorizationFilterProvider(IWindsorContainer container)
    {
        Contract.Requires(container != null);
        _container = container;
    }

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        Type controllerType = controllerContext.Controller.GetType();
        var authorizationProvider = _container.Resolve<IAuthorizationProvider>();
        foreach (MyAuthorizeAttribute attribute in controllerType.GetCustomAttributes(typeof(MyAuthorizeAttribute), false))
        {
            yield return new Filter(new MyAuthorizationFilter(authorizationProvider, attribute.Code), FilterScope.Controller, 0);
        }
        foreach (MyAuthorizeAttribute attribute in actionDescriptor.GetCustomAttributes(typeof(MyAuthorizeAttribute), false))
        {
            yield return new Filter(new MyAuthorizationFilter(authorizationProvider, attribute.Code), FilterScope.Action, 0);
        }
    }
}

El último paso es el registro del proveedor de filtros en Global.asax

FilterProviders.Providers.Add(new MyAuthorizationFilterProvider(_container));

Así que me pregunto primero, si tengo la idea correcta y la segunda, lo que podría mejorarse.

¿Fue útil?

Solución

Sí, creo que tienes la idea correcta. Me gusta que esté separando las preocupaciones entre el atributo y la implementación del filtro, y me gusta que esté usando constructor DI en lugar de la propiedad DI.

Su enfoque funciona bien si solo tiene un tipo de filtro. Creo que el área de mayor potencial para mejorar, si tuvo más de un tipo de filtro, sería la forma en que se implementa el proveedor del filtro. Actualmente, el proveedor del filtro está acoplado firmemente al atributo y las instancias de filtro que está proporcionando.

Si está dispuesto a combinar el atributo con el filtro y use la propiedad DI, hay una manera sencilla de tener un proveedor de filtros más desconectado. Aquí hay dos ejemplos de ese enfoque: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structureMap-action-filters-and-dependency-inyy-in-asp-net-mvc-3 http://lozanotek.com/blog/archive/2010/10/12 /dependency_inyyy_for_filters_in_mvc3.aspx

Hay dos desafíos para resolver con el enfoque actual: 1. Inyectar algunos, pero no todos, de los parámetros del constructor de filtros a través de DI. 2. Mapeo de un atributo a una instancia de filtro (inyección de dependencia).

Actualmente, está haciendo ambas manualmente, lo que está bien, cuando solo hay un filtro / atributo. Si hubiera más, probablemente querrías un enfoque más general para ambas partes.

Para el desafío # 1, puede usar algo como una sobrecarga de _container.resolve que le permite pasar en argumentos. Esa solución es bastante específica del contenedor y probablemente un poco complicado.

Otra solución, que voy a describir aquí, separa una clase de fábrica que solo toma dependencias en su constructor y produce una instancia de filtro que requiere los DI y los argumentos no di.

Aquí está la fábrica de la fábrica:

public interface IFilterInstanceFactory
{
    object Create(Attribute attribute);
}

Luego implementaría una fábrica para cada par de atributos / filtros:

public class MyAuthorizationFilterFactory : IFilterInstanceFactory
{
    private readonly IAuthorizationProvider provider;

    public MyAuthorizationFilterFactory(IAuthorizationProvider provider)
    {
        this.provider = provider;
    }

    public object Create(Attribute attribute)
    {
        MyAuthorizeAttribute authorizeAttribute = attribute as MyAuthorizeAttribute;

        if (authorizeAttribute == null)
        {
            return null;
        }

        return new MyAuthorizationFilter(provider, authorizeAttribute.Code);
   }
}

Puede resolver el desafío # 2 al registrar cada implementación de IfilterinstanceFactory con CastlewindSor.

El proveedor del filtro ahora se puede desacoplar de cualquier conocimiento de los atributos y filtros específicos:

public class MyFilterProvider : IFilterProvider
{
    private IWindsorContainer _container;

    public MyFilterProvider(IWindsorContainer container)
    {
        Contract.Requires(container != null);
        _container = container;
    }

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        Type controllerType = controllerContext.Controller.GetType();
        var authorizationProvider = _container.Resolve<IAuthorizationProvider>();
        foreach (FilterAttribute attribute in controllerType.GetCustomAttributes(typeof(FilterAttribute), false))
        {
            object instance = Resolve(attribute);
            yield return new Filter(instance, FilterScope.Controller, 0);
        }
        foreach (FilterAttribute attribute in actionDescriptor.GetCustomAttributes(typeof(FilterAttribute), false))
        {
            object instance = Resolve(attribute);
            yield return new Filter(instance, FilterScope.Action, 0);
        }
    }

    private object Resolve(Attribute attribute)
    {
        IFilterInstanceFactory[] factories = _container.ResolveAll<IFilterInstanceFactory>();

        foreach (IFilterInstanceFactory factory in factories)
        {
            object dependencyInjectedInstance = factory.Create(attribute);

            if (dependencyInjectedInstance != null)
            {
                return dependencyInjectedInstance;
            }
        }

        return attribute;
    }
}

DAVID

Otros consejos

Esto es probablemente un poco más, pero una forma de evitar la fábrica como sugerida por David (y hacer esto un poco más genérica) es introducir otro atributo.

[AssociatedFilter(typeof(MyAuthorizationFilter))]

que podría agregar al atributo original de la siguiente manera.

[AssociatedFilter(typeof(MyAuthorizationFilter))]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : Attribute
{
    public string Code { get; set; }
}

El atributo asociadofilter parece esto.

public class AssociatedFilterAttribute : Attribute
{
    public AssociatedFilterAttribute(Type filterType)
    {
        FilterType = filterType;
    }
    public Type FilterType { get; set; }
}

Luego, puede recuperar el filtro correcto tirando del filtrante de este atributo.

private object Resolve(Attribute attribute)
{
    var filterAttributes = attribute.GetType().GetCustomAttributes(typeof(AssociatedFilterAttribute), false);
    var first = (AssociatedFilterAttribute)filterAttributes.FirstOrDefault();
    return new Filter(_container.Resolve(first.FilterType), FilterScope.First, null); 
}

Actualmente, esto se limita a tomar solo el primer atributo asociado, teóricamente, supongo que podría agregar más de uno (un atributo inicia varios filtros), en cuyo caso omitiría el bit donde esto agarra el primer resultado.

Obviamente también necesitamos agregar el manejo de errores, por ejemplo.Si no hay ningunafilterattribute ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top