12

アクションフィルターにいくつかの依存関係を挿入する必要がある状況があります。つまり、カスタム承認属性のカスタム承認プロバイダーです。「属性メタデータ」を「動作」から分離する必要があると言っている多くの人々や投稿に出くわしました。これは理にかなっており、フィルター属性が「DependencyResolver」を介してインスタンス化されないため、依存関係を挿入するのが難しいという事実もあります。

そこで、コードのリファクタリングを少し行い、それが正しいかどうかを知りたいと思いました(DIフレームワークとしてCastle Windsorを使用しています)。

まず、属性を削除して、必要な生データのみを含めました

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

ですから、私は最初に、私が正しい考えを持っていれば、次に何を改善できるのか疑問に思っています。

4

2 に答える 2

3

はい、あなたの考えは正しかったと思います。属性とフィルターの実装の間で懸念事項を分離している点と、プロパティ DI ではなくコンストラクター DI を使用している点が気に入っています。

フィルターの種類が 1 つしかない場合、このアプローチはうまく機能します。複数の種類のフィルターを使用している場合、改善の可能性が最も高い領域は、フィルター プロバイダーの実装方法であると思います。現在、フィルター プロバイダーは、それが提供する属性およびフィルター インスタンスに密接に結合されています。

属性をフィルターと組み合わせてプロパティ DI を使用する場合は、フィルター プロバイダーをさらに分離する簡単な方法があります。そのアプローチの 2 つの例を次に示し ます ://lozanotek.com/blog/archive/2010/10/12/dependency_injection_for_filters_in_mvc3.aspx

現在のアプローチで解決すべき課題が 2 つあります。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);
   }
}

IFilterInstanceFactory の各実装を CastleWindsor に登録するだけで、課題 #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;
    }
}

デビッド

于 2013-02-08T20:50:35.513 に答える
0

これはおそらく少し多いですが、David が提案したファクトリを回避する (そしてこれをもう少し汎用的にする) 1 つの方法は、さらに別の属性を導入することです。

[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 を引き出すことで、正しいフィルターを取得できます。

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 属性のみを取得するように制限されています。理論的には、複数を追加できると思います (1 つの属性が複数のフィルターを開始します)。その場合、最初の結果を取得する部分を省略します。

明らかに、AssociatedFilterAttribute がない場合など、エラー処理も追加する必要があります...

于 2014-01-21T11:47:42.270 に答える