Injection du constructeur du régulateur de Base ASP.NET MVC avec unité

j'ai un contrôleur de base dans mon projet MVC 5 qui implémente certaines fonctionnalités partagées. Cette fonctionnalité nécessite certaines dépendances. J'utilise Unity 3 pour injecter ces implémentations dans mes controllers, un pattern qui a bien fonctionné jusqu'à ce que je commute mes controllers pour hériter de ce controller de base. Maintenant, je suis en cours d'exécution dans le problème suivant:

public class BaseController : Controller
{
    private readonly IService _service;

    public BaseController(IService service)
    {
        _service = service;
    }
}

public class ChildController : BaseController
{
    private readonly IDifferentService _differentService;

    public ChildController(IDifferentService differentService)
    {
        _differentService = differentService;
    }
}

c'est naturellement lancer une erreur de 'BaseController' does not contain a constructor that takes 0 arguments. L'unité ne résout pas la construction du BaseController, donc il ne peut pas injecter les dépendances. Je vois deux façons évidentes de résoudre ce problème:

1.) Appelez explicitement le contrôleur de BaseController ctor et demandez à chaque contrôleur de ChildController ctor d'injecter les dépendances du contrôleur de BaseController

public class ChildController : BaseController
{
    private readonly IDifferentService _differentService;

    public ChildController(IDifferentService differentService,
                           IService baseService)
        : base(baseService)
    {
        _differentService = differentService;
    }
}

Je n'aime pas cette approche pour plusieurs raisons: d'une part, parce que les ChildControllers n'utilisent pas les dépendances supplémentaires (de sorte qu'il provoque le bloat du constructeur dans les contrôleurs enfants sans raison), et plus important encore, si jamais je change la signature du constructeur du contrôleur de Base, je dois changer les signatures du constructeur de chaque contrôleur enfant.

2.) Implémenter les dépendances du contrôleur de base via l'injection des propriétés

public class BaseController : Controller
{
    [Dependency]
    public IService Service { get; set; }

    public BaseController() { }
}

j'aime mieux cette approche - je n'utilise aucune des dépendances dans le code du constructeur du contrôleur de base - mais cela rend la stratégie d'injection de dépendances du code incohérente, ce qui n'est pas idéal soit.

il y a probablement une approche encore meilleure qui implique une sorte de fonction de résolution de dépendance de BaseController qui fait appel au conteneur unité pour trier la signature de la méthode ctor, mais avant que j'écrive quelque chose de trop impliqué, je me demandais si quelqu'un avait résolu ce problème auparavant? J'ai trouvé quelques solutions sur le web, mais il s'agissait de solutions de rechange comme le Localisateur de Services que je ne veux pas utiliser.

Merci!

27
demandé sur NWard 2014-02-20 22:05:07

3 réponses

la première chose que vous devez comprendre est que vous n'instanciez pas le contrôleur de base. Vous instanciez le contrôleur enfant, qui hérite de l'interface et de la fonctionnalité des contrôleurs de base. C'est une distinction importante. Quand vous dites "les ChildControllers n'utilisent pas les dépendances supplémentaires", alors vous vous trompez complètement. Parce que le ChildController le BaseController. Il n'y a pas deux classes différentes créées. Juste un la classe qui implémente les fonctionnalités.

donc, puisque ChildController EST UN BaseController, il n'y a rien de mal ou d'étrange à passer des paramètres dans le constructeur des contrôleurs enfants qui appelle le constructeur des classes de base. C'est la façon dont il devrait être fait.

si vous changez de classe de base, vous devrez probablement changer de classe de toute façon. Il n'y a aucun moyen d'utiliser l'injection de constructeur pour injecter des dépendances de classe de base qui ne sont pas inclus dans la classe enfant.

Je ne recommande pas l'injection de propriété, car cela signifie que vos objets peuvent être créés sans initialisation appropriée, et vous devez vous rappeler de les configurer correctement.

BTW, les termes sont sous-classe de super-classe. Un " enfant "est une sous-classe, le parent est la"superclasse".

28
répondu Erik Funkenbusch 2014-07-04 17:49:30
public class BaseController : Controller
{
    protected ISomeType SomeMember { get; set; }

    public BaseController(IServiceProvider serviceProvider)
    {
        //Init all properties you need
        SomeMember = (SomeMember)serviceProvider.GetService(typeof(ISomeMember));
    }
}

public class MyController : BaseController  
{
public MyController(/*Any other injections goes here*/, 
                      IServiceProvider serviceProvider) : 
 base(serviceProvider)
{}
}

UPDATE

SomeMember = serviceProvider.GetRequiredService<ISomeMember>();
8
répondu Vitaly 2016-01-26 16:20:35

j'ai adopté une approche quelque peu différente (mais pour moi, assez évidente et probablement commune). Ça marche pour moi, mais il y a peut-être des pièges que je ne connais pas.

j'ai créé des propriétés de service commun dans ma classe BaseController que la plupart de mes contrôleurs utilisent. Ils sont instanciés quand ils sont nécessaires/référencés.

S'il y a un service dont un controller particulier a besoin et qui est moins utilisé, Je l'injecte dans le constructeur de ce controller comme normal.

using JIS.Context;
using JIS.Managers;
using JIS.Models;
using JIS.Services;
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;

namespace JIS.Controllers
{
    public class BaseController : Controller
    {
        public BaseController()
        {
            ViewBag.User = UserManager?.User;
        }

        private IUserManager userManager;
        public IUserManager UserManager
        {
            get
            {
                if (userManager == null)
                {
                    userManager = DependencyResolver.Current.GetService<IUserManager>();
                }
                return userManager;
            }
            set
            {
                userManager = value;
            }
        }

        private ILoggingService loggingService;
        public ILoggingService LoggingService
        {
            get
            {
                if (loggingService == null)
                {
                    loggingService = DependencyResolver.Current.GetService<ILoggingService>();
                }
                return loggingService;
            }
            set { loggingService = value; }
        }

        private IUserDirectory userDirectory;
        public IUserDirectory UserDirectory
        {
            get
            {
                if (userDirectory == null)
                {
                    userDirectory = DependencyResolver.Current.GetService<IUserDirectory>();
                }
                return userDirectory;
            }
            set { userDirectory = value; }
        }

        private ApplicationDbContext appDb;
        public ApplicationDbContext AppDb
        {
            get
            {
                if (appDb == null)
                {
                    appDb = new ApplicationDbContext();
                }
                return appDb;
            }
            set
            {
                appDb = value;
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && appDb != null)
            {
                appDb.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

dans mon contrôleur je classe simplement ce BaseController:

public class UsersController : BaseController
{
    // and any other services I need are here:
    private readonly IAnotherService svc;
    public UsersController(IAnotherService svc)
    {
        this.svc = svc;
    }
...
}

de cette façon, les services communs sont générés à la volée quand ils sont nécessaires, et sont disponibles pour mes contrôleurs sans beaucoup de boilerplate.

0
répondu DBatesX 2017-11-29 19:16:52