web-dev-qa-db-ja.com

Unityを使用したASP.NET MVCのベースコントローラーコンストラクターインジェクション

MVC 5プロジェクトには、共有機能を実装するベースコントローラーがあります。この機能にはいくつかの依存関係が必要です。 Unity 3を使用してこれらの実装をコントローラーに注入しています。このパターンは、このベースコントローラーから継承するようにコントローラーを切り替えるまで正常に機能していました。今、私は次の問題に直面しています:

public class BaseController : Controller
{
    private readonly IService _service;

    public BaseController(IService service)
    {
        _service = service;
    }
}

public class ChildController : BaseController
{
    private readonly IDifferentService _differentService;

    public ChildController(IDifferentService differentService)
    {
        _differentService = differentService;
    }
}

これは当然'BaseController' does not contain a constructor that takes 0 argumentsのエラーをスローしています。 UnityはBaseControllerの構築を解決していないため、依存関係を挿入することはできません。この問題を解決する2つの明白な方法があります。

1。)BaseController ctorを明示的に呼び出し、各ChildController ctorにBaseControllerの依存関係を注入させます

public class ChildController : BaseController
{
    private readonly IDifferentService _differentService;

    public ChildController(IDifferentService differentService,
                           IService baseService)
        : base(baseService)
    {
        _differentService = differentService;
    }
}

私はこのアプローチがいくつかの理由で好きではありません:1つは、ChildControllersが追加の依存関係を使用していないためです(したがって、理由もなく子コントローラでコンストラクタが肥大化する)、より重要なことは、コンストラクタシグネチャを変更した場合ベースコントローラーの場合、各子コントローラーのコンストラクターシグネチャを変更する必要があります。

2。)プロパティインジェクションを介してBaseControllerの依存関係を実装します

public class BaseController : Controller
{
    [Dependency]
    public IService Service { get; set; }

    public BaseController() { }
}

私はこのアプローチがより気に入っています-BaseControllerのコンストラクターコードで依存関係を使用していません-しかし、それはコードの依存関係注入戦略を一貫性のないものにします。これも理想的ではありません。

Unityコンテナを呼び出してctorのメソッドシグネチャを整理する何らかの種類のBaseController依存関係解決関数を含むさらに優れたアプローチがおそらくありますが、あまりにも複雑なことを書く前に、誰かがこの問題を以前に解決したことがあるのか​​と思いましたか?ウェブ上に浮かぶいくつかのソリューションを見つけましたが、それらは使用したくないService Locatorなどの回避策でした。

ありがとう!

27
NWard

最初に理解する必要があるのは、ベースコントローラーをインスタンス化していないことです。ベースコントローラーのインターフェイスと機能を継承する子コントローラーをインスタンス化します。これは重要な違いです。 「ChildControllersは追加の依存関係を使用していない」と言うと、間違いです。 ChildController [〜#〜] is [〜#〜] BaseControllerでもあるため。作成された2つの異なるクラスはありません。両方の機能を実装するクラスは1つだけです。

したがって、ChildController IS A BaseControllerであるため、基本クラスコンストラクターを呼び出す子コントローラーコンストラクターにパラメーターを渡すことに関して、何も悪いことや奇妙なことはありません。これは、その方法です。

基本クラスを変更すると、おそらく子クラスを変更する必要があります。子クラスに含まれない基本クラスの依存関係を注入するために、コンストラクター注入を使用する方法はありません。

プロパティの注入はお勧めしません。これは、適切な初期化なしでオブジェクトを作成でき、それらを正しく構成することを忘れないでください。

ところで、適切な用語はサブクラスとスーパークラスです。 「子」はサブクラスであり、親は「スーパークラス」です。

29

ASP.Net 5を使用し、DIで構築されています

public class BaseController : Controller
{
    protected ISomeType SomeMember { get; set; }

    public BaseController(IServiceProvider serviceProvider)
    {
        //Init all properties you need
        SomeMember = (SomeMember)serviceProvider.GetService(typeof(ISomeMember));
    }
}

public class MyController : BaseController  
{
public MyController(/*Any other injections goes here*/, 
                      IServiceProvider serviceProvider) : 
 base(serviceProvider)
{}
}

[〜#〜] update [〜#〜]

Microsoft.Extensions.DependencyInjectionには、それを短くするための拡張メソッドもあります

SomeMember = serviceProvider.GetRequiredService<ISomeMember>();
13
Vitaly

私はやや異なる(しかし、私にとっては、かなり明白で、おそらく一般的な)アプローチを採用しました。それは私には有効ですが、私が知らない落とし穴があるかもしれません。

ほとんどのコントローラーが使用するBaseControllerクラスに共通のサービスプロパティを作成しました。それらは、必要/参照されるとインスタンス化されます。

特定のコントローラーに必要なサービスのうち、あまり使用されていないサービスがある場合は、通常どおり、そのコントローラーのコンストラクターにサービスを注入します。

using JIS.Context;
using JIS.Managers;
using JIS.Models;
using JIS.Services;
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;

namespace JIS.Controllers
{
    public class BaseController : Controller
    {
        public BaseController()
        {
            ViewBag.User = UserManager?.User;
        }

        private IUserManager userManager;
        public IUserManager UserManager
        {
            get
            {
                if (userManager == null)
                {
                    userManager = DependencyResolver.Current.GetService<IUserManager>();
                }
                return userManager;
            }
            set
            {
                userManager = value;
            }
        }

        private ILoggingService loggingService;
        public ILoggingService LoggingService
        {
            get
            {
                if (loggingService == null)
                {
                    loggingService = DependencyResolver.Current.GetService<ILoggingService>();
                }
                return loggingService;
            }
            set { loggingService = value; }
        }

        private IUserDirectory userDirectory;
        public IUserDirectory UserDirectory
        {
            get
            {
                if (userDirectory == null)
                {
                    userDirectory = DependencyResolver.Current.GetService<IUserDirectory>();
                }
                return userDirectory;
            }
            set { userDirectory = value; }
        }

        private ApplicationDbContext appDb;
        public ApplicationDbContext AppDb
        {
            get
            {
                if (appDb == null)
                {
                    appDb = new ApplicationDbContext();
                }
                return appDb;
            }
            set
            {
                appDb = value;
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && appDb != null)
            {
                appDb.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

私のコントローラーでは、このBaseControllerからサブクラスを作成するだけです。

public class UsersController : BaseController
{
    // and any other services I need are here:
    private readonly IAnotherService svc;
    public UsersController(IAnotherService svc)
    {
        this.svc = svc;
    }
...
}

このように、共通サービスは必要なときにオンザフライで生成され、多くのボイラープレートなしで私のコントローラーで利用できます。

0
DBatesX