wake-up-neo.com

Abhängigkeitsinjektion mit anderen Klassen als einer Controller-Klasse

Zu diesem Zeitpunkt füge ich problemlos Dinge in meine Controller ein und erstelle in einigen Fällen meine eigene ResolverServices-Klasse. Das Leben ist gut.

Was ich nicht herausfinden kann, wie man es macht, ist das Framework zu bekommen, um es automatisch in Nicht-Controller-Klassen einzufügen. Was funktioniert, ist, dass das Framework automatisch in meinen Controller IOptions eingefügt wird. Dies ist praktisch die Konfiguration für mein Projekt:

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;

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

Ich denke, ich kann dasselbe für meine eigenen Klassen tun und ich gehe davon aus, dass ich nah dran bin, wenn ich den Controller imitiere, wie folgt:

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 Controllerebene darüber spricht. Ich habe versucht, herauszufinden, wo es im Controller -Objektquellcode vorkommt, aber dort wird es irgendwie verrückt.

Ich weiß, dass ich eine Instanz von IOptions manuell erstellen und an den Konstruktor MyHelper übergeben kann, aber es scheint, dass ich in der Lage sein sollte, das Framework dazu zu bringen, dies zu tun, da es für Controllers funktioniert.

Deine Hilfe wird wertgeschätzt.

53
Robert Paulsen

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

Das ShoppingCart-Objekt erhält über DI eine Instanz von INotifier (die den Kunden über seine 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();
        }
    }
}
24
Robert Paulsen

Angenommen, MyHelper wird von MyService verwendet, was wiederum von Ihrem Controller verwendet wird.

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

  • Registrieren sowohl MyService als auch MyHelper in Startup.ConfigureServices.

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

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

    public MyService(MyHelper helper) { ... }
    

Das DI-Framework kann das gesamte Objektdiagramm problemlos auflösen. Wenn Sie befürchten, dass jedes Mal, wenn ein Objekt aufgelöst wird, neue Instanzen erstellt werden, können Sie die verschiedenen Lebensdauer- und Registrierungsoptionen wie Singleton- oder Anforderungslebensdauern nachlesen.

Sie sollten wirklich misstrauisch sein, wenn Sie glauben, eine Instanz eines Dienstes manuell erstellen zu müssen, da Sie möglicherweise im Dienstlokalisierungs-Antimuster landen. Überlassen Sie die Erstellung der Objekte lieber dem DI-Container. Wenn Sie sich wirklich in dieser Situation befinden (sagen wir, Sie erstellen eine abstrakte Factory), können Sie das IServiceProvider direkt verwenden (fordern Sie entweder ein IServiceProvider in Ihrem Konstruktor an oder verwenden Sie das im httpContext verfügbar gemacht ).

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

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

21
Daniel J.G.

Leider gibt es keinen direkten Weg. Die einzige Möglichkeit, wie ich es geschafft habe, besteht darin, eine statische Klasse zu erstellen und diese an anderer Stelle wie folgt zu verwenden:

public static class SiteUtils
{

 public static string AppName { get; set; }

    public static string strConnection { get; set; }

}

Dann fülle es in deiner 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, da dies für den gesamten Lebenszyklus der Anwendung gilt und ich keinen besseren Weg finden könnte, es außerhalb des Controllers zu verwenden.

5
Ali

Hier ist ein vollständigeres Beispiel zur direkten Beantwortung der Frage des OP, basierend auf der aktuellen .NET Core 2.2 DI-Dokumentation hier . Hinzufügen dieser Antwort, da sie möglicherweise jemandem hilft, der .NET Core DI noch nicht kennt, und weil diese Frage das wichtigste Suchergebnis von Google ist.

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

public interface IMyHelper
{
    bool CheckIt();
}

Aktualisieren Sie anschließend 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;
    }
}

Registrieren Sie drittens die Schnittstelle als einen 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
        }
    }
}
0
sfors