Auflösen von Instanzen mit ASP.NET Core DI

302

Wie löse ich einen Typ manuell mithilfe des in ASP.NET Core MVC integrierten Frameworks für die Abhängigkeitsinjektion auf?

Das Aufstellen des Containers ist einfach genug:

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddTransient<ISomeService, SomeConcreteService>();
}

Aber wie kann ich das Problem beheben, ISomeServiceohne eine Injektion durchzuführen? Zum Beispiel möchte ich dies tun:

ISomeService service = services.Resolve<ISomeService>();

Es gibt keine solchen Methoden in IServiceCollection.

Dave New
quelle
1
Mögliches Duplikat von So lösen Sie Instanzen in ConfigureServices in ASP.NET 5
Muhammad Rehan Saeed
3
Möchten Sie sie in der ConfigureServices()Methode (mit IServiceCollection) oder irgendwo in der Anwendung auflösen ?
Henk Mollema
2
@HenkMollema: Eigentlich überall im Startup.
Dave New

Antworten:

485

Die IServiceCollectionSchnittstelle wird zum Erstellen eines Abhängigkeitsinjektionscontainers verwendet. Nachdem es vollständig erstellt wurde, wird es zu einer IServiceProviderInstanz zusammengesetzt, mit der Sie Dienste auflösen können. Sie können eine IServiceProviderin jede Klasse injizieren . Die Klassen IApplicationBuilderund HttpContextkönnen den Dienstanbieter auch über ihre ApplicationServicesbzw. RequestServicesEigenschaften bereitstellen .

IServiceProviderdefiniert eine GetService(Type type)Methode zum Auflösen eines Dienstes:

var service = (IFooService)serviceProvider.GetService(typeof(IFooService));

Es stehen auch verschiedene praktische Erweiterungsmethoden zur Verfügung, z. B. serviceProvider.GetService<IFooService>()(ein usingfür hinzufügen Microsoft.Extensions.DependencyInjection).

Auflösen von Diensten innerhalb der Startklasse

Abhängigkeiten einfügen

Die Laufzeit der Hosting - Service - Provider können bestimmte Dienste in den Konstruktor der injizieren StartupKlasse, wie IConfiguration, IWebHostEnvironment( IHostingEnvironmentin Pre-3.0 - Versionen), ILoggerFactoryund IServiceProvider. Beachten Sie, dass letztere eine von der Hosting-Schicht erstellte Instanz ist und nur die wesentlichen Dienste zum Starten einer Anwendung enthält .

Die ConfigureServices()Methode erlaubt kein Injizieren von Diensten, sondern akzeptiert nur ein IServiceCollectionArgument. Dies ist sinnvoll, da ConfigureServices()Sie hier die für Ihre Anwendung erforderlichen Dienste registrieren. Sie können hier jedoch Dienste verwenden, die in den Konstruktor des Startups eingefügt wurden, zum Beispiel:

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    // Use Configuration here
}

Alle in registrierten Dienste ConfigureServices()können dann in die Configure()Methode eingefügt werden. Sie können nach dem IApplicationBuilderParameter eine beliebige Anzahl von Diensten hinzufügen :

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IFooService>();
}

public void Configure(IApplicationBuilder app, IFooService fooService)
{
    fooService.Bar();
}

Abhängigkeiten manuell auflösen

Wenn Sie manuell lösen Dienste benötigen, sollten Sie vorzugsweise die Verwendung der ApplicationServicesvon einer IApplicationBuilderin dem Configure()Verfahren:

public void Configure(IApplicationBuilder app)
{
    var serviceProvider = app.ApplicationServices;
    var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}

Es ist möglich, ein IServiceProviderim Konstruktor Ihrer StartupKlasse zu übergeben und direkt zu verwenden , aber wie oben enthält dieses eine begrenzte Teilmenge von Diensten und hat daher einen begrenzten Nutzen:

public Startup(IServiceProvider serviceProvider)
{
    var hostingEnv = serviceProvider.GetService<IWebHostEnvironment>();
}

Wenn Sie Dienste in der ConfigureServices()Methode auflösen müssen , ist ein anderer Ansatz erforderlich. Sie können IServiceProvideraus der IServiceCollectionInstanz ein Zwischenprodukt erstellen, das die bis zu diesem Zeitpunkt registrierten Dienste enthält :

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IFooService, FooService>();

    // Build the intermediate service provider
    var sp = services.BuildServiceProvider();

    // This will succeed.
    var fooService = sp.GetService<IFooService>();
    // This will fail (return null), as IBarService hasn't been registered yet.
    var barService = sp.GetService<IBarService>();
}

Bitte beachten Sie: Im Allgemeinen sollten Sie das Auflösen von Diensten innerhalb der ConfigureServices()Methode vermeiden , da dies tatsächlich der Ort ist, an dem Sie die Anwendungsdienste konfigurieren . Manchmal benötigen Sie nur Zugriff auf eine IOptions<MyOptions>Instanz. Sie können dies erreichen, indem Sie die Werte von der IConfigurationInstanz an eine Instanz von binden MyOptions(was im Wesentlichen das Options-Framework tut):

public void ConfigureServices(IServiceCollection services)
{
    var myOptions = new MyOptions();
    Configuration.GetSection("SomeSection").Bind(myOptions);
}

Das manuelle Auflösen von Diensten (auch als Service Locator bezeichnet) wird im Allgemeinen als Anti-Pattern angesehen . Obwohl es Anwendungsfälle gibt (für Frameworks und / oder Infrastrukturschichten), sollten Sie dies so weit wie möglich vermeiden.

Henk Mollema
quelle
14
@HenkMollema, aber was ist, wenn mir nichts injiziert werden kann? Ich meine, ich kann keine IServiceCollectionKlasse injizieren lassen, eine Klasse, die manuell erstellt wird ( außerhalb des Bereichs der mittleren Ware ), in meinem Fall ein Scheduler, der regelmäßig einige Dienste zum Generieren benötigt und senden Sie eine E-Mail.
Merdan Gochmuradov
52
Warnung: Wenn Sie Dienste in auflösen müssen ConfigureServicesund dieser Dienst ein Singleton ist, handelt es sich um einen anderen Singleton als den von Ihnen Controllerverwendeten! Ich nehme an, das liegt daran, dass es eine andere verwendet IServiceProvider- um dies zu vermeiden, lösen Sie NICHT über BuildServiceProviderund verschieben Sie stattdessen Ihre Suche nach dem Singleton von ConfigureServicesnach Configure(..other params, IServiceProvider serviceProvider)inStartup.cs
wal
3
@wal guter Punkt. Da es sich um eine andere IServiceProviderInstanz handelt, wird eine neue Singleton-Instanz erstellt. Sie können dies vermeiden, indem Sie die Dienstanbieterinstanz von der ConfigureServicesMethode zurückgeben, sodass dies auch der Container ist, den Ihre Anwendung verwendet.
Henk Mollema
1
Anrufen collection.BuildServiceProvider();war das, was ich brauchte, danke!
Chris Marisic
2
@HenkMollema Wie funktioniert es mit nur einer Dienstanbieterinstanz? In der Regel würden Sie 1) einige Ihrer Abhängigkeiten registrieren 2) eine vorläufige Dienstanbieterinstanz erstellen 3) diesen Dienstanbieter verwenden, um etwas aufzulösen, das Sie zum Registrieren einiger anderer Abhängigkeiten benötigen. Danach können Sie die Zwischeninstanz nicht mehr zurückgeben, da einige Ihrer Abhängigkeiten fehlen (registriert in 3). Vermisse ich etwas
Filip
109

Das manuelle Auflösen von Instanzen umfasst die Verwendung der IServiceProviderSchnittstelle:

Auflösen der Abhängigkeit in Startup.ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyService, MyService>();

    var serviceProvider = services.BuildServiceProvider();
    var service = serviceProvider.GetService<IMyService>();
}

Auflösen von Abhängigkeiten in Startup.Configure

public void Configure(
    IApplicationBuilder application,
    IServiceProvider serviceProvider)
{
    // By type.
    var service1 = (MyService)serviceProvider.GetService(typeof(MyService));

    // Using extension method.
    var service2 = serviceProvider.GetService<MyService>();

    // ...
}

Auflösen von Abhängigkeiten in Startup.Configure in ASP.NET Core 3

public void Configure(
    IApplicationBuilder application,
    IWebHostEnvironment webHostEnvironment)
{
    app.ApplicationServices.GetService<MyService>();
}

Verwenden von Runtime Injected Services

Einige Typen können als Methodenparameter eingefügt werden:

public class Startup
{
    public Startup(
        IHostingEnvironment hostingEnvironment,
        ILoggerFactory loggerFactory)
    {
    }

    public void ConfigureServices(
        IServiceCollection services)
    {
    }

    public void Configure(
        IApplicationBuilder application,
        IHostingEnvironment hostingEnvironment,
        IServiceProvider serviceProvider,
        ILoggerFactory loggerfactory,
        IApplicationLifetime applicationLifetime)
    {
    }
}

Auflösen von Abhängigkeiten in Controller-Aktionen

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";
Muhammad Rehan Saeed
quelle
1
@AfsharMohebbi Das, GetServicewas generisch ist, ist eine Erweiterungsmethode im Microsoft.Extensions.DependencyInjectionNamespace.
Ahmadali Shafiee
Informationen zu Erweiterungsmethoden: Eine Erweiterungsmethode ist eine statische Methode, die einer Klasse Funktionalität hinzufügt. Sie können den öffentlichen statischen TheReturnType TheMethodName (diesen TheTypeYouExtend theTypeYouExtend {// BODY} deklarieren und ihn dann wie folgt verwenden: TheTypeYouExtend.TheMethodName (); Mit .NET Core wird dies zu einem weit verbreiteten Ansatz, sodass Entwickler die Basisfunktionalität erweitern können ... gute Beispiele hier: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Juan
17

Wenn Sie eine Anwendung mit einer Vorlage generieren, wird Folgendes in der StartupKlasse angezeigt:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddApplicationInsightsTelemetry(Configuration);

    services.AddMvc();
}

Dort können Sie dann Abhängigkeiten hinzufügen, zum Beispiel:

services.AddTransient<ITestService, TestService>();

Wenn Sie ITestServiceauf Ihrem Controller zugreifen möchten, können Sie IServiceProviderden Konstruktor hinzufügen und er wird injiziert:

public HomeController(IServiceProvider serviceProvider)

Anschließend können Sie den hinzugefügten Dienst auflösen:

var service = serviceProvider.GetService<ITestService>();

Beachten Sie, dass Sie zur Verwendung der generischen Version den Namespace in die Erweiterungen aufnehmen müssen:

using Microsoft.Extensions.DependencyInjection;

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}

Startup.cs (ConfigureServices)

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationInsightsTelemetry(Configuration);
    services.AddMvc();

    services.AddTransient<ITestService, TestService>();
}

HomeController.cs

using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(IServiceProvider serviceProvider)
        {
            var service = serviceProvider.GetService<ITestService>();
            int rnd = service.GenerateRandom();
        }
BrunoLM
quelle
10

Wenn Sie nur eine Abhängigkeit auflösen müssen, um sie an den Konstruktor einer anderen von Ihnen registrierten Abhängigkeit zu übergeben, können Sie dies tun.

Angenommen, Sie hatten einen Dienst, der eine Zeichenfolge und einen ISomeService aufgenommen hat.

public class AnotherService : IAnotherService
{
    public AnotherService(ISomeService someService, string serviceUrl)
    {
        ...
    }
}

Wenn Sie dies in Startup.cs registrieren, müssen Sie Folgendes tun:

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);
raterus
quelle
Das OP gab nicht den Grund für die Notwendigkeit an, einen Dienst in der ConfigureService-Methode aufzulösen, aber dies ist höchstwahrscheinlich der Grund, warum jemand daran denken würde
kilkfoe
1
Tatsächlich sollte dies die akzeptierte Antwort sein ... Obwohl die Antwort von Henk Mollema sehr anschaulich ist, ist Ihre Antwort heutzutage sauberer und führt nicht zu Problemen beim Erstellen eines IServiceProvider-Zwischenprodukts (verschiedene Instanzen von Singletons ...). Wahrscheinlich war diese Lösung 2015 nicht verfügbar, als Henk antwortete, aber jetzt ist es der richtige Weg.
Vi100
Versuchte dies, ISomeServicewar aber immer noch null für mich.
Ajbeaven
2 Fragen: 1) Wenn sich der Parameterkonstruktor der Serviceklasse AnotherService ändert (entfernte oder hinzugefügte Services), muss ich das Registersegment des Service IAnotherService ändern und es ändert sich ständig? 2) Stattdessen kann ich nur einen Konstruktor für AnotherService mit einem Parameter wie public AnotherService (IServiceProvider serviceProvider) hinzufügen und vom Konstruktor benötigte Dienste abrufen. Und ich muss nur die Serviceklasse AnotherService in der Startup-Klasse wie services.AddTransient <IAnotherService, AnotherService> registrieren (sp => {var service = new AnotherService (sp); return service;});
Thomas.Benz
2

Auf diese Weise können Sie Abhängigkeiten in Attribute wie AuthorizeAttribute einfügen

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));
Bora Aydın
quelle
Dies ist, wonach ich gesucht habe. Danke
Reyan Chougle
0

Ich weiß, dass dies eine alte Frage ist, aber ich bin erstaunt, dass ein ziemlich offensichtlicher und ekelhafter Hack nicht hier ist.

Sie können die Möglichkeit nutzen, Ihre eigene ctor-Funktion zu definieren, um die erforderlichen Werte aus Ihren Diensten abzurufen, während Sie sie definieren. Dies wird natürlich jedes Mal ausgeführt, wenn der Dienst angefordert wurde, es sei denn, Sie entfernen / löschen die Definition von explizit und fügen sie erneut hinzu Dieser Service innerhalb des ersten Aufbaus des ausbeutenden Ctors .

Diese Methode hat den Vorteil, dass Sie den Servicebaum während der Konfiguration des Service nicht erstellen oder verwenden müssen. Sie definieren noch, wie Dienste konfiguriert werden.

public void ConfigureServices(IServiceCollection services)
{
    //Prey this doesn't get GC'd or promote to a static class var
    string? somevalue = null;

    services.AddSingleton<IServiceINeedToUse, ServiceINeedToUse>(scope => {
         //create service you need
         var service = new ServiceINeedToUse(scope.GetService<IDependantService>())
         //get the values you need
         somevalue = somevalue ?? service.MyDirtyHack();
         //return the instance
         return service;
    });
    services.AddTransient<IOtherService, OtherService>(scope => {
         //Explicitly ensuring the ctor function above is called, and also showcasing why this is an anti-pattern.
         scope.GetService<IServiceINeedToUse>();
         //TODO: Clean up both the IServiceINeedToUse and IOtherService configuration here, then somehow rebuild the service tree.
         //Wow!
         return new OtherService(somevalue);
    });
}

Die Möglichkeit, dieses Muster zu beheben, besteht darin, OtherServiceeine explizite Abhängigkeit von anzugeben IServiceINeedToUse, anstatt entweder implizit von ihm oder dem Rückgabewert seiner Methode abhängig zu sein ... oder diese Abhängigkeit explizit auf eine andere Weise aufzulösen.

Izzy
quelle
-4
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddDbContext<ConfigurationRepository>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));

    services.AddScoped<IConfigurationBL, ConfigurationBL>();
    services.AddScoped<IConfigurationRepository, ConfigurationRepository>();
}
Nathan Alard
quelle
5
Es ist wahrscheinlicher, dass Ihre Antworten akzeptiert und positiv bewertet werden, wenn Sie kurz erklären, warum es sich um eine gute Antwort handelt, nicht nur um einen Code-Ausschnitt. Es hilft dem Fragesteller auch sicherzustellen, dass dies tatsächlich die von ihm gestellte Frage beantwortet.
Jim L
Jemand hat Ihre Antwort fälschlicherweise als minderwertig gekennzeichnet. Sie sollten einen Begleittext hinzufügen, um zu erläutern, wie Ihre Antwort funktioniert, um weitere Markierungen und / oder Abstimmungen zu verhindern. Eine Nur-Code-Antwort ist nicht von geringer Qualität . Versucht es, die Frage zu beantworten? Wenn nicht, markieren Sie als "keine Antwort" oder empfehlen Sie das Löschen (falls in der Überprüfungswarteschlange). b) Ist es technisch falsch? Downvote oder Kommentar. Aus der Überprüfung .
Wai Ha Lee