我有一个情况,我需要在动作过滤器中注入一些依赖关系,即我自定义授权属性中的自定义授权提供程序。我偶然发现了很多人和帖子,他说我们应该将“属性元数据”分开来自“行为”。这是有道理的,并且还有以下事实是通过“dependencyResolver”无法实例化过滤属性,因此很难注入依赖关系。

所以我做了一点重构我的代码,我想知道我是否正确(我正在使用城堡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));
.

所以我首先想知道,如果我得到了这个想法,第二个,可以改进什么。

有帮助吗?

解决方案

是的,我觉得你得到了这个想法。我喜欢你在属性和过滤器实现之间分开关注,我喜欢你使用的是建设者di而不是property di。

如果您只有一种类型的过滤器,您的方法很好。我认为最大的改进潜在区域,如果您有多种类型的过滤器,则会如何实现过滤器提供商。目前,过滤器提供程序紧密耦合到它提供的属性和过滤器实例。

如果您愿意将属性与过滤器结合使用,并且使用属性di,则有一个简单的方法来拥有更加耦合的过滤器提供程序。以下是该方法的两个例子: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structiruremap-action-filters-andependency-inexp帖--in-asp-net-mvc-3 http://lozanotek.com/blog/archive/2010/10/12 /dependency_inempe_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);
   }
}
.

您可以通过使用CastleWindsor注册每个IfilterInstanceFactory的每个实现来解决挑战#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;
    }
}
.

David

其他提示

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

[AssociatedFilter(typeof(MyAuthorizationFilter))]
.

您可以添加到原始属性,如下所示。

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

关联的yilter属性看起来像这样。

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); 
}
. 目前,这仅限于仅采用第一个关联的愿者属性,理论上我猜你可以添加多个(一个属性踢掉几个过滤器),在这种情况下,您可以省略该位抓取第一个结果。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top