web-dev-qa-db-ja.com

.Net Core 2.0のControllerおよびBaseControllerでの依存性注入の複製

いくつかの一般的な依存関係をカプセル化するBaseControllerをAsp.Net Core 2.0 Webアプリケーションで作成する場合、実際のコントローラーでは依然として必要です。

たとえば、デフォルトのMVC 6 Webアプリケーションの標準のアカウントおよび管理コントローラー。

public class AccountController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IEmailSender _emailSender;
    private readonly ILogger _logger;

    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ILogger<AccountController> logger)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
        _logger = logger;
    }
   //rest of code removed
}

public class ManageController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IEmailSender _emailSender;
    private readonly ILogger _logger;
    private readonly UrlEncoder _urlEncoder;

    private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";

    public ManageController(
      UserManager<ApplicationUser> userManager,
      SignInManager<ApplicationUser> signInManager,
      IEmailSender emailSender,
      ILogger<ManageController> logger,
      UrlEncoder urlEncoder)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
        _logger = logger;
        _urlEncoder = urlEncoder;
    }
    // rest of code removed
}

作成しているカスタムWebアプリケーションテンプレートでは、アカウントコントローラーを3つの異なるコントローラー、RegisterController(ユーザー登録に関するすべてを処理する)、LoginController(ログインとログアウトを処理する)、およびバランスを3つにリファクタリングします。 Manage ControllerをManagePasswordController(パスワードに関連するすべて)とUserManageController(その他すべて)の2つに分割しました。

それぞれのDI要件には多くの共通点があり、それらをBaseControllerに入れたいと思います。このように見えるのは?

public abstract class BaseController : Controller
{
    private readonly IConfiguration _config;
    private readonly IEmailSender _emailSender;
    private readonly ILogger _logger;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly UserManager<ApplicationUser> _userManager;

     protected BaseController(IConfiguration iconfiguration,
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ILogger<ManageController> logger)
    {
        _config = iconfiguration;
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
        _logger = logger;
    }
    //rest of code removed
}

しかし、それは何も達成しないように思えますか?私にはまだすべてを注入する必要があるようだからです。私は正しいことはできません(私はDIが初めてなので、明らかに手がかりがありません)が、BaseControllerはBaseControllerとRegisterControllerに共通するNO DIを許可するはずです。私が間違っている?私がやろうとしていることをどのように達成しますか?

public class RegisterController : BaseController
{
    private const string ConfirmedRegistration = "User created a new account with password.";

    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IEmailSender _emailSender;
    private readonly ILogger _logger;
    private readonly IConfiguration _config;

     public RegisterController(
        IConfiguration config,
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ILogger<AccountController> logger) : base(config, userManager, signInManager, emailSender, logger)

    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
        _logger = logger;
        _config = config;
    }
    //rest of code removed
}

更新

ルーフォirの提案による

public abstract class BaseController : Controller
{
    protected UserManager<ApplicationUser> UserManager { get; }
    protected SignInManager<ApplicationUser> SignInManager { get; }
    protected IConfiguration Config { get; }
    protected IEmailSender EmailSender { get; }
    protected ILogger AppLogger { get; }

    protected BaseController(IConfiguration iconfiguration,
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ILogger<ManageController> logger)
    {
        AppLogger = logger;
        EmailSender = emailSender;
        Config = iconfiguration;
        SignInManager = signInManager;
        UserManager = userManager; 
    }
}

そして継承コントローラー

public class TestBaseController : BaseController
{

    public TestBaseController() : base()
    {

    }
}

これは機能しません。 Resharperは、TestBaseControllerコンストラクターの基本コンストラクター呼び出しにパラメーターを追加する必要があると言っています。

また、BaseControllerは.Net Core 2.0のControllerまたはControllerBaseを継承する必要がありますか?

12
dinotom

MVCでBaseControllerを使用する理由はほとんどありません です。このシナリオのベースコントローラーは、維持するコードを追加するだけで、実際のメリットはありません。

真の 横断的関心事 の場合、MVCでそれらを処理する最も一般的な方法は グローバルフィルター を使用することですが、MVCコアで考慮する価値のある新しいオプションがいくつかあります。

ただし、あなたの問題は、 単一責任原則 の違反ほど横断的な懸念のようには見えません。つまり、3つ以上の依存関係が挿入されるということは、コントローラーがやりすぎているコードのにおいです。最も実用的な解決策は、 集約サービスへのリファクタリング です。

この場合、明示的にする必要がある暗黙的なサービスが少なくとも1つあると主張します。つまり、UserManagerSignInManagerは独自のサービスにラップする必要があります。そこから、他の3つの依存関係をそのサービスに潜在的に注入することができます(もちろん、それらの使用方法によって異なります)。したがって、これを潜在的にAccountControllerManageControllerの両方の単一の依存関係に絞り込めます。

コントローラーがあまりにも多くのことをしていることを示すいくつかの兆候:

  1. アクション間で共有されるビジネスロジックを含む「ヘルパー」メソッドが多数あります。
  2. アクションメソッドは、単純なHTTP要求/応答以上のものを実行しています。通常、アクションメソッドは、入力を処理したり、出力を生成したり、ビューや応答コードを返すサービスを呼び出したりするだけです。

このような場合、そのロジックを独自のサービスに、共有ロジックをそのサービスの依存関係などに移動できるかどうかを確認する価値があります。

7
NightOwl888

Microsoft.AspNetCore.MVC.Controllerクラスには、拡張メソッドが付属しています

HttpContext.RequestServices.GetService<T>

HttpContextがパイプラインで使用可能な場合はいつでも使用できます(たとえば、コントローラーのコンストラクターから呼び出された場合、HttpContextプロパティはNullになります)

このパターンを試してください

注:このディレクティブを必ず含めてくださいsing Microsoft.Extensions.DependencyInjection;

ベースコントローラー

public abstract class BaseController<T> : Controller where T: BaseController<T>
{

    private ILogger<T> _logger;

    protected ILogger<T> Logger => _logger ?? (_logger = HttpContext.RequestServices.GetService<ILogger<T>>());

子コントローラー

[Route("api/authors")]
public class AuthorsController : BaseController<AuthorsController>
{

    public AuthorsController(IAuthorRepository authorRepository)
    {
        _authorRepository = authorRepository;
    }

    [HttpGet("LogMessage")]
    public IActionResult LogMessage(string message)
    {
        Logger.LogInformation(message);

        return Ok($"The following message has been logged: '{message}'");
    }

言うまでもなく、Startup.cs-> ConfingureServicesメソッドでサービスを登録することを忘れないでください

13

CalcとSir Rufoの両方の提案によると、これは機能します。

 public abstract class BaseController : Controller
{
    protected UserManager<ApplicationUser> UserManager { get; }
    protected SignInManager<ApplicationUser> SignInManager { get; }
    protected IConfiguration Config { get; }
    protected IEmailSender EmailSender { get; }
    protected ILogger AppLogger { get; }

    protected BaseController(IConfiguration iconfiguration,
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ILogger<ManageController> logger)
    {
        AppLogger = logger;
        EmailSender = emailSender;
        Config = iconfiguration;
        SignInManager = signInManager;
        UserManager = userManager; 
    }

    protected BaseController()
    {
    }
}

パラメーターは引き続き継承されたコントローラーに注入され、基本コンストラクターに渡される必要があります

public class TestBaseController : BaseController
{
    public static IConfigurationRoot Configuration { get; set; }

    public TestBaseController(IConfiguration config,
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ILogger<ManageController> logger) : base(config,userManager,signInManager,emailSender,logger)
    {
    }

    public string TestConfigGetter()
    {

        var t = Config["ConnectionStrings:DefaultConnection"];
        return t;
    }

    public class TestViewModel
    {
        public string ConnString { get; set; }
    }
    public IActionResult Index()
    {
        var tm = new TestViewModel { ConnString = TestConfigGetter() };
        return View(tm);
    }
}

そのため、注入されたすべてのオブジェクトにインスタンスがあります。

最終的なソリューションでは、一般的に必要なインスタンスを継承された各コントローラーに注入する必要はなく、その特定のコントローラーに必要な追加のインスタンスオブジェクトのみを注入することを望んでいました。コード繰り返しの側面から私が本当に解決したのは、各コントローラーのプライベートフィールドを削除することだけでした。

BaseControllerがControllerまたはControllerBaseを継承する必要があるかどうか疑問に思っていますか?

2
dinotom