web-dev-qa-db-fra.com

Duplication par injection de dépendances dans Controller et BaseController dans .Net Core 2.0

Si je crée un BaseController dans mon application Web Asp.Net Core 2.0 qui résume certaines des dépendances courantes, est-il toujours nécessaire dans les contrôleurs réels.

Par exemple, les contrôleurs de compte et de gestion standard dans une application Web MVC 6 par défaut.

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
}

Dans le modèle d'application Web personnalisé que je construis, je refaçonne le contrôleur de compte en trois contrôleurs différents, RegisterController (qui gère tout ce qui concerne l'enregistrement d'un utilisateur), LoginController (qui gère la connexion et la déconnexion) et le solde en un troisième. J'ai divisé le contrôleur de gestion en deux, un ManagePasswordController (tout ce qui concerne les mots de passe) et un UserManageController (tout le reste).

Il y a beaucoup de points communs dans les exigences DI pour chacun et je veux les mettre dans un BaseController. Pour ressembler à quelque chose comme ça?

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
}

Mais il semble que cela ne fasse rien? car il me semble que je dois encore tout injecter. Je ne peux pas avoir raison (je suis nouveau sur DI, donc je n'ai clairement aucun indice) mais le BaseController devrait me permettre de ne faire aucun DI commun entre BaseController et RegisterController. Ai-je tort? Comment puis-je accomplir ce que j'essaie de faire?

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
}

Mise à jour

Selon la suggestion de 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; 
    }
}

Et le contrôleur héritant

public class TestBaseController : BaseController
{

    public TestBaseController() : base()
    {

    }
}

Ça ne marche pas. Resharper me dit que je dois ajouter les paramètres à l'appel du constructeur de base dans le constructeur TestBaseController.

BaseController doit-il également hériter de Controller ou ControllerBase dans .Net Core 2.0?

12
dinotom

Il y a très peu de bonnes raisons d'utiliser un BaseController dans MVC . Un contrôleur de base dans ce scénario ajoute uniquement plus de code à maintenir, sans réel avantage.

Pour les vrais problèmes transversaux , la façon la plus courante de les gérer dans MVC est d'utiliser filtres globaux , bien qu'il existe quelques nouvelles options à considérer dans le cœur MVC.

Cependant, votre problème ne ressemble pas tant à une préoccupation transversale qu'à une violation du principe de responsabilité unique . Autrement dit, avoir plus de 3 dépendances injectées est une odeur de code que votre contrôleur fait trop. La solution la plus pratique serait de Refactor to Aggregate Services .

Dans ce cas, je dirais que vous avez au moins 1 service implicite que vous devez rendre explicite - à savoir, UserManager et SignInManager devraient être enveloppés dans un service qui lui est propre. À partir de là, vous pourriez potentiellement injecter vos 3 autres dépendances dans ce service (en fonction de la façon dont elles sont utilisées, bien sûr). Ainsi, vous pouvez potentiellement réduire cela à une seule dépendance à la fois pour AccountController et ManageController.

Quelques signes qu'un contrôleur en fait trop:

  1. Il existe de nombreuses méthodes "d'assistance" qui contiennent une logique métier partagée entre les actions.
  2. Les méthodes d'action font plus que de simples requêtes/réponses HTTP. Une méthode d'action ne doit généralement rien faire d'autre que d'appeler des services qui traitent des entrées et/ou produisent des sorties et renvoient des vues et des codes de réponse.

Dans de tels cas, il vaut la peine de voir si vous pouvez déplacer cette logique dans un service qui leur est propre et n'importe quelle logique partagée dans les dépendances de ce service, etc.

7
NightOwl888

La classe Microsoft.AspNetCore.MVC.Controller est fournie avec la méthode d'extension

HttpContext.RequestServices.GetService<T>

Qui peut être utilisé chaque fois que HttpContext est disponible dans le pipeline (par exemple, la propriété HttpContext sera Null si elle est appelée depuis le constructeur du contrôleur)

Essayez ce modèle

Remarque: assurez-vous d'inclure cette directive en utilisant Microsoft.Extensions.DependencyInjection;

Contrôleur de base

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

contrôleur enfant

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

Inutile de dire, n'oubliez pas d'enregistrer vos services dans la méthode Startup.cs -> ConfingureServices

13
Francisco Vilches

Selon les suggestions de Calc et de Sir Rufo, cela fonctionne.

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

Les paramètres doivent encore être injectés dans le contrôleur hérité et passés au constructeur de base

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

Alors maintenant, tous les objets injectés auront des instances.

J'espérais que la solution finale ne nécessiterait pas d'injecter les instances couramment nécessaires dans chaque contrôleur hérité, uniquement tous les objets d'instance supplémentaires requis pour ce contrôleur spécifique. Tout ce que j'ai vraiment résolu d'un aspect répétitif du code était la suppression des champs privés dans chaque contrôleur.

Vous vous demandez toujours si le BaseController devrait hériter de Controller ou ControllerBase?

2
dinotom