IFilterProvider 和关注点分离 [英] IFilterProvider and separation of concerns

查看:14
本文介绍了IFilterProvider 和关注点分离的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一种情况,我需要在操作过滤器中注入一些依赖项,即我的自定义授权属性中的自定义授权提供程序.我偶然发现很多人和帖子都说我们应该将属性元数据"与行为"分开.这是有道理的,而且过滤器属性不是通过DependencyResolver"实例化的,因此很难注入依赖项.

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.

所以我对我的代码进行了一些重构,我想知道我是否正确(我使用 Castle Windsor 作为 DI 框架).

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

最后一步是在 global.asax 中注册过滤器提供者

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.

推荐答案

是的,我认为您的想法是对的.我喜欢你在属性和过滤器实现之间分离关注点,我喜欢你使用构造函数 DI 而不是属性 DI.

Yes, I think you got the idea right. I like that you're separating concerns between the attribute and the filter implementation, and I like that you're using constructor DI rather than property DI.

如果您只有一种类型的过滤器,那么您的方法效果很好.如果您有不止一种类型的过滤器,我认为最大的改进潜力领域将是过滤器提供程序的实现方式.目前,过滤器提供者与其提供的属性和过滤器实例紧密耦合.

Your approach works well if you only have one type of filter. I think the biggest potential area for improvement, if you had more than one type of filter, would be how the filter provider is implemented. Currently, the filter provider is tightly coupled to the attribute and filter instances it is providing.

如果您愿意将属性与过滤器结合使用并使用属性 DI,那么有一种简单的方法可以让过滤器提供程序更加解耦.以下是该方法的两个示例:http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in-asp-net-mvc-3http://lozanotek.com/blog/archive/2010/10/12/dependency_injection_for_filters_in_mvc3.aspx

If you're willing to combine the attribute with the filter and use property DI, there's a simple way to have a more decoupled filter provider. Here are two examples of that approach: 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. 从属性映射到(依赖注入的)过滤器实例.

There are two challenges to solve with the current approach: 1. Injecting some, but not all, of the filter constructor parameters via DI. 2. Mapping from an attribute to a (dependency-injected) filter instance.

目前,您正在手动执行这两项操作,当只有一个过滤器/属性时这当然没问题.如果还有更多,您可能希望对这两个部分采用更通用的方法.

Currently, you're doing both manually, which is certainly fine when there's only one filter/attribute. If there were more, you'd probably want a more general approach for both parts.

对于挑战 #1,您可以使用类似 _container.Resolve 的重载,让您传入参数.该解决方案是特定于容器的,可能有点棘手.

For challenge #1, you could use something like a _container.Resolve overload that lets you pass in arguments. That solution is rather container-specific and probably a bit tricky.

另一个解决方案,我将在这里描述,分离出一个工厂类,它只在其构造函数中接受依赖项,并生成一个需要 DI 和非 DI 参数的过滤器实例.

Another solution, which I'll describe here, separates out a factory class that only takes dependencies in its constructor and produces a filter instance requiring both DI and non-DI arguments.

这家工厂可能长这样:

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

然后,您将为每个属性/过滤器对实现一个工厂:

You'd then implement a factory for each attribute/filter pair:

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.

You can solve challenge #2 by just registering each implementation of IFilterInstanceFactory with CastleWindsor.

过滤器提供者现在可以与特定属性和过滤器的任何知识分离:

The filter provider can now be decoupled from any knowledge of specific attributes and filters:

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

大卫

这篇关于IFilterProvider 和关注点分离的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆