Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

For Example, the standard Account and Manage controllers in a default MVC 6 web application.

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
}

In the custom web application template I am building I refactor the Account Controller to three different Controllers, RegisterController(Which handles everything regarding a user registration), LoginController(which handles login and logout), and the balance into a third. I split the Manage Controller in two, a ManagePasswordController(everything related to passwords) and a UserManageController(everything else).

There is a lot of commonality in the DI requirements for each and I want to put them in a BaseController. To look something like this?

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
}

But it seems like that accomplishes nothing? because it seems to me I still have to inject everything. I can't be right (I'm new to DI so clearly have no clue) but the BaseController should allow me to do NO DI that's common between BaseController and RegisterController. Am I wrong? How do I accomplish what I am trying to do?

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
}

Update

Per Sir Rufo's suggestion

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

And the inheriting controller

public class TestBaseController : BaseController
{

    public TestBaseController() : base()
    {

    }
}

This doesn't work. Resharper is telling me I have to add the parameters to the base constructor call in the TestBaseController constructor.

Also should BaseController be inheriting from Controller or ControllerBase in .Net Core 2.0?

like image 574
dinotom Avatar asked Nov 18 '17 22:11

dinotom


People also ask

What is difference between AddTransient and AddScoped?

AddTransient() - This method creates a Transient service. A new instance of a Transient service is created each time it is requested. AddScoped() - This method creates a Scoped service. A new instance of a Scoped service is created once per request within the scope.

Does .NET Core have dependency injection?

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. For more information specific to dependency injection within MVC controllers, see Dependency injection into controllers in ASP.NET Core.

How can we inject the service dependency into the controller?

How can we inject the service dependency into the controller C# Asp.net Core? ASP.NET Core injects objects of dependency classes through constructor or method by using built-in IoC container. The built-in container is represented by IServiceProvider implementation that supports constructor injection by default.

How does dependency injection work .NET Core?

Dependency Injection is the design pattern that helps us to create an application which loosely coupled. This means that objects should only have those dependencies that are required to complete tasks.


1 Answers

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

like image 183
Francisco Vilches Avatar answered Nov 05 '22 04:11

Francisco Vilches