2012-05-22 26 views
11

我有一种情况,我需要在操作筛选器中注入一些依赖项,即我的自定义授权属性中的自定义授权提供程序。我偶然发现很多人和帖子都在说我们应该把'属性元数据'和'行为'区分开来。这是有道理的,并且还有一个事实,即filter属性没有通过'DependencyResolver'实例化,所以很难注入依赖关系。IFilterProvider和问题分离

因此,我对代码做了一些重构,并且想知道我是否正确(我使用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)); 

所以我想先注册过滤器供应商,如果我有这个想法的权利;第二,什么可以改善。

+0

嗨,弗朗索瓦,我想出了一个非常类似的解决方案,为你解决同样的问题。我现在正在问自己和你一样的问题。你最终使用这个解决方案吗?随着时间的推移它的任何问题?你有什么建议吗?谢谢。 –

回答

2

是的,我认为你的想法是正确的。我喜欢你将属性和过滤器实现之间的关注区分开来,我喜欢你使用构造函数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); 
    } 
} 

您可以只登记每个解决难题#2用CastleWindsor实现IFilterInstanceFactory。

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

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

大卫

+0

可能丢失了一些东西,但是上面的代码实际上并没有返回属性的实例(不是关联的过滤器)。 'code' object instance = Resolve(attribute); yield返回新的Filter(实例,FilterScope.Action,0); '代码' –

+0

Doh,忽略我,重新阅读并意识到工厂处理过滤器创建。 –

0

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

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