iFilterProviderと懸念の分離
-
13-12-2019 - |
質問
私はアクションフィルタ、すなわちカスタム承認属性のマイカスタム認証プロバイダに依存関係を注入する必要がある状況を持っています。私たちは「行動」から「属性メタデータ」を分離しているべきだと言っていた多くの人や投稿につまずいた。これは理にかなっており、フィルタ属性が '依存関係リストローバー'を介してインスタンス化されていないという事実もあり、それは依存関係を注入するのが難しいです。
だから私は私のコードを少しリファクタリングしました。
最初のOFF I Lawデータのみを除去する
[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ではなくコンストラクタdiを使っていることが好きです。
あなたが1つのタイプのフィルタしか持っていない場合、あなたのアプローチはうまく機能します。私は改善のための最大の潜在的な分野であると思います、あなたが複数の種類のフィルタを持っていた場合、フィルタプロバイダーの実装方法は何でしょうか。現在、フィルタプロバイダはそれが提供している属性のインスタンスとフィルタインスタンスに密接に結合されています。
属性をフィルタと組み合わせてプロパティDIを使用しても構わないと思っている場合は、切り離されたフィルタプロバイダを使用する簡単な方法があります。そのアプローチの2つの例は次のとおりです。
現在のアプローチで解決するための2つの課題があります。 1. DIを介したフィルタコンストラクタパラメータの全部を注入する。 2.属性から(依存注入)フィルタインスタンスへのマッピング。
現在、あなたは手動でも手動でやっています。フィルタ/属性が1つしかないときは確かに問題ありません。もっとあったら、あなたはおそらく両方の部分にとってより一般的なアプローチを望むでしょう。
チャレンジ#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);
}
}
.
CharterWindsorで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
他のヒント
これはおそらく少しだけですが、Davidによって提案されているように工場を避けることの1つの方法は、もう1つの属性を紹介することです。
[AssociatedFilter(typeof(MyAuthorizationFilter))]
.
元の属性に次のように追加することができます。
[AssociatedFilter(typeof(MyAuthorizationFilter))]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : Attribute
{
public string Code { get; set; }
}
.
aponsefilter属性はこのようになります。
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);
}
.
現在これは、最初のaccountsfilter属性のみを取得することに限定されています。理論的には、1つ以上のフィルタを追加できると思います(1つの属性はいくつかのフィルタをキックオフします)。P>
明らかに私達はまたエラー処理を追加する必要があります。関連する充実したものがない場合は...