IFilterProvider和关注点分离IFilterProvider和关注点分离(IFilter

2019-05-14 10:36发布

I have a situation where I need to inject some dependencies in a action filter, namely, my custom authorization provider in my custom authorization attribute. I stumbled upon a lot of people and posts who were saying that we should be separating the 'attribute metadata' from the 'behavior'. This makes sense and there is also the fact that filter attributes are not instantiated through the 'DependencyResolver' so it is difficult to inject the dependencies.

So I did a little refactoring of my code and I wanted to know if I had it right (I'm using Castle Windsor as the DI framework).

First off I stripped my attribute to contain only the raw data I need

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

I created a custom authorization filter that would contain the logic of determining if the current user has the proper authorization

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

The last part was to create a custom filter provider that would fetch this specific attribute and instantiate my custom filter passing its dependencies and any data it needs, extracted from the attribute.

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

The last step is the register the filter provider in the global.asax

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

So I'm wondering first, if I got the idea right and second, what could be improved.

Answer 1:

是的,我觉得你有这个想法的权利。 我喜欢你分离属性和过滤器实现之间的关切,我喜欢你正在使用的构造DI,而不是财产DI。

你的方法效果很好,如果你只有一个类型的过滤器。 我认为最大的潜在领域需要改进,如果你有一个以上类型的过滤器,将过滤器供应商是如何实现的。 目前,该过滤器提供紧密耦合到所述属性和过滤器情况下,它被提供。

如果你愿意的属性与过滤器相结合,使用属性DI,有一个简单的办法有一个更解耦过滤器供应商。 以下是方法的两个例子: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in-asp-net-mvc-3 HTTP ://lozanotek.com/blog/archive/2010/10/12/dependency_injection_for_filters_in_mvc3.aspx

有两个挑战,目前的办法来解决:1,注射一些,但不是全部通过DI过滤器的构造函数的参数。 2.从属性映射到(依赖注射)滤波器实例。

目前,你正在做手动两种,这当然是很好,当只有一个过滤器/属性。 如果有更多的,你可能想为两个部分一个更通用的方法。

对于挑战#1,你可以使用像一个_container.Resolve过载,使您在参数传递。 该解决方案是相当集装箱专用,可能有点棘手。

另一种解决方案,我将在这里描述,分离出一个工厂类,只需要依赖于它的构造和生产的同时需要DI和非DI参数的过滤器实例。

下面是工厂可能是什么样子:

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

然后你会实现对每个属性/滤波器对工厂:

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

你可以只注册IFilterInstanceFactory的每个实现与CastleWindsor解决难题#2。

该过滤器提供商现在可以从特定的属性和过滤器的任何知识去耦:

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

大卫



Answer 2:

这可能是有点多,但避免了工厂大卫的建议(并使该多一点通用)是引入另一种属性的一种方式。

[AssociatedFilter(typeof(MyAuthorizationFilter))]

这可以按如下方式添加到原来的属性。

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

该AssociatedFilter属性看起来是这样的。

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

然后你就可以从这个属性拉出过滤式检索正确的过滤器。

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

目前,这仅限于拍摄第一AssociatedFilter属性,理论上我想你可以添加多个(一个属性序幕几个过滤器)在这种情况下你会忽略这个地方抓住了第一个结果位。

很显然,我们还需要添加错误处理,例如,如果没有AssociatedFilterAttribute ...



文章来源: IFilterProvider and separation of concerns