IFilterProvider和关注点分离

13
我有一个情况,需要在操作筛选器中注入一些依赖项,即在自定义授权属性中注入我的自定义授权提供程序。我遇到了很多人和帖子,他们说我们应该将“属性元数据”与“行为”分离。这是有道理的,还有过滤器属性不是通过“DependencyResolver”实例化的,因此难以注入依赖项。
所以我对我的代码进行了一些重构,并想知道是否正确(我使用Castle Windsor作为DI框架)。
首先,我剥离了我的属性,仅包含我需要的原始数据。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : Attribute
{
    public string Code { get; set; }
}

我创建了一个自定义授权过滤器,其中包含确定当前用户是否具有适当授权的逻辑。

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

最后一步是创建一个自定义的过滤器提供程序,该提供程序将获取该特定属性并实例化我的自定义过滤器,传递其依赖项和从属性中提取的任何数据。

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中注册过滤器提供程序

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

所以我首先想知道,我是否理解得正确,其次是有什么可以改进的地方。


嗨,弗朗索瓦,我为同样的问题想出了一个非常相似的解决方案。我目前正在问自己你曾经问过的同样的问题。你最终使用了这个解决方案吗?随着时间的推移,它有任何问题吗?你有什么建议吗?谢谢。 - Jean-Francois
2个回答

3

是的,我认为您的想法正确。我喜欢您在属性和过滤器实现之间分离关注点,并且我喜欢您使用构造函数 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;
    }
}

大卫


可能有些遗漏,但是上面的代码实际上返回的是属性的一个实例(而不是相关联的过滤器)。 code object instance = Resolve(attribute); yield return new Filter(instance, FilterScope.Action, 0); code - Jon Hilton
哦,不用理我,我重新读了一遍,意识到工厂处理过滤器的创建。 - Jon Hilton

0

这可能有点过了,但是避免工厂的一种方法(正如David所建议的)并使其更通用的方法之一是引入另一个属性。

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

然后,您可以通过从该属性中提取FilterType来检索正确的过滤器。

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...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接