IFilter提供者和关注点分离

我有一种情况需要在动作filter中注入一些依赖项,即我的自定义授权属性中的自定义授权提供程序。 我偶然发现了许多人和post,他们说我们应该将“属性元数据”与“行为”分开。 这是有道理的,并且还有一个事实是filter属性没有通过’DependencyResolver’实例化,因此很难注入依赖项。

所以我对我的代码进行了一些重构,我想知道我是否正确(我使用Castle Windsor作为DI框架)。

首先,我剥离了我的属性,只包含我需要的原始数据

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

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

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

最后一部分是创建一个自定义filter提供程序,它将获取此特定属性并实例化我的自定义filter,传递其依赖项以及从属性中提取所需的任何数据。

 public class MyAuthorizationFilterProvider : IFilterProvider { private IWindsorContainer _container; public MyAuthorizationFilterProvider(IWindsorContainer container) { Contract.Requires(container != null); _container = container; } public IEnumerable GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { Type controllerType = controllerContext.Controller.GetType(); var authorizationProvider = _container.Resolve(); 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中注册filter提供程序

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

所以我首先想知道,如果我有了正确的想法,那么可以改进的是什么。

是的,我认为你的想法是正确的。 我喜欢你将属性和filter实现之间的关注分开,我喜欢你使用构造函数DI而不是属性DI。

如果您只有一种类型的filter,那么您的方法很有效。 我认为,如果您有多种类型的filter,最大的潜在改进领域将是filter提供商的实现方式。 目前,filter提供程序与它提供的属性和filter实例紧密耦合。

如果您愿意将属性与filter结合使用并使用属性DI,那么可以通过一种简单的方法来获得更加分离的filter提供程序。 以下是该方法的两个示例: 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注入一些但不是全部的filter构造函数参数。 2.从属性映射到(dependency injection的)filter实例。

目前,你手动完成,当只有一个filter/属性时,这当然很好。 如果还有更多,你可能想要对这两个部分采用更通用的方法。

对于挑战#1,您可以使用类似_container.Resolve的重载,它允许您传入参数。 该解决方案特定于容器,可能有点棘手。

我将在这里描述的另一个解决方案是分离出一个工厂类,它只在构造函数中使用依赖项,并生成一个需要DI和非DI参数的filter实例。

这是工厂的样子:

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

然后,您将为每个属性/filter对实现一个工厂:

 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。

filter提供程序现在可以与特定属性和filter的任何知识分离:

 public class MyFilterProvider : IFilterProvider { private IWindsorContainer _container; public MyFilterProvider(IWindsorContainer container) { Contract.Requires(container != null); _container = container; } public IEnumerable GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { Type controllerType = controllerContext.Controller.GetType(); var authorizationProvider = _container.Resolve(); 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(); foreach (IFilterInstanceFactory factory in factories) { object dependencyInjectedInstance = factory.Create(attribute); if (dependencyInjectedInstance != null) { return dependencyInjectedInstance; } } return attribute; } } 

大卫

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

 [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来检索正确的filter。

 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属性,理论上我猜你可以添加多个(一个属性启动几个filter),在这种情况下你省略了抓取第一个结果的位。

显然我们还需要添加error handling,例如,如果没有AssociatedFilterAttribute …

Interesting Posts