Resolvendo instâncias com o ASP.NET Core DI

302

Como resolver manualmente um tipo usando a estrutura de injeção de dependência interna do ASP.NET Core MVC?

A configuração do contêiner é fácil:

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

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

Mas como posso resolver ISomeServicesem executar a injeção? Por exemplo, eu quero fazer isso:

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

Não existem tais métodos no IServiceCollection.

Dave New
fonte
1
possível duplicata Como resolver ConfigureServices Instância Dentro em ASP.NET 5
Muhammad Rehan Saeed
3
Deseja resolvê-los no ConfigureServices()método (with IServiceCollection) ou em qualquer lugar do aplicativo?
Henk Mollema
2
@HenkMollema: Em qualquer lugar do Startup, na verdade.
Dave New

Respostas:

486

A IServiceCollectioninterface é usada para criar um contêiner de injeção de dependência. Depois de totalmente construído, ele é composto por uma IServiceProviderinstância que você pode usar para resolver serviços. Você pode injetar um IServiceProviderem qualquer classe. As classes IApplicationBuildere também HttpContextpodem fornecer o provedor de serviços, por meio de suas propriedades ApplicationServicesou RequestServices.

IServiceProviderdefine um GetService(Type type)método para resolver um serviço:

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

Existem também vários métodos de extensão de conveniência disponíveis, como serviceProvider.GetService<IFooService>()(adicione um usingparaMicrosoft.Extensions.DependencyInjection ).

Resolvendo serviços dentro da classe de inicialização

Injetando dependências

Provedor de serviço do tempo de execução pode injetar certos serviços para o construtor da Startupclasse, tais como IConfiguration, IWebHostEnvironment( IHostingEnvironmentem pré-versões 3.0), ILoggerFactorye IServiceProvider. Observe que a última é uma instância criada pela camada de hospedagem e contém apenas os serviços essenciais para iniciar um aplicativo .

O ConfigureServices()método não permite a injeção de serviços, apenas aceita um IServiceCollectionargumento. Isso faz sentido porque ConfigureServices()é onde você registra os serviços exigidos pelo seu aplicativo. No entanto, você pode usar serviços injetados no construtor da inicialização aqui, por exemplo:

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

public IConfiguration Configuration { get; }

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

Quaisquer serviços registrados ConfigureServices()podem ser injetados no Configure()método; você pode adicionar um número arbitrário de serviços após o IApplicationBuilderparâmetro:

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

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

Resolução manual de dependências

Se você precisar resolver serviços manualmente, use preferencialmente o ApplicationServicesfornecido por IApplicationBuilderno Configure()método:

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

É possível passar e usar diretamente um IServiceProviderno construtor de sua Startupclasse, mas, como acima, ele conterá um subconjunto limitado de serviços e, portanto, possui utilidade limitada:

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

Se você precisar resolver serviços no ConfigureServices()método, será necessária uma abordagem diferente. Você pode criar um intermediário a IServiceProviderpartir da IServiceCollectioninstância que contém os serviços que foram registrados até aquele 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>();
}

Observação: geralmente você deve evitar resolver serviços dentro do ConfigureServices()método, pois esse é realmente o local em que você está configurando os serviços de aplicativo. Às vezes, você só precisa acessar uma IOptions<MyOptions>instância. Você pode fazer isso vinculando os valores da IConfigurationinstância a uma instância de MyOptions(que é essencialmente o que a estrutura de opções faz):

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

A resolução manual de serviços (aka Service Locator) é geralmente considerada um antipadrão . Embora tenha seus casos de uso (para estruturas e / ou camadas de infraestrutura), você deve evitá-lo o máximo possível.

Henk Mollema
fonte
14
@HenkMollema mas e se eu não puder injetar nada, quero dizer que não posso IServiceCollectioninjetar, alguma classe que está sendo criada manualmente ( fora do escopo do middle ware ), um agendador no meu caso, que periodicamente precisa de alguns serviços para gerar e envie um email.
Merdan Gochmuradov 29/11
52
aviso se você precisar resolver serviços ConfigureServicese esse serviço for um singleton, será um singleton diferente daquele que você Controllerusa! Suponho que isso é porque ele usa um diferente IServiceProvider- para evitar este não resolverem via BuildServiceProvidere, em vez mover o lookup do singleton a partir ConfigureServicesde Configure(..other params, IServiceProvider serviceProvider)dentroStartup.cs
wal
3
@wal bom ponto. Por ser uma IServiceProviderinstância diferente, ela criará uma nova instância singleton. Você pode evitar isso retornando a instância do provedor de serviços do ConfigureServicesmétodo para que também seja o contêiner que seu aplicativo usa.
Henk Mollema 24/03
1
Invocar collection.BuildServiceProvider();era o que eu precisava, obrigado!
Chris Marisic
2
@HenkMollema como você trabalha com apenas uma instância de provedor de serviços? Normalmente, você 1) registra algumas de suas dependências 2) cria uma instância provisória de provedor de serviços 3) usa esse provedor de serviços para resolver algo necessário para registrar outras dependências. Posteriormente, você não poderá retornar a instância provisória, pois faltam algumas de suas dependências (registradas em 3). Estou esquecendo de algo?
21418 Filip
109

A resolução manual de instâncias envolve o uso do método IServiceProvider interface:

Resolvendo dependência em Startup.ConfigureServices

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

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

Resolvendo dependências na inicialização.

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

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

    // ...
}

Resolvendo dependências no Startup.Configure no ASP.NET Core 3

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

Usando serviços injetados em tempo de execução

Alguns tipos podem ser injetados como parâmetros de método:

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

Resolvendo dependências em ações do controlador

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";
Muhammad Rehan Saeed
fonte
1
@AfsharMohebbi, o GetServiceque é genérico, é um método de extensão no Microsoft.Extensions.DependencyInjectionespaço para nome.
ahmadali Shafiee
Sobre os métodos de extensão: Um método de extensão é um método estático que adiciona funcionalidade a uma classe. Você pode declarar estático público TheReturnType TheMethodName (este TheTypeYouExtend theTypeYouExtend {// BODY}) e então pode ser usado como: TheTypeYouExtend.TheMethodName (); se torne uma abordagem muito comum com o .NET Core, para que os desenvolvedores possam estender a funcionalidade básica ... bons exemplos aqui: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Juan
17

Se você gerar um aplicativo com um modelo, terá algo parecido com isto na Startupclasse:

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

    services.AddMvc();
}

Você pode adicionar dependências lá, por exemplo:

services.AddTransient<ITestService, TestService>();

Se você deseja acessar ITestServiceno seu controlador, você pode adicionar IServiceProvidero construtor e ele será injetado:

public HomeController(IServiceProvider serviceProvider)

Em seguida, você pode resolver o serviço que você adicionou:

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

Observe que, para usar a versão genérica, é necessário incluir o espaço para nome com as extensões:

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
fonte
10

Se você apenas precisar resolver uma dependência com a finalidade de transmiti-la ao construtor de outra dependência que está registrando, poderá fazer isso.

Digamos que você tenha um serviço que tenha uma string e um ISomeService.

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

Quando você registrar isso em Startup.cs, precisará fazer o seguinte:

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);
raterus
fonte
O OP não especificou a razão para a necessidade de resolver um serviço no método ConfigureService, mas esta é provavelmente a alguém motivo pensaria em fazer isso
kilkfoe
1
De fato, essa deve ser a resposta aceita ... Embora a resposta de Henk Mollema seja muito ilustrativa, hoje em dia sua resposta é mais limpa e não apresenta problemas relacionados à criação de um IServiceProvider intermediário (diferentes instâncias de singletons ...). Provavelmente, esta solução não estava disponível em 2015 quando Henk respondeu, mas agora é o caminho a percorrer.
Vi100
Tentei isso, mas ISomeServiceainda era nulo para mim.
ajbeaven
2 perguntas: 1) Se o construtor de parâmetros da classe de serviço AnotherService mudar (serviços removidos ou adicionados), preciso modificar o segmento de registro do serviço IAnotherService e ele continuará mudando? 2) Em vez disso, posso adicionar apenas um construtor para AnotherService com 1 parâmetro como public AnotherService (IServiceProvider serviceProvider) e obter os serviços que preciso do construtor. E eu só preciso registrar a classe de serviço AnotherService na classe Startup, como services.AddTransient <IAnotherService, AnotherService> (sp => {var service = new AnotherService (sp); return service;});
Thomas.Benz
2

Você pode injetar dependências em atributos como AuthorizeAttribute dessa maneira

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));
Bora Aydın
fonte
Isto é o que eu estava procurando .. Obrigado
Reyan Chougle 28/03
0

Eu sei que esta é uma pergunta antiga, mas estou surpreso que um hack bastante óbvio e nojento não esteja aqui.

Você pode explorar a capacidade de definir sua própria função de ctor para obter os valores necessários de seus serviços conforme você os define ... obviamente isso seria executado toda vez que o serviço fosse solicitado, a menos que você explicitamente remova / limpe e adicione novamente a definição de este serviço na primeira construção do ctor explorador .

Esse método tem a vantagem de não exigir que você construa a árvore de serviços ou use-a durante a configuração do serviço. Você ainda está definindo como os serviços serão configurados.

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

A maneira de corrigir esse padrão seria fornecer OtherServiceuma dependência explícita IServiceINeedToUse, em vez de depender implicitamente dele ou do valor de retorno do seu método ... ou resolver essa dependência explicitamente de alguma outra maneira.

Izzy
fonte
-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
fonte
5
É mais provável que suas respostas sejam aceitas e votadas se você fornecer uma breve explicação de por que é uma boa resposta, não apenas um trecho de código. Também ajuda o solicitante a ter certeza de que está realmente respondendo à pergunta que fez.
21718 Jim
Alguém sinalizou incorretamente sua resposta como de baixa qualidade. Você deve adicionar algum texto anexo para explicar como sua resposta funciona para evitar mais sinalizações e / ou votos negativos. Uma resposta apenas de código não é de baixa qualidade . Ele tenta responder à pergunta? Caso contrário, sinalize como 'não é uma resposta' ou recomende a exclusão (se estiver na fila de revisão). b) É tecnicamente incorreto? Voto negativo ou comentário. Da revisão .
Wai Ha Lee