Dependency injection duplication in Controller and BaseController in .Net Core 2.0

前端 未结 3 941
太阳男子
太阳男子 2021-02-01 21:54

If I create a BaseController in my Asp.Net Core 2.0 web application that capsulizes some of the common dependencies are they still necessary in the actual controllers.

F

相关标签:
3条回答
  • 2021-02-01 22:28

    There are very few good reasons to use a BaseController in MVC. A base controller in this scenario only adds more code to maintain, with no real benefit.

    For true cross-cutting concerns, the most common way to handle them in MVC is to use global filters, although there are a few new options worth considering in MVC core.

    However, your problem doesn't look like a cross-cutting concern so much as a violation of the Single Responsibility Principle. That is, having more than 3 injected dependencies is a code smell that your controller is doing too much. The most practical solution would be to Refactor to Aggregate Services.

    In this case, I would argue that you have at least 1 implicit service that you need to make explicit - namely, UserManager and SignInManager should be wrapped into a service of its own. From there, you could potentially inject your other 3 dependencies into that service (depending on how they are used, of course). So, you could potentially whittle this down to a single dependency for both the AccountController and ManageController.

    Some signs that a controller is doing too much:

    1. There are a lot of "helper" methods that contain business logic that are shared between actions.
    2. Action methods are doing more than simple HTTP request/response stuff. An action method should generally do nothing but call services that process input and/or produce output and return views and response codes.

    In cases such as these, it is worth a look to see if you can move that logic into a service of their own and any shared logic into dependencies of that service, etc.

    0 讨论(0)
  • 2021-02-01 22:37

    The Microsoft.AspNetCore.MVC.Controller class comes with the extension method

    HttpContext.RequestServices.GetService<T>

    Which can be used whenever the HttpContext is available in the pipeline (e.g. The HttpContext property will be Null if called from the controller's constructor)

    Try this pattern

    Note: make sure you include this directive using Microsoft.Extensions.DependencyInjection;

    Base Controller

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

    Child Controller

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

    Needless to say, remember to register your services in the Startup.cs --> ConfingureServices method

    0 讨论(0)
  • 2021-02-01 22:52

    Per suggestions from both Calc and Sir Rufo, this works.

     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()
        {
        }
    }
    

    The parameters still have to be injected into the inherited controller and passed to the base constructor

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

    So now all the injected objects will have instances.

    Was hoping the final solution would not require injecting the commonly needed instances into each inherited controller, only any additional instance objects required for that specific controller. All I really solved from a code repeating aspect was the removal of the private fields in each Controller.

    Still wondering if the BaseController should inherit from Controller or ControllerBase?

    0 讨论(0)
提交回复
热议问题