ifilterprovider和关注的分离
-
13-12-2019 - |
题
我有一个情况,我需要在动作过滤器中注入一些依赖关系,即我自定义授权属性中的自定义授权提供程序。我偶然发现了很多人和帖子,他说我们应该将“属性元数据”分开来自“行为”。这是有道理的,并且还有以下事实是通过“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 ...