2012-05-22 34 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 ...