Como resolver uma instância dentro de ConfigureServices no ASP.NET Core

101

É possível resolver uma instância de IOptions<AppSettings>do ConfigureServicesmétodo na inicialização? Normalmente, você pode usar IServiceProviderpara inicializar instâncias, mas não o tem nesta fase quando está registrando serviços.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<AppSettings>(
        configuration.GetConfigurationSection(nameof(AppSettings)));

    // How can I resolve IOptions<AppSettings> here?
}
Muhammad Rehan Saeed
fonte

Respostas:

152

Você pode construir um provedor de serviços usando o BuildServiceProvider()método em IServiceCollection:

public void ConfigureService(IServiceCollection services)
{
    // Configure the services
    services.AddTransient<IFooService, FooServiceImpl>();
    services.Configure<AppSettings>(configuration.GetSection(nameof(AppSettings)));

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

    // Resolve the services from the service provider
    var fooService = sp.GetService<IFooService>();
    var options = sp.GetService<IOptions<AppSettings>>();
}

Você precisa do Microsoft.Extensions.DependencyInjectionpacote para isso.


No caso em que você só precisa vincular algumas opções ConfigureServices, também pode usar o Bindmétodo:

var appSettings = new AppSettings();
configuration.GetSection(nameof(AppSettings)).Bind(appSettings);

Esta funcionalidade está disponível no Microsoft.Extensions.Configuration.Binderpacote.

Henk Mollema
fonte
E se você precisar resolver esse serviço em outra parte do aplicativo? Tenho certeza que nem tudo é feito em ConfigureServices () certo?
Ray
1
@Ray então você pode usar os mecanismos de injeção de dependência padrão, como injeção de construtor. Esta questão é especificamente sobre a resolução de serviços dentro do ConfigureServicesmétodo.
Henk Mollema
@pcdev Você obtém NULL ao fazer isso e, em seguida, tenta resolver a instância. Você tem que adicionar o serviço primeiro.
IngoB
@IngoB sim, desculpe, esse comentário estava incorreto e deveria ser excluído - eu não entendi direito o que estava acontecendo quando escrevi esse comentário. Por favor, veja a resposta que indiquei anteriormente e que depois atualizei - fiz mais algumas investigações e agora a compreendo melhor.
pcdev
14
Embora isso possa ser útil nos casos em que o método para adicionar um serviço não tem uma sobrecarga de fábrica de implementação (por exemplo, aqui ), o uso BuildServiceProvidercausa um aviso se usado no código do aplicativo, como ConfigureServicesresulta em uma cópia adicional dos serviços singleton sendo criada. A resposta de Ehsan Mirsaeedi aqui é a solução mais ideal para casos como este.
Neo
65

A melhor maneira de instanciar classes que dependem de outros serviços é usar a sobrecarga Add XXX que fornece o IServiceProvider . Dessa forma, você não precisa instanciar um provedor de serviços intermediário.

Os exemplos a seguir mostram como você pode usar essa sobrecarga nos métodos AddSingleton / AddTransient .

services.AddSingleton(serviceProvider =>
{
    var options = serviceProvider.GetService<IOptions<AppSettings>>();
    var foo = new Foo(options);
    return foo ;
});


services.AddTransient(serviceProvider =>
{
    var options = serviceProvider.GetService<IOptions<AppSettings>>();
    var bar = new Bar(options);
    return bar;
});
Ehsan Mirsaeedi
fonte
16
Use esta solução em vez da resposta aceita para .Net Core 3 ou superior!
Joshit
7
@Joshit Não estou tão certo de que esta seja uma substituição viável para a resposta aceita em todos os cenários. IServiceProvider está disponível para, por exemplo, AddSingleton, AddScoped, AddTransient. Mas existem muitos outros métodos Add que não fornecem essa sobrecarga, ou seja, AddCors, AddAuthentication, AddAuthorization.
Jpsy
1
@Jpsy Você confunde coisas não relacionadas. AddCors, AddAuthentication e assim por diante são ajudantes que chamam os emthods de registro para conectar os vários middlewares subjacentes. AddTransient, AddSingleton, AddScoped são os três registros (com os três tempos de vida comumente usados)
Fab
Isso não cobre todos os casos. Por favor, consulte minha resposta para uma solução adequada.
Ian Kemp
7

A maneira mais simples e correta de conseguir isso, em todas as versões do ASP.NET Core , é implementar oIConfigureOptions<TOptions> interface. Embora isso exista desde o .NET Core 1.0, parece que poucas pessoas sabem como ele faz as coisas Just Work ™.

Por exemplo, você deseja adicionar um validador de modelo personalizado que dependa de um dos outros serviços do seu aplicativo. Inicialmente, parece impossível - não há como resolver IMyServiceDependencyporque você não tem acesso a IServiceProvider:

public class MyModelValidatorProvider : IModelValidatorProvider
{
    public MyModelValidatorProvider(IMyServiceDependency dependency)
    {
        ...
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.ModelValidatorProviders.Add(new MyModelValidatorProvider(??????));
    });
}

Mas a "mágica" de IConfigureOptions<TOptions>torna isso tão fácil:

public class MyMvcOptions : IConfigureOptions<MvcOptions>
{
    private IMyServiceDependency _dependency;

    public MyMvcOptions(IMyServiceDependency dependency)
        => _dependency = dependency;

    public void Configure(MvcOptions options)
    {
        options.ModelValidatorProviders.Add(new MyModelValidatorProvider(_dependency));
    }
}

public void ConfigureServices(IServiceCollection services)
{
    // or scoped, or transient
    services.AddSingleton<IConfigureOptions<MvcOptions>, MyMvcOptions>();
    services.AddControllers();
}

Essencialmente, qualquer configuração que você teria feito nos Add***(***Options)delegados ConfigureServicesagora é movida para a sua IConfigureOptions<TOptions>classeConfigure método . Então você registra as opções da mesma forma que registraria qualquer outro serviço, e pronto!

Para obter mais detalhes, bem como informações sobre como isso funciona nos bastidores, indico o sempre excelente Andrew Locke .

Ian Kemp
fonte
1

Você está procurando algo como seguir? Você pode dar uma olhada em meus comentários no código:

// this call would new-up `AppSettings` type
services.Configure<AppSettings>(appSettings =>
{
    // bind the newed-up type with the data from the configuration section
    ConfigurationBinder.Bind(appSettings, Configuration.GetConfigurationSection(nameof(AppSettings)));

    // modify these settings if you want to
});

// your updated app settings should be available through DI now
Kiran Challa
fonte
-1

Quer ajudar outras pessoas que têm a mesma aparência, mas também usam o Autofac.

Se você quiser obter ILifetimeScope (ou seja, o contêiner do escopo atual), você precisa chamar o app.ApplicationServices.GetAutofacRoot()método Configure(IApplicationBuilder app)neste retornará a instância ILifetimeScope que você pode usar para resolver os serviços

public void Configure(IApplicationBuilder app)
    {
        //app middleware registrations 
        //...
        //

        ILifetimeScope autofacRoot = app.ApplicationServices.GetAutofacRoot();
        var repository = autofacRoot.Resolve<IRepository>();
    }
simplesmente bom
fonte
1
Esta resposta é muito específica para AutoFac, o que não está no escopo desta pergunta.
Pure.Krome
Eu vim aqui pesquisando esta questão com o prefixo autofac e não encontrei nenhum tópico específico, infelizmente. Portanto, espero que outras pessoas que também venham a fazer essa pergunta, lutando com esse problema, possam encontrar uma resposta.
simplesmente bom