web-dev-qa-db-ja.com

ASP.NET MVC 3アクションフィルターへの依存関係の注入。このアプローチの何が問題になっていますか?

これがセットアップです。サービスのインスタンスを必要とするアクションフィルターがあるとします。

public interface IMyService
{
   void DoSomething();
}

public class MyService : IMyService
{
   public void DoSomething(){}
}

次に、そのサービスのインスタンスを必要とするActionFilterがあります。

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService; // <--- How do we get this injected

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

MVC 1/2では、アクションフィルターに依存関係を挿入することは、ちょっとした苦痛でした。最も一般的なアプローチは、次のようにカスタムアクション呼び出しを使用することでした: http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action -filters / この回避策の背後にある主な動機は、この次のアプローチがコンテナとのずさんで密な結合と考えられていたためです。

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService;

   public MyActionFilter()
      :this(MyStaticKernel.Get<IMyService>()) //using Ninject, but would apply to any container
   {

   }

   public MyActionFilter(IMyService myService)
   {
      _myService = myService;
   }

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

ここでは、コンストラクター注入を使用し、コンテナーを使用してサービスを注入するためにコンストラクターをオーバーロードしています。コンテナとActionFilterを緊密に結合することに同意します。

私の質問はこれです:ASP.NET MVC 3では、使用されているコンテナの抽象化が(DependencyResolverを介して)ありますが、これらのフープはすべて必要ですか?デモをさせてください:

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService;

   public MyActionFilter()
      :this(DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService)
   {

   }

   public MyActionFilter(IMyService myService)
   {
      _myService = myService;
   }

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

今、私は一部の純粋主義者がこれをsc笑するかもしれないことを知っていますが、真剣に、何が欠点でしょうか?テスト時にIMyServiceを取得し、モックサービスをそのように注入するコンストラクターを使用できるため、テスト可能です。 DependencyResolverを使用しているため、DIコンテナーの実装に縛られていません。このアプローチには欠点がありますか?

ちなみに、MVC3で新しいIFilterProviderインターフェイスを使用してこれを行うための別の素晴らしいアプローチがあります: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency -injection-in-asp-net-mvc-

77
BFree

私は肯定的ではありませんが、空のコンストラクターを(attribute部分に)使用し、実際に値を注入するコンストラクター(filterpart)。*

Edit:少し読んだ後、これを行うための受け入れられた方法はプロパティインジェクションを介しているようです:

public class MyActionFilter : ActionFilterAttribute
{
    [Injected]
    public IMyService MyService {get;set;}

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        MyService.DoSomething();
        base.OnActionExecuting(filterContext);
    }
}

Service Locatorの質問を使用しない理由について:それは、主に依存性注入の柔軟性を低下させるだけです。たとえば、ロギングサービスを注入していて、ロギングサービスに注入先のクラスの名前を自動的に付けたい場合はどうでしょうか。コンストラクターインジェクションを使用する場合、それはうまく機能します。 Dependency Resolver/Service Locatorを使用している場合は、運が悪いでしょう。

更新

これが答えとして受け入れられたので、私は Mark Seemanのアプローチ を好むと言って記録に行きたいと思います。なぜなら、アクションフィルターの責任を属性から分離しているからです。さらに、NinjectのMVC3拡張機能には、バインディングを介してアクションフィルターを構成する非常に強力な方法がいくつかあります。詳細については、次のリファレンスを参照してください。

更新2

以下のコメントで@usrが指摘したように、クラスがロードされるとActionFilterAttributesがインスタンス化され、アプリケーションのライフタイム全体にわたって持続します。 IMyServiceインターフェイスがシングルトンであると想定されていない場合、 Captive Dependency になります。その実装がスレッドセーフでない場合、あなたは多くの苦痛を感じるかもしれません。

クラスの予想寿命よりも短い寿命の依存関係がある場合は、直接依存関係を生成するのではなく、オンデマンドで依存関係を生成するファクトリを注入するのが賢明です。

30

Mark Seemannが提案した解決策はエレガントに思えます。ただし、単純な問題にはかなり複雑です。 AuthorizeAttributeを実装してフレームワークを使用すると、より自然に感じられます。

私の解決策は、global.asaxに登録されたサービスへの静的デリゲートファクトリを使用してAuthorizeAttributeを作成することでした。これは、あらゆるDIコンテナーで機能し、Service Locatorよりも少し良い感じです。

Global.asaxの場合:

MyAuthorizeAttribute.AuthorizeServiceFactory = () => Container.Resolve<IAuthorizeService>();

私のカスタム属性クラス:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
    public static Func<IAuthorizeService> AuthorizeServiceFactory { get; set; } 

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        return AuthorizeServiceFactory().AuthorizeCore(httpContext);
    }
}
6
Jakob