2017-11-18 7 views
2

共通の依存関係の一部をカプセル化するAsp.Net Core 2.0 WebアプリケーションでBaseControllerを作成しても、それは実際のコントローラーではまだ必要です。.Net Core 2.0のコントローラーとBaseControllerの依存性注入の重複

例えば、デフォルトのMVC 6 Webアプリケーションの標準のアカウントと管理コントローラ。私は、私は、3つの異なるコントローラにアカウントコントローラをリファクタリング構築(ユーザー登録に関するすべてを扱う)RegisterController、(ログインとログアウトを扱う)LoginController、そしてバランスにしていますカスタム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 
} 

三分の一。 Manage Controllerを2つに分割し、ManagePasswordController(パスワードに関連するすべて)とUserManageController(その他すべて)を分割しました。

それぞれの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の間で共通の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 
} 

アップデートサー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; 
    } 
} 

パー

、これは動作しません継承コントローラ

public class TestBaseController : BaseController 
{ 

    public TestBaseController() : base() 
    { 

    } 
} 

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

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

+1

'BaseController'にコンストラクタ呼び出しのすべてを提供する必要がある場合は、yesを返します。しかし、それらのすべてを必要としないように 'BaseController'をリファクタリングすることができます。また、 'BaseController'とそのサブクラスの両方で参照を保存する理由は? – fredrik

+0

@ fredrik ..私がポストで言及している5つのコントローラは、すべてそれらのDIが必要です。それは私が実際のコントローラで行う必要があるのでしょうか?プライベート参照を削除し、渡されたパラメータを使用するだけですか?つまり、_signInManager.IsSignedInではなく、signInManager.IssignedIn? – dinotom

+1

注入された参照をBaseControllerの保護されたプロパティを介してパブリッシュして、派生クラスからアクセスできるようにします –

答えて

3

very few good reasons to use a BaseController in MVCがあります。このシナリオのベースコントローラは、管理するコードを増やすだけで、実際のメリットはありません。

trueの場合、MVCで扱う最も一般的な方法はglobal filtersですが、MVCコアで検討する価値のある新しいオプションがいくつかあります。

しかし、あなたの問題は、横断的な懸念のように、Single Responsibility Principleの違反のようには見えません。つまり、3つ以上の依存関係が注入されていると、コントローラがあまりにも多くのことをしているというコードの臭いがあります。最も実用的な解決策はRefactor to Aggregate Servicesです。

この場合、明示する必要がある少なくとも1つの暗黙のサービスがあると言います - つまり、UserManagerSignInManagerは、それ自身のサービスにラップする必要があります。そこから、他の3つの依存関係をそのサービスに潜在的に注入することができます(もちろん、その使い方に応じて)。したがって、これをAccountControllerManageControllerの両方の単一の依存関係に変えることができます。コントローラはあまりやっていることを

いくつかの兆候:

  1. アクション間で共有されるビジネスロジックを含む「ヘルパー」方法がたくさんあります。
  2. アクションメソッドは単純なHTTPリクエスト/レスポンス以上のことをしています。アクションメソッドは、通常、入力を処理したり、出力を生成したり、ビューと応答コードを返すサービスを呼び出すだけですべきです。このような場合には

は、それはあなたがなど、独自のサービスとそのサービスの依存関係に任意の共有ロジック、

+1

@ NightOwl888 ...まだMVC 6のためのデフォルトのWebアプリケーションは、 4個の依存関係を注入し、ManageControllerに5個の依存関係を注入する。これは、共通の注入された依存関係を1つの基本クラスに減らすために、この演習の目的全体であったため、新しく作成されたコントローラは基本クラスからそれらを取り出すことができました。私はプロのプログラマーではなく、学習の仕組みとして自分のWebアプリケーションテンプレートを構築するためにCore 2.0を学ぼうとしています。 「明示的に指定する必要がある少なくとも1つの暗黙のサービスがある」という例は、参考になります – dinotom

1

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から継承する必要があるかどうか疑問ですか?

0

マイクロソフトにそのロジックを動かすことができるかどうかを確認するには一見の価値があります。 AspNetCore.MVC.ControllerクラスはF呼び出された場合などのHttpContextプロパティはnullになります(のHttpContextがパイプラインで利用可能であるときはいつでも使用することができます拡張メソッド

HttpContext.RequestServices.GetService<T>

が付属していますROMコントローラのコンストラクター)

スタートアップにあなたのサービスを登録することを忘れない、言うまでもなく

[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}'"); 
    } 

ベースコントローラ

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

子供コントローラーこのパターンを試してみてください.cs - > ConfingureServicesメソッド