.Net Core 2.0中Controller和BaseController中的依赖注入重复

时间:2017-11-18 22:00:24

标签: c# asp.net-mvc dependency-injection asp.net-core-2.0

如果我在我的Asp.Net Core 2.0 Web应用程序中创建一个BaseController,其中包含了一些常见的依赖关系,那么它们在实际控制器中仍然是必需的。

例如,默认MVC 6 Web应用程序中的标准Account和Manage控制器。

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应用程序模板中,我将帐户控制器重构为三个不同的控制器,RegisterController(处理用户注册的所有内容),LoginController(处理登录和注销),余额为三分之一。我将Manage Controller拆分为两个,一个是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之间执行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
}

更新

Per 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吗?

3 个答案:

答案 0 :(得分:12)

Microsoft.AspNetCore.MVC.Controller类附带扩展方法

HttpContext.RequestServices.GetService<T>

只要HttpContext在管道中可用,就可以使用它(例如,如果从控制器的构造函数调用,HttpContext属性将为Null)

尝试这种模式

注意:请确保包含此指令 使用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中注册您的服务 - &gt; ConfingureServices方法

答案 1 :(得分:7)

very few good reasons to use a BaseController in MVC。此方案中的基本控制器仅添加了更多代码来维护,没有任何实际好处。

对于true cross-cutting concerns,在MVC中处理它们的最常用方法是使用global filters,尽管在MVC核心中有一些值得考虑的新选项。

但是,您的问题看起来不像是违反Single Responsibility Principle的跨领域问题。也就是说,拥有超过3个注入的依赖项是一个代码气味,你的控制器做得太多了。最实际的解决方案是Refactor to Aggregate Services

在这种情况下,我认为你至少有一个隐式服务需要明确 - 即UserManagerSignInManager应该包装在自己的服务中。从那里,您可以将其他3个依赖项注入该服务(当然,取决于它们的使用方式)。因此,您可以将这一点减少为AccountControllerManageController的单一依赖关系。

有些迹象表明控制器做得太多了:

  1. 有很多“帮助”方法包含在操作之间共享的业务逻辑。
  2. 动作方法不仅仅是简单的HTTP请求/响应。操作方法通常应该只调用处理输入和/或生成输出并返回视图和响应代码的服务。
  3. 在这些情况下,值得一看,您是否可以将该逻辑转移到自己的服务中,将任何共享逻辑转移到该服务的依赖关系等等。

答案 2 :(得分:2)

根据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);
    }
}

所以现在所有注入的对象都有实例。

希望最终解决方案不需要将常用的实例注入每个继承的控制器,只需要该特定控制器所需的任何其他实例对象。我从代码重复方面真正解决的是删除每个Controller中的私有字段。

仍然想知道BaseController是否应该从Controller或ControllerBase继承?