Risoluzione di istanze con ASP.NET Core DI


302

Come risolvere manualmente un tipo utilizzando il framework di iniezione delle dipendenze integrato ASP.NET Core MVC?

La configurazione del contenitore è abbastanza semplice:

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

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

Ma come posso risolvere ISomeServicesenza eseguire l'iniezione? Ad esempio, voglio fare questo:

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

Non ci sono tali metodi in IServiceCollection.



3
Vuoi risolverli nel ConfigureServices()metodo (con IServiceCollection) o semplicemente in qualsiasi punto dell'applicazione?
Henk Mollema,

2
@HenkMollema: Ovunque in Startup effettivamente.
Dave New,

Risposte:


486

L' IServiceCollectioninterfaccia viene utilizzata per la creazione di un contenitore di iniezione di dipendenza. Dopo che è stato completamente creato, viene composto in IServiceProviderun'istanza che è possibile utilizzare per risolvere i servizi. Puoi iniettare un IServiceProviderin qualsiasi classe. Le classi IApplicationBuildere HttpContextpossono fornire anche il fornitore di servizi, rispettivamente tramite le loro proprietà ApplicationServiceso RequestServices.

IServiceProviderdefinisce un GetService(Type type)metodo per risolvere un servizio:

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

Esistono anche diversi metodi di estensione della convenienza, come serviceProvider.GetService<IFooService>()(aggiungi un usingper Microsoft.Extensions.DependencyInjection).

Risoluzione dei servizi all'interno della classe di avvio

Iniezione di dipendenze

Hosting provider di servizi del runtime può iniettare alcuni servizi nel costruttore della Startupclasse, come ad esempio IConfiguration, IWebHostEnvironment( IHostingEnvironmentin pre-3.0 versioni), ILoggerFactorye IServiceProvider. Si noti che quest'ultima è un'istanza creata dal livello di hosting e contiene solo i servizi essenziali per l'avvio di un'applicazione .

Il ConfigureServices()metodo non consente l'iniezione di servizi, accetta solo un IServiceCollectionargomento. Questo ha senso perché ConfigureServices()è dove registri i servizi richiesti dalla tua applicazione. Tuttavia, è possibile utilizzare i servizi iniettati nel costruttore di avvio qui, ad esempio:

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

public IConfiguration Configuration { get; }

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

Tutti i servizi registrati ConfigureServices()possono quindi essere inseriti nel Configure()metodo; puoi aggiungere un numero arbitrario di servizi dopo il IApplicationBuilderparametro:

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

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

Risolvere dipendenze manualmente

Se è necessario risolvere manualmente i servizi, è preferibile utilizzare il metodo ApplicationServicesfornito da IApplicationBuildernel Configure()metodo:

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

È possibile passare e utilizzare direttamente un IServiceProvidernel costruttore della tua Startupclasse, ma come sopra conterrà un sottoinsieme limitato di servizi e quindi ha un'utilità limitata:

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

Se è necessario risolvere i servizi nel ConfigureServices()metodo, è necessario un approccio diverso. È possibile costruire un intermedio IServiceProviderdal IServiceCollectiongrado che contiene i servizi che sono stati registrati fino a quel momento :

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

Nota: in genere dovresti evitare di risolvere i servizi all'interno del ConfigureServices()metodo, poiché in realtà questo è il luogo in cui stai configurando i servizi dell'applicazione. A volte hai solo bisogno di accedere a IOptions<MyOptions>un'istanza. È possibile ottenere ciò vincolando i valori IConfigurationdall'istanza a un'istanza di MyOptions(che è essenzialmente ciò che fa il framework delle opzioni):

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

I servizi di risoluzione manuale (aka Service Locator) sono generalmente considerati un anti-schema . Sebbene abbia i suoi casi d'uso (per framework e / o livelli di infrastruttura), dovresti evitarlo il più possibile.


14
@HenkMollema ma cosa succede se non riesco a iniettare nulla, intendo dire che non posso aver IServiceCollectioniniettato, una classe che viene creata manualmente ( fuori dall'ambito del middle ware ), uno scheduler nel mio caso, che periodicamente ha bisogno di alcuni servizi per generare e invia un'email.
Merdan Gochmuradov,

52
avviso se devi risolvere i servizi ConfigureServicese quel servizio è un singleton, sarà un singleton diverso da quello che stai Controllerusando! Suppongo che ciò avvenga perché utilizza un altro IServiceProvider- per evitare ciò NON risolvere tramite BuildServiceProvidere invece spostare la ricerca del singleton da ConfigureServicesa Configure(..other params, IServiceProvider serviceProvider)inStartup.cs
wal

3
@wal buon punto. Perché è IServiceProviderun'istanza diversa , creerà una nuova istanza singleton. È possibile evitare ciò restituendo l'istanza del fornitore di servizi dal ConfigureServicesmetodo in modo che sia anche il contenitore utilizzato dall'applicazione.
Henk Mollema,

1
Invocare collection.BuildServiceProvider();era ciò di cui avevo bisogno, grazie!
Chris Marisic,

2
@HenkMollema come si fa a farlo funzionare con una sola istanza del fornitore di servizi? In genere, 1) Registrare alcune delle dipendenze 2) creare un'istanza del fornitore di servizi temporaneo 3) Utilizzare quel fornitore di servizi per risolvere qualcosa che è necessario per registrare alcune altre dipendenze. Successivamente, non è possibile restituire l'istanza temporanea, poiché mancano alcune delle dipendenze (registrate in 3). Mi sto perdendo qualcosa?
Filip,

109

La risoluzione manuale delle istanze comporta l'utilizzo IServiceProviderdell'interfaccia:

Risoluzione della dipendenza in Startup.ConfigureServices

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

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

Risoluzione delle dipendenze 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>();

    // ...
}

Risoluzione delle dipendenze in Startup.Configure in ASP.NET Core 3

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

Utilizzo dei servizi iniettati in runtime

Alcuni tipi possono essere iniettati come parametri del metodo:

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

Risoluzione delle dipendenze nelle azioni del controller

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";

1
@AfsharMohebbi GetServiceche è generico è un metodo di estensione nello Microsoft.Extensions.DependencyInjectionspazio dei nomi.
ahmadali shafiee,

Informazioni sui metodi di estensione: un metodo di estensione è un metodo statico che aggiunge funzionalità a una classe, è possibile dichiarare TheReturnType statico pubblico TheMethodName (questo TheTypeYouExtend theTypeYouExtend {// BODY} e quindi è possibile utilizzarlo come: TheTypeYouExtend.TheMethodName (); That has has diventare un approccio molto comune con .NET Core, in modo che gli sviluppatori possano estendere le funzionalità di base ... buoni esempi qui: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Juan

17

Se generi un'applicazione con un modello avrai qualcosa del genere nella Startupclasse:

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

    services.AddMvc();
}

È quindi possibile aggiungere dipendenze lì, ad esempio:

services.AddTransient<ITestService, TestService>();

Se vuoi accedere ITestServiceal tuo controller puoi aggiungere IServiceProvideril costruttore e verrà iniettato:

public HomeController(IServiceProvider serviceProvider)

Quindi puoi risolvere il servizio che hai aggiunto:

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

Nota che per usare la versione generica devi includere lo spazio dei nomi con le estensioni:

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

10

Se hai solo bisogno di risolvere una dipendenza allo scopo di passarla al costruttore di un'altra dipendenza che stai registrando, puoi farlo.

Supponiamo che tu abbia un servizio che include una stringa e un ISomeService.

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

Quando vai a registrarlo in Startup.cs, dovrai farlo:

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);

L'OP non ha indicato il motivo per cui è necessario risolvere un servizio nel metodo ConfigureService, ma questa è probabilmente la ragione per cui qualcuno penserebbe di farlo
Kilkfoe,

1
In realtà questa dovrebbe essere la risposta accettata ... Sebbene la risposta di Henk Mollema sia molto illustrativa, al giorno d'oggi la tua risposta è più pulita e non introduce problemi relativi alla costruzione di un IServiceProvider intermedio (diverse istanze di singoli ...). Probabilmente, questa soluzione non era disponibile nel 2015 quando Henk ha risposto, ma ora è la strada da percorrere.
Vi100,

ISomeServiceHo provato questo ma era ancora nullo per me.
ajbeaven,

2 domande: 1) Se il costruttore di parametri della classe di servizio AnotherService cambia (servizi rimossi o aggiunti), allora devo modificare il segmento di registro del servizio IAnotherService e continua a cambiare? 2) Invece, posso aggiungere solo un costruttore per AnotherService con 1 parametro come AnotherService pubblico (IServiceProvider serviceProvider) e ottenere i servizi di cui ho bisogno dal costruttore. E ho solo bisogno di registrare la classe di servizio AnotherService nella classe Startup come services.AddTransient <IAnotherService, AnotherService> (sp => {var service = new AnotherService (sp); return service;});
Thomas.Benz,

2

In questo modo è possibile iniettare dipendenze in attributi come AuthorizeAttribute

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));

Questo è quello che stavo cercando .. Grazie
Reyan Chougle il

0

So che questa è una vecchia domanda, ma sono sorpreso che un hack piuttosto ovvio e disgustoso non sia qui.

Puoi sfruttare la capacità di definire la tua funzione ctor per estrarre i valori necessari dai tuoi servizi mentre li definisci ... ovviamente questo verrà eseguito ogni volta che il servizio è stato richiesto a meno che tu non rimuova / cancelli esplicitamente e aggiungi nuovamente la definizione di questo servizio nell'ambito della prima costruzione del ctor sfruttatore .

Questo metodo ha il vantaggio di non richiedere la creazione dell'albero del servizio o l'utilizzo durante la configurazione del servizio. Stai ancora definendo come verranno configurati i servizi.

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

Il modo per risolvere questo modello sarebbe quello di dare OtherServiceuna dipendenza esplicita IServiceINeedToUse, piuttosto che dipendere implicitamente da esso o dal valore di ritorno del suo metodo ... o risolvere esplicitamente tale dipendenza in qualche altro modo.


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

5
È più probabile che le tue risposte vengano accettate e votate se fornisci una breve spiegazione del perché è una buona risposta, non solo uno snippet di codice. Aiuta anche chi si chiede di essere sicuro che questo stia effettivamente rispondendo alla domanda che hanno posto.
Jim L

Qualcuno ha erroneamente segnalato la tua risposta come di bassa qualità. Dovresti aggiungere del testo di accompagnamento per spiegare come funziona la tua risposta per prevenire ulteriori segnalazioni e / o voti negativi. Una risposta di solo codice non è di bassa qualità . Tenta di rispondere alla domanda? In caso contrario, contrassegnare come 'non una risposta' o raccomandare la cancellazione (se nella coda di revisione). b) È tecnicamente errato? Downvote o commento. Dalla recensione .
Wai Ha Lee,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.