Fra*_*oly 12 c# dependency-injection asp.net-mvc-filters
我有一种情况需要在动作过滤器中注入一些依赖项,即我的自定义授权属性中的自定义授权提供程序.我偶然发现了许多人和帖子,他们说我们应该将"属性元数据"与"行为"分开.这是有道理的,而且还有一个事实是过滤器属性没有通过'DependencyResolver'实例化,因此很难注入依赖项.
所以我对我的代码进行了一些重构,我想知道我是否正确(我使用Castle Windsor作为DI框架).
首先,我剥离了我的属性,只包含我需要的原始数据
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : Attribute
{
public string Code { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
我创建了一个自定义授权过滤器,其中包含确定当前用户是否具有适当授权的逻辑
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;
}
}
Run Code Online (Sandbox Code Playgroud)
最后一部分是创建一个自定义过滤器提供程序,它将获取此特定属性并实例化我的自定义过滤器,传递其依赖项以及从属性中提取所需的任何数据.
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后一步是在global.asax中注册过滤器提供程序
FilterProviders.Providers.Add(new MyAuthorizationFilterProvider(_container));
Run Code Online (Sandbox Code Playgroud)
所以我首先想知道,如果我的想法是正确的,那么可以改进的是什么.
是的,我认为你的想法是对的。我喜欢您将属性和过滤器实现之间的关注点分开,并且我喜欢您使用构造函数 DI 而不是属性 DI。
如果您只有一种类型的过滤器,您的方法效果很好。我认为,如果您有不止一种类型的过滤器,则最大的潜在改进领域将是过滤器提供程序的实现方式。目前,过滤器提供者与其提供的属性和过滤器实例紧密耦合。
如果您愿意将属性与过滤器结合起来并使用属性 DI,则可以通过一种简单的方法来获得更加解耦的过滤器提供程序。以下是该方法的两个示例: http ://www.thecoding humanist.com/blog/archives/2011/1/27/structuralmap-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);
}
Run Code Online (Sandbox Code Playgroud)
然后,您将为每个属性/过滤器对实现一个工厂:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
您只需向 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;
}
}
Run Code Online (Sandbox Code Playgroud)
大卫