Abhängigkeitsinjektion mit anderen Klassen als einer Controller-Klasse

80

An diesem Punkt füge ich mühelos Dinge in meine Controller ein und baue in einigen Fällen meine eigene ResolverServices-Klasse auf. Das Leben ist gut .

Was ich nicht herausfinden kann, ist, das Framework so zu gestalten, dass es automatisch in Nicht-Controller-Klassen eingefügt wird. Was funktioniert, ist, dass das Framework automatisch in meinen Controller eingefügt wird. Dies IOptionsist effektiv die Konfiguration für mein Projekt:

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;

    public MessageCenterController(IOptions<MyOptions> options)
    {
        _options = options.Value;
    }
}

Ich überlege, ob ich das auch für meine eigenen Klassen tun kann. Ich gehe davon aus, dass ich nah dran bin, wenn ich den Controller nachahme:

public class MyHelper
{
    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    {
        _options = options.Value;
    }

    public bool CheckIt()
    {
        return _options.SomeBoolValue;
    }
}

Ich denke, wo ich versage, ist, wenn ich es so nenne:

public void DoSomething()
{
    var helper = new MyHelper(??????);

    if (helper.CheckIt())
    {
        // Do Something
    }
}

Das Problem, das ich habe, ist, dass praktisch alles, was über DI spricht, auf Controller-Ebene darüber spricht. Ich habe versucht herauszufinden, wo es im ControllerObjektquellcode passiert , aber es wird dort irgendwie verrückt.

Ich weiß, dass ich eine Instanz von IOptions manuell erstellen und an den MyHelperKonstruktor übergeben kann, aber es scheint, als ob ich in der Lage sein sollte, das Framework dazu zu bringen, da es funktioniert Controllers.

Robert Paulsen
quelle
8
Wenn Sie die Abhängigkeitsinjektion verwenden, rufen Sie nicht an new. Niemals für Objekte, die aufgelöst werden sollten
Tseng
1
Wenn ich versuche, eine Instanz von MyHelper zu erstellen, rufe ich nicht neu auf? (1) Das klingt zu einfach. (2) Es ist ein Syntaxfehler. :-)
Robert Paulsen
2
Ja, das ist der springende Punkt bei der Abhängigkeitsinjektion (insbesondere bei Verwendung der Inversion von Steuercontainern, die diese Instanziierung verwalten und ausführen). So verschieben Sie die Instanz außerhalb Ihrer Dienste / Klassen bis zu dem Punkt, an dem der ioc-Container dies intern ausführt. In Fällen, in denen Sie es nicht über den Konstruktor injizieren können, erstellen Sie eine Factory und übergeben die Factory-Schnittstelle an Ihren Service. die Umsetzung verwendet es den Behälter um es zu lösen, in ASP.NET Core - Fall Injektion IServiceProviderin Ihrem Betrieb und ruftIMyHelper helper = services.RequestService<IMyHelper>()
Tseng

Antworten:

40

Im Folgenden finden Sie ein funktionierendes Beispiel für die Verwendung von DI ohne MVC-Controller. Dies ist, was ich tun musste, um den Prozess zu verstehen, also hilft es vielleicht jemand anderem.

Das ShoppingCart-Objekt erhält über DI eine Instanz von INotifier (die den Kunden über ihre Bestellung benachrichtigt).

using Microsoft.Extensions.DependencyInjection;
using System;

namespace DiSample
{
    // STEP 1: Define an interface.
    /// <summary>
    /// Defines how a user is notified. 
    /// </summary>
    public interface INotifier
    {
        void Send(string from, string to, string subject, string body);
    }

    // STEP 2: Implement the interface
    /// <summary>
    /// Implementation of INotifier that notifies users by email.
    /// </summary>
    public class EmailNotifier : INotifier
    {
        public void Send(string from, string to, string subject, string body)
        {
            // TODO: Connect to something that will send an email.
        }
    }

    // STEP 3: Create a class that requires an implementation of the interface.
    public class ShoppingCart
    {
        INotifier _notifier;

        public ShoppingCart(INotifier notifier)
        {
            _notifier = notifier;
        }

        public void PlaceOrder(string customerEmail, string orderInfo)
        {
            _notifier.Send("[email protected]", customerEmail, $"Order Placed", $"Thank you for your order of {orderInfo}");
        }

    }

    public class Program
    {
        // STEP 4: Create console app to setup DI
        static void Main(string[] args)
        {
            // create service collection
            var serviceCollection = new ServiceCollection();

            // ConfigureServices(serviceCollection)
            serviceCollection.AddTransient<INotifier, EmailNotifier>();

            // create service provider
            var serviceProvider = serviceCollection.BuildServiceProvider();

            // This is where DI magic happens:
            var myCart = ActivatorUtilities.CreateInstance<ShoppingCart>(serviceProvider);

            myCart.PlaceOrder("[email protected]", "2 Widgets");

            System.Console.Write("Press any key to end.");
            System.Console.ReadLine();
        }
    }
}
Robert Paulsen
quelle
16
Was ist, wenn ich ShoppingCartin einer anderen Klasse oder Methode instanziieren möchte, dass wir nicht auf das serviceProviderObjekt zugreifen ?
Bagherani
1
mit dem gleichen Problem hier
Casey
4
Vielen Dank, ich musste viel zu weit suchen, um zu ActivatorUtilities.CreateInstance zu gelangen.
H. Tugkan Kibar
1
Was ist, wenn ich keinen ServiceProvider habe? !!
HelloWorld
1
Vielen Dank! Ich verwende es mit Microsoft.AspNetCore.TestHost, erstelle einen TestServer und rufe ActivatorUtilities.CreateInstance <MyCustomerController> (_server.Host.Services) auf.
Tolga
34

Angenommen, es MyHelperwird verwendet, von MyServicedem wiederum Ihr Controller verwendet wird.

Der Weg, um diese Situation zu lösen, ist:

  • Registrieren Sie sich beide MyServiceund MyHelperin Startup.ConfigureServices.

    services.AddTransient<MyService>();
    services.AddTransient<MyHelper>();
    
  • Der Controller empfängt eine Instanz von MyServicein seinem Konstruktor.

    public HomeController(MyService service) { ... }
    
  • MyServiceDer Konstruktor erhält wiederum eine Instanz von MyHelper.

    public MyService(MyHelper helper) { ... }
    

Das DI-Framework kann den gesamten Objektgraphen problemlos auflösen. Wenn Sie befürchten, dass bei jeder Auflösung eines Objekts neue Instanzen erstellt werden, können Sie sich über die verschiedenen Lebensdauer- und Registrierungsoptionen wie Singleton oder Anforderungslebensdauer informieren .

Sie sollten wirklich misstrauisch sein, wenn Sie der Meinung sind, dass Sie eine Instanz eines Dienstes manuell erstellen müssen, da Sie möglicherweise im Anti-Pattern des Service Locator landen . Überlassen Sie das Erstellen der Objekte besser dem DI-Container. Wenn Sie sich wirklich in dieser Situation befinden (sagen wir, Sie erstellen eine abstrakte Factory), können Sie die IServiceProviderdirekt verwenden (entweder eine IServiceProviderin Ihrem Konstruktor anfordern oder die im httpContext bereitgestellte verwenden ).

var foo = serviceProvider.GetRequiredService<MyHelper>();

Ich würde empfehlen, die spezifische Dokumentation zum ASP.Net 5 DI-Framework und zur Abhängigkeitsinjektion im Allgemeinen zu lesen .

Daniel JG
quelle
Das Problem dabei ist, dass ich Hintergrunddienste verwende, die mit der Datenbank interagieren, sodass die Lebensdauer mit dbcontext nicht funktioniert, oder? Wie verwenden Sie DI richtig mit EF-Kern- und Hintergrunddiensten?
6

Leider gibt es keinen direkten Weg. Die einzige Möglichkeit, es zum Laufen zu bringen, besteht darin, eine statische Klasse zu erstellen und diese überall wie folgt zu verwenden:

public static class SiteUtils
{

 public static string AppName { get; set; }

    public static string strConnection { get; set; }

}

Füllen Sie es dann in Ihrer Startklasse wie folgt aus:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //normal as detauls , removed for space 
    // set my variables all over the site

    SiteUtils.strConnection = Configuration.GetConnectionString("DefaultConnection");
    SiteUtils.AppName = Configuration.GetValue<string>("AppName");
}

Obwohl dies ein schlechtes Muster ist, bleibt dies für den gesamten Lebenszyklus der Anwendung bestehen und ich konnte keinen besseren Weg finden, es außerhalb des Controllers zu verwenden.

Ali
quelle
4

Hier ist ein vollständigeres Beispiel, um die Frage des OP direkt zu beantworten, basierend auf der aktuellen .NET Core 2.2 DI-Dokumentation hier . Hinzufügen dieser Antwort, da sie möglicherweise jemandem hilft, der neu in .NET Core DI ist, und weil diese Frage das beste Suchergebnis von Google ist.

Fügen Sie zunächst eine Schnittstelle für MyHelper hinzu:

public interface IMyHelper
{
    bool CheckIt();
}

Aktualisieren Sie zweitens die MyHelper-Klasse, um die Schnittstelle zu implementieren (drücken Sie in Visual Studio Strg-, um die Schnittstelle zu implementieren):

public class MyHelper : IMyHelper
{
    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    {
        _options = options.Value;
    {

    public bool CheckIt()
    {
        return _options.SomeBoolValue;
    }
}

Drittens registrieren Sie die Schnittstelle als vom Framework bereitgestellten Dienst im DI-Dienstcontainer. Registrieren Sie dazu den IMyHelper-Dienst mit dem konkreten Typ MyHelper in der ConfigureServices-Methode in Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<IMyHelper, MyHelper>();
    ...
}

Viertens erstellen Sie eine private Variable, um auf eine Instanz des Dienstes zu verweisen. Übergeben Sie den Dienst als Argument im Konstruktor (über die Konstruktorinjektion) und initialisieren Sie die Variable mit der Dienstinstanz. Verweisen Sie über die private Variable auf Eigenschaften oder Aufrufmethoden für diese Instanz der benutzerdefinierten Klasse.

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;
    private readonly IMyHelper _myHelper;

    public MessageCenterController(
        IOptions<MyOptions> options,
        IMyHelper myHelper
    )
    {
        _options = options.value;
        _myHelper = myHelper;
    }

    public void DoSomething()
    {
        if (_myHelper.CheckIt())
        {
            // Do Something
        }
    }
}
sfors sagt, Monica wieder einzusetzen
quelle
0

Sie können Activator.CreateInstance () verwenden. Hier ist eine Wrapper-Funktion dafür. Sie verwenden dies wie folgt.

var determiniertProgrammatisch = "My.NameSpace.DemoClass1"; // implementiert die IDemo-Schnittstelle var obj = CreateInstance <My.NameSpace.IDemo, string> (bestimmt programmgesteuert: "Dies geht in den Parameter des Konstruktors.", "Lassen Sie diesen Parameter weg, wenn Ihre Klasse in der aktuellen Assembly lebt");

Jetzt haben Sie eine Instanz von obj, die anhand des programmgesteuert bestimmten Typs instanziiert wird. Dieses Objekt kann in Nicht-Controller-Klassen eingefügt werden.

public TInterface CreateInstance<TInterface, TParameter>(string typeName, TParameter constructorParam, string dllName = null)
{
    var type = dllName == null ? System.Type.GetType(typeName) :
            System.AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.StartsWith(dllName, System.StringComparison.OrdinalIgnoreCase)).GetType(typeName);
    return (TInterface)System.Activator.CreateInstance(type, constructorParam);

}

PS: Sie können System.AppDomain.CurrentDomain.GetAssemblies () durchlaufen, um den Namen der Assembly zu ermitteln, in der sich Ihre Klasse befindet. Dieser Name wird im 3. Parameter der Wrapper-Funktion verwendet.

Denny Jacob
quelle