Cómo resolver una instancia dentro de ConfigureServices en ASP.NET Core

105

¿Es posible resolver una instancia de IOptions<AppSettings>desde el ConfigureServicesmétodo en Inicio? Normalmente, puede usar IServiceProviderpara inicializar instancias, pero no lo tiene en esta etapa cuando está registrando servicios.

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

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

Respuestas:

155

Puede crear un proveedor de servicios utilizando el BuildServiceProvider()método en 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>>();
}

Necesita el Microsoft.Extensions.DependencyInjectionpaquete para esto.


En el caso de que solo necesite vincular algunas opciones ConfigureServices, también puede usar el Bindmétodo:

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

Esta funcionalidad está disponible a través del Microsoft.Extensions.Configuration.Binderpaquete.

Henk Mollema
fuente
¿Qué sucede si necesita resolver este servicio en otra parte de la aplicación? Estoy seguro de que no todo está hecho en ConfigureServices (), ¿verdad?
Ray
1
@Ray, entonces puede usar los mecanismos de inyección de dependencia predeterminados, como la inyección del constructor. Esta pregunta trata específicamente sobre la resolución de servicios dentro del ConfigureServicesmétodo.
Henk Mollema
@pcdev Obtiene NULL cuando lo hace y luego intenta resolver la instancia. Primero debes agregar el servicio.
IngoB
@IngoB sí, lo siento, ese comentario era incorrecto y debería eliminarse; no entendí bien lo que estaba pasando cuando escribí ese comentario. Por favor, vea la respuesta a la que vinculé anteriormente y que he actualizado desde entonces. Investigué un poco más y ahora lo entiendo mejor.
pcdev
15
Si bien esto puede ser útil en los casos en que el método para agregar un servicio no tiene una sobrecarga de fábrica de implementación (por ejemplo, aquí ), el uso BuildServiceProvidergenera una advertencia si se usa en el código de la aplicación, ConfigureServicesya que da como resultado una copia adicional de los servicios singleton creado. La respuesta de Ehsan Mirsaeedi aquí es la solución más ideal para casos como este.
Neo
68

La mejor manera de crear instancias de clases que dependen de otros servicios es utilizar la sobrecarga Add XXX que le proporciona IServiceProvider . De esta forma, no es necesario crear una instancia de un proveedor de servicios intermedio.

Los siguientes ejemplos muestran cómo puede utilizar esta sobrecarga en los 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
fuente
16
Utilice esta solución en lugar de la respuesta aceptada para .Net Core 3 o superior.
Joshit
7
@Joshit No estoy tan seguro de que este sea un reemplazo viable para la respuesta aceptada en todos los escenarios. IServiceProvider está disponible para, por ejemplo, AddSingleton, AddScoped, AddTransient. Pero hay muchos otros métodos Add que no proporcionan esta sobrecarga, es decir, AddCors, AddAuthentication, AddAuthorization.
Jpsy
1
@Jpsy Mezclas cosas que no están relacionadas. AddCors, AddAuthentication, etc., son ayudantes que llaman debajo de los métodos de registro para conectar los diversos middleware subyacentes. AddTransient, AddSingleton, AddScoped son los tres registros (con las tres vidas útiles comúnmente utilizadas)
Fab
Esto no cubre todos los casos. Consulte mi respuesta para obtener una solución que lo haga.
Ian Kemp
8

La forma más sencilla y correcta de lograr esto, en todas las versiones de ASP.NET Core , es implementar la IConfigureOptions<TOptions>interfaz. Si bien esto ha existido desde .NET Core 1.0, parece que pocas personas saben cómo hace que las cosas funcionen simplemente .

Como ejemplo, desea agregar un validador de modelo personalizado que dependa de uno de los otros servicios de su aplicación. Inicialmente parece imposible; no hay forma de resolverlo IMyServiceDependencyporque no tiene acceso a un IServiceProvider:

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

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

Pero la "magia" de lo IConfigureOptions<TOptions>hace tan fácil:

public class ConfigureMvcOptions : 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)
{
    services.AddControllers();

    ...

    // or scoped, or transient, as necessary for your service
    services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();
}

Esencialmente, cualquier configuración que hubiera hecho en los Add***(***Options)delegados ConfigureServicesahora se mueve al método de su IConfigureOptions<TOptions>clase Configure. Luego registra las opciones de la misma manera que registraría cualquier otro servicio, ¡y listo!

Para obtener más detalles, así como información sobre cómo funciona esto detrás de escena, lo remito al siempre excelente Andrew Lock .

Ian Kemp
fuente
1

¿Estás buscando algo como seguir? Puedes echar un vistazo a mis comentarios en el 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
fuente
-1

Quiere ayudar a otras personas que tienen el mismo aspecto pero que también usan Autofac.

Si desea obtener ILifetimeScope (es decir, un contenedor del alcance actual), debe llamar al app.ApplicationServices.GetAutofacRoot()método, Configure(IApplicationBuilder app)esto devolverá la instancia de ILifetimeScope que puede usar para resolver los servicios

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

        ILifetimeScope autofacRoot = app.ApplicationServices.GetAutofacRoot();
        var repository = autofacRoot.Resolve<IRepository>();
    }
simplemente bueno
fuente
1
Esta respuesta es demasiado específica para AutoFac, que no está dentro del alcance de esta pregunta.
Pure.Krome
Vine aquí buscando en Google esta pregunta con el prefijo autofac y desafortunadamente no encontré ningún tema específico. Así que espero que otros que también se acerquen a esta pregunta luchando con este problema puedan encontrar una respuesta.
simplemente bueno