Controller di Base costruttore di iniezione in ASP.NET MVC con l’Unità

Ho un controller di base nel mio MVC 5 progetto che implementa alcune funzionalità condivise. Questa funzionalità richiede un po ‘ di dipendenze. Sto usando 3 Unità per iniettare queste implementazioni nel mio controller, un modello che ha funzionato bene fino a quando ho acceso il mio controller per ereditare da questa base di controller. Ora io sono in esecuzione nel seguente problema:

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

Questo è, comprensibilmente, generando un errore di 'BaseController' does not contain a constructor that takes 0 arguments. L’unità non è risolvere la costruzione del BaseController, in modo che si può iniettare le dipendenze in esso. Vedo due modi ovvi per risolvere questo problema:

1.) Chiamare in modo esplicito il BaseController ctor e ogni ChildController ctor iniettare il BaseController dipendenze

public class ChildController : BaseController
{
    private readonly IDifferentService _differentService;

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

Non mi piace questo approccio per diversi motivi: uno, perché il ChildControllers non stanno facendo uso delle dipendenze aggiuntive (in questo modo le cause costruttore gonfiare il bambino controller per nessun motivo), e, cosa più importante, se mai dovessi cambiare la firma del costruttore del Controller di Base, devo cambiare il costruttore firme di ogni bambino controller.

2.) Implementare il BaseController dipendenze tramite iniezione di proprietà

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

    public BaseController() { }
}

Mi piace questo approccio migliore, non sto usando una qualsiasi delle dipendenze nel BaseController del codice del costruttore – ma rende l’iniezione di dipendenza strategia del codice incoerente, che non è ideale.

Probabilmente c’è anche un migliore approccio che prevede una qualche forma di BaseController dipendenza della funzione di risoluzione che invita la Unità contenitore per risolvere il costruttore della firma del metodo, ma prima di entrare in scrivere qualcosa di troppo coinvolto, mi chiedevo se qualcuno aveva risolto questo problema prima? Ho trovato un paio di soluzioni galleggianti intorno sul web, ma erano soluzioni alternative come il Servizio Locator che non voglio utilizzare.

Grazie!

InformationsquelleAutor NWard | 2014-02-20



3 Replies
  1. 29

    La prima cosa che dovete capire è che non si istanzia il controller di base. Si sta creando il bambino controller, che eredita la base controller di interfaccia e funzionalità. Questa è una distinzione importante. Quando si dice “il ChildControllers non stanno facendo uso delle dipendenze aggiuntive”, allora siete assolutamente sbagliato. Perché il ChildController È il BaseController così. Non ci sono due classi diverse creato. Solo una classe che implementa funzionalità.

    Così, dal momento che ChildController È UN BaseController, non c’è nulla di sbagliato o di strano passaggio di parametri nel bambino controller costruttore che chiama le classi base costruttore. Questo è il modo in cui dovrebbe essere fatto.

    Se si modifica la classe base, è probabile che sia necessario modificare il vostro bambino classi comunque. Non c’è modo di utilizzare l’iniezione del costruttore per iniettare classe base dipendenze che non sono inclusi nella classe figlia.

    Io non consiglio di iniezione di proprietà, in quanto questo significa che i tuoi oggetti possono essere creati senza la corretta inizializzazione, e bisogna ricordarsi di configurare correttamente.

    BTW, i termini corretti sono Sottoclasse e Superclasse. “Un bambino” è una sottoclasse, il genitore è la “superclasse”.

    • Hai ragione! Ho aggiunto un controller di base come una soluzione al bisogno di funzionalità condivise, senza considerare le OO implicazioni. Penso a quello che devo fare è effettuare il refactoring un po ‘ per assicurarsi che le dipendenze di base sono bella e ordinata, e passare a quelli dei bambini costruttori come suggerisci tu. Grazie Eric!
    • Forse questa è la risposta giusta qui, ma è, purtroppo, che, mentre mi può racchiudere l’inizializzazione del codice all’interno della classe base, ogni classe derivata deve attuare la corretta costruttore. Devo ricordare a digitare in un costruttore stesso per ogni classe derivata. E qualsiasi modifica i dati che sto passando al costruttore richiede la modifica di ogni classe che deriva da esso. Sarebbe molto meglio per essere in grado di mantenere questo solo a partire da una classe base.
    • Si tratta di un argomento non particolarmente convincenti, dal momento che i costruttori sono lì per un motivo… per inizializzare l’oggetto con i dati specifici. Se si dimentica di includere il diritto costruttore, quindi, il compito che si vuole realizzare non funziona perché non ci sarà un costruttore che è necessario e si nota subito il tuo errore. E ‘ come lamentarsi che è necessario “ricordare” per aggiungere le proprietà di una classe… non andare molto lontano, se non si ricorda.
    • Io non credo di essere stato di litigare per una cosa o un’altra. Stavo sottolineando quello che vedo come alcune limitazioni di questo approccio. Uno dei grandi punti di programmazione OOP, è l’incapsulamento. E credo che alcuni di questo è stato perso con questo approccio, e ho cercato di descrivere alcuni dei temi che seguono, come un risultato.
    • quello che si chiama un “approccio” è il modo in cui tutti i linguaggi ad oggetti che sono a conoscenza del lavoro, e dal momento che questo è.. per definizione “l’approccio object oriented”, non ho ben capito cosa vuoi dire. A mia conoscenza, tutti i linguaggi ad oggetti che implementano l’ereditarietà sono implementati in un modo in cui la classe derivata è l’unica istanza dell’oggetto, e quindi non ci sono alternate le istanze che qualcosa come l’Unità o qualsiasi altro contenitore DI potrebbe inserire un valore in se la classe derivata stesso non implementa tale costruttore.
    • Io credo che quello che sembra suggerire è che i costruttori della classe base deve essere esposta per impostazione predefinita, il che è davvero una cosa molto diversa da quello che il poster originale era per chiedere. Se è quello che vuoi dire, allora ti suggerisco di leggere uno dei tanti articoli là fuori sul perché i costruttori non vengono ereditate in C++, Java e altre lingue (tra cui C#). FWIW, C++-11 ha aggiunto una funzione che consente al compilatore di creare automaticamente la corrispondenza costruttori delle classi base, ma questo non è proprio la stessa cosa.
    • Senso +1..

  2. 13

    Con ASP.Net 5 e è costruito DI

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

    AGGIORNAMENTO

    C’è anche un metodo di estensione in Microsoft.Le estensioni.DependencyInjection per renderlo più breve

    SomeMember = serviceProvider.GetRequiredService<ISomeMember>();
    • Questa è una grande aggiunta. E ‘ possibile implementare un controller di base, senza DI in ASP.NET 4.5?
    • Non so cosa intendi. Si potrebbe utilizzare Fabbrica+Singleton invece DI in BaseController.
    • sì, disprezzo. Ho lasciato spazio dei nomi Controller. Questo progetto a cui sto lavorando non dispone DI implimented.
    • Bene, va bene con MVC 6
    • si potrebbe fornire un ulteriore esempio? Ho affrontato il controller deve avere senza parametri pubblico ctor di errore.
    • si utilizza una versione diversa di MVC MVC (4\5, WebAPI, ecc), o l’uso non di default DI contenitore. L’esempio è per ASP.Net Core MVC 6, che è stato chiamato ASP.Net 5 al momento ho aggiunto la mia risposta. In ASP.Net Core si hanno raramente costruttore senza parametri e non c’è alcuna restrizione esiste
    • MVC 5 è utilizzato e MS DI. Io ho risolto il mio problema aggiungendo public HomeController() : this(DependencyInjectionResolver.ServiceProvider) { } ecc
    • invece si potrebbe utilizzare DependencyInjectionResolver.ServiceProvider o creare una proprietà in base controller che restituisce direttamente. Non ha senso che il passaggio di proprietà statica nel costruttore della classe base

  3. 0

    Ho preso un po ‘ diverso (ma, per me, abbastanza evidente e, probabilmente, comune) approccio. A me funziona, ma ci possono essere le insidie non sono a conoscenza di.

    Ho creato il comune di servizio di proprietà a mio BaseController classe che la maggior parte del mio utilizzo di controller. Essi ottenere istanziato quando sono necessari/a cui fa riferimento.

    Se c’è un servizio di un particolare controller che è meno usato, mi inietta il controller del costruttore, come normale.

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

    Nel mio controller ho semplicemente sottoclasse da questo BaseController:

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

    In questo modo, i servizi comuni sono generate in tempo reale quando sono necessari, e sono disponibili per il mio controller senza un sacco di boilerplate.

    • Questo è utilizzando il Localizzatore Servizio modello che è generalmente considerato un anti-pattern.

Lascia un commento