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などの回避策でした。
ありがとう!
最初に理解する必要があるのは、ベースコントローラーをインスタンス化していないことです。ベースコントローラーのインターフェイスと機能を継承する子コントローラーをインスタンス化します。これは重要な違いです。 「ChildControllersは追加の依存関係を使用していない」と言うと、間違いです。 ChildController [〜#〜] is [〜#〜] BaseControllerでもあるため。作成された2つの異なるクラスはありません。両方の機能を実装するクラスは1つだけです。
したがって、ChildController IS A BaseControllerであるため、基本クラスコンストラクターを呼び出す子コントローラーコンストラクターにパラメーターを渡すことに関して、何も悪いことや奇妙なことはありません。これは、その方法です。
基本クラスを変更すると、おそらく子クラスを変更する必要があります。子クラスに含まれない基本クラスの依存関係を注入するために、コンストラクター注入を使用する方法はありません。
プロパティの注入はお勧めしません。これは、適切な初期化なしでオブジェクトを作成でき、それらを正しく構成することを忘れないでください。
ところで、適切な用語はサブクラスとスーパークラスです。 「子」はサブクラスであり、親は「スーパークラス」です。
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>();
私はやや異なる(しかし、私にとっては、かなり明白で、おそらく一般的な)アプローチを採用しました。それは私には有効ですが、私が知らない落とし穴があるかもしれません。
ほとんどのコントローラーが使用する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;
}
...
}
このように、共通サービスは必要なときにオンザフライで生成され、多くのボイラープレートなしで私のコントローラーで利用できます。