2012-05-22 38 views
11

Tôi có một tình huống mà tôi cần phải tiêm một số phụ thuộc vào bộ lọc hành động, cụ thể là nhà cung cấp ủy quyền tùy chỉnh của tôi trong thuộc tính ủy quyền tùy chỉnh của tôi. Tôi tình cờ gặp rất nhiều người và các bài đăng đang nói rằng chúng ta nên tách 'siêu dữ liệu thuộc tính' khỏi 'hành vi'. Điều này có ý nghĩa và cũng có một thực tế là các thuộc tính lọc không được khởi tạo thông qua 'DependencyResolver' do đó rất khó để chèn các phụ thuộc.IFilterProvider và tách mối quan tâm

Vì vậy, tôi đã làm một chút refactoring mã của tôi và tôi muốn biết nếu tôi đã có nó đúng (Tôi đang sử dụng Castle Windsor như khuôn khổ DI).

Trước hết tôi lột thuộc tính của tôi để chỉ chứa các dữ liệu thô Tôi cần

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

Tôi tạo ra một bộ lọc cho phép tùy chỉnh mà sẽ chứa logic của việc xác định nếu người dùng hiện có sự uỷ quyền thích hợp

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

Phần cuối cùng là tạo nhà cung cấp bộ lọc tùy chỉnh sẽ tìm nạp thuộc tính cụ thể này và khởi tạo bộ lọc tùy chỉnh của tôi chuyển phụ thuộc của nó và bất kỳ dữ liệu nào cần, trích xuất từ ​​thuộc tính.

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

Bước cuối cùng là đăng ký nhà cung cấp bộ lọc trong global.asax

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

Vì vậy, tôi tự hỏi đầu tiên, nếu tôi đã có ý tưởng đúng đắn và thứ hai, những gì có thể được cải thiện.

+0

Xin chào Francois, tôi đã đưa ra một giải pháp khá giống với cùng một vấn đề với bạn. Tôi hiện đang tự hỏi mình những câu hỏi tương tự như bạn. Bạn đã kết thúc bằng cách sử dụng giải pháp này? Bất kỳ vấn đề với nó theo thời gian? Bạn có đề nghị nào không? Cảm ơn bạn. –

Trả lời

2

Vâng, tôi nghĩ bạn có ý tưởng đúng. Tôi thích rằng bạn đang tách mối quan tâm giữa thuộc tính và triển khai bộ lọc và tôi thích rằng bạn đang sử dụng hàm tạo DI thay vì thuộc tính DI.

Cách tiếp cận của bạn hoạt động tốt nếu bạn chỉ có một loại bộ lọc. Tôi nghĩ rằng khu vực tiềm năng lớn nhất để cải thiện, nếu bạn có nhiều loại bộ lọc, sẽ là cách nhà cung cấp bộ lọc được triển khai. Hiện tại, nhà cung cấp bộ lọc được kết hợp chặt chẽ với thuộc tính và bộ lọc mà nó đang cung cấp.

Nếu bạn sẵn sàng kết hợp thuộc tính với bộ lọc và sử dụng thuộc tính DI, có một cách đơn giản để có nhà cung cấp bộ lọc được tách riêng hơn. Dưới đây là hai ví dụ về cách tiếp cận rằng: 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

Có hai thách thức để giải quyết với cách tiếp cận hiện tại: 1. Tiêm một số người, nhưng không phải tất cả, các thông số bộ lọc constructor qua DI. 2. Ánh xạ từ một thuộc tính đến một thể hiện bộ lọc (phụ thuộc).

Hiện tại, bạn đang thực hiện cả hai thủ công, điều này chắc chắn là tốt khi chỉ có một bộ lọc/thuộc tính. Nếu có nhiều hơn, bạn có thể muốn có một cách tiếp cận tổng quát hơn cho cả hai phần.

Đối với thách thứC# 1, bạn có thể sử dụng một cái gì đó như _container.Resolve overload cho phép bạn vượt qua đối số. Giải pháp đó khá cụ thể cho từng thùng chứa và có thể hơi phức tạp một chút.

Một giải pháp khác, tôi sẽ mô tả ở đây, tách ra một lớp nhà máy chỉ phụ thuộc vào hàm tạo của nó và tạo ra một cá thể lọc yêu cầu cả đối số DI và non-DI.

Đây là những gì nhà máy mà có thể trông giống như:

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

Sau đó, bạn muốn thực hiện một nhà máy cho mỗi cặp thuộc tính/lọc:

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

Bạn có thể giải quyết thách thứC# 2 bằng cách chỉ cần đăng ký mỗi thực hiện IFilterInstanceFactory với CastleWindsor.

Nhà cung cấp bộ lọc bây giờ có thể được tách riêng từ bất kỳ kiến ​​thức về các thuộc tính và các bộ lọc cụ thể:

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

+0

Có lẽ thiếu một cái gì đó, nhưng không mã này ở trên thực sự trả về một thể hiện của thuộc tính (không phải là bộ lọc liên quan). 'mã' đối tượng thể hiện = Giải quyết (thuộc tính); sản lượng trả về Bộ lọc mới (ví dụ, FilterScope.Action, 0); 'code' –

+0

Doh, bỏ qua tôi, đọc lại và nhận ra rằng nhà máy xử lý việc tạo bộ lọc. –

0

Đây có lẽ là một chút nhiều, nhưng một trong những cách để tránh các nhà máy theo đề nghị của David (và làm cho điều này một chút chung chung hơn) là để giới thiệu thêm một thuộc tính.

[AssociatedFilter(typeof(MyAuthorizationFilter))] 

Bạn có thể thêm thuộc tính ban đầu như sau.

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

Thuộc tính AssociatedFilter trông như thế này.

public class AssociatedFilterAttribute : Attribute 
{ 
    public AssociatedFilterAttribute(Type filterType) 
    { 
     FilterType = filterType; 
    } 
    public Type FilterType { get; set; } 
} 

Sau đó, bạn có thể truy xuất bộ lọc chính xác bằng cách kéo ra Loại bộ lọc từ thuộc tính này.

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

Hiện nay đây là hạn chế để chỉ lấy thuộc tính AssociatedFilter đầu tiên, về mặt lý thuyết Tôi đoán bạn có thể thêm nhiều hơn một (một thuộc tính đá ra nhiều bộ lọc) trong trường hợp bạn muốn bỏ qua chút nơi này lấy người đầu tiên kết quả.

Rõ ràng chúng tôi cũng cần thêm xử lý lỗi, ví dụ: nếu không có AssociatedFilterAttribute ...

Các vấn đề liên quan