web-dev-qa-db-de.com

Basiscontroller-Konstruktor-Injektion in ASP.NET-MVC mit Unity

Ich habe in meinem MVC 5-Projekt einen Basiscontroller, der einige gemeinsame Funktionen implementiert. Diese Funktionalität erfordert einige Abhängigkeiten. Ich verwende Unity 3, um diese Implementierungen in meine Controller einzuspeisen. Dieses Muster hat gut funktioniert, bis ich meine Controller von diesem Basis-Controller übernommen habe. Jetzt stoße ich auf folgendes Problem:

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

Dies wirft verständlicherweise einen Fehler von 'BaseController' does not contain a constructor that takes 0 arguments aus. Unity löst den Aufbau des BaseControllers nicht auf, daher können die Abhängigkeiten nicht in ihn eingefügt werden. Ich sehe zwei offensichtliche Wege, um dieses Problem zu lösen:

1.) Rufen Sie explizit den BaseController-Prozessor auf und lassen Sie jeden ChildController-Controller die Abhängigkeiten des BaseControllers einführen

public class ChildController : BaseController
{
    private readonly IDifferentService _differentService;

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

Ich mag diesen Ansatz aus mehreren Gründen nicht: Zum einen, weil die ChildControllers die zusätzlichen Abhängigkeiten nicht nutzen (wodurch die Konstruktoren in den untergeordneten Controllern ohne Grund aufgebläht werden) und was noch wichtiger ist, wenn ich die Konstruktorsignatur ändert des Basiscontrollers muss ich die Konstruktorsignaturen jedes untergeordneten Controllers ändern.

2.) Implementiere die Abhängigkeiten des BaseControllers über die Eigenschaftsinjektion

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

    public BaseController() { }
}

Ich mag diesen Ansatz besser - ich verwende keine der Abhängigkeiten im Konstruktorcode des BaseControllers -, aber er macht die Abhängigkeitsinjektionsstrategie des Codes inkonsistent, was ebenfalls nicht ideal ist.

Es gibt wahrscheinlich einen noch besseren Ansatz, der eine Art BaseController-Abhängigkeitsauflösungsfunktion beinhaltet, die den Unity-Container aufruft, um die Methodensignatur des ctor zu sortieren. Ich habe ein paar Lösungen im Web gefunden, aber es waren Umgehungen wie Service Locator, die ich nicht verwenden möchte.

Vielen Dank!

27
NWard

Das erste, was Sie verstehen müssen, ist, dass Sie den Basiscontroller nicht instanziieren. Sie instanziieren den untergeordneten Controller, der die Basiscontroller-Schnittstelle und -Funktionalität erbt. Dies ist eine wichtige Unterscheidung. Wenn Sie sagen "die ChildControllers machen von den zusätzlichen Abhängigkeiten keinen Gebrauch", dann liegen Sie absolut falsch. Weil der ChildControllerAUCHder BaseController ist. Es werden nicht zwei verschiedene Klassen erstellt. Nur eine Klasse, die beide Funktionen implementiert.

Seit ChildController IS A BaseController gibt es nichts Falsches oder Seltsames an der Übergabe von Parametern im Konstruktor der untergeordneten Controller, der den Konstruktor der Basisklassen aufruft. So sollte es gemacht werden.

Wenn Sie Ihre Basisklasse ändern, müssen Sie wahrscheinlich auch Ihre Kinderklassen ändern. Es ist nicht möglich, die Konstruktorinjektion zum Einfügen von Abhängigkeiten der Basisklasse zu verwenden, die nicht in der untergeordneten Klasse enthalten sind.

Die Eigenschaftsinjektion wird nicht empfohlen, da dies bedeutet, dass Ihre Objekte ohne korrekte Initialisierung erstellt werden können, und Sie müssen daran denken, sie richtig zu konfigurieren.

Übrigens, die richtigen Begriffe sind Unterklasse und Oberklasse. Ein "Kind" ist eine Unterklasse, das Elternteil ist die "Oberklasse".

28

Mit ASP.Net 5 und seiner eingebauten 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)
{}
}

UPDATE

Es gibt auch eine Erweiterungsmethode in Microsoft.Extensions.DependencyInjection, um sie zu verkürzen

SomeMember = serviceProvider.GetRequiredService<ISomeMember>();
11
Vitaly

Ich habe einen etwas anderen (aber für mich ziemlich offensichtlichen und wahrscheinlich üblichen) Ansatz gewählt. Es funktioniert für mich, aber möglicherweise gibt es Fallstricke, die mir nicht bewusst sind.

Ich habe in der BaseController-Klasse allgemeine Diensteigenschaften erstellt, die die meisten meiner Controller verwenden. Sie werden instanziiert, wenn sie benötigt/referenziert werden. 

Wenn ein Dienst einen bestimmten Controller benötigt, der weniger verwendet wird, füge ich ihn wie üblich in den Konstruktor des Controllers ein.

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

In meinem Controller habe ich einfach eine Unterklasse von diesem BaseController:

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

Auf diese Weise werden allgemeine Dienste im Bedarfsfall generiert und stehen meinen Steuerungen ohne viel Boilerplate zur Verfügung.

0
DBatesX