Как разрешить экземпляр внутри ConfigureServices в ASP.NET Core

113

Можно ли разрешить экземпляр IOptions<AppSettings>из ConfigureServicesметода в запуске? Обычно вы можете использовать его IServiceProviderдля инициализации экземпляров, но на данном этапе, когда вы регистрируете службы, у вас его нет.

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

    // How can I resolve IOptions<AppSettings> here?
}
Мухаммад Рехан Саид
источник

Ответы:

161

Вы можете создать поставщика услуг, используя BuildServiceProvider()метод 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>>();
}

Для этого вам понадобится Microsoft.Extensions.DependencyInjectionпакет.


В случае, когда вам просто нужно привязать некоторые параметры ConfigureServices, вы также можете использовать Bindметод:

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

Эта функция доступна через Microsoft.Extensions.Configuration.Binderпакет.

Хенк Моллема
источник
Что делать, если вам нужно разрешить эту услугу в другой части приложения? Я уверен, что не все это делается в ConfigureServices (), верно?
Ray
2
@Ray, тогда вы можете использовать механизмы внедрения зависимостей по умолчанию, такие как внедрение конструктора. Этот вопрос конкретно касается разрешения служб внутри ConfigureServicesметода.
Хенк Моллема,
@pcdev Когда вы это делаете, вы получаете NULL, а затем пытаетесь разрешить экземпляр. Сначала вам нужно добавить услугу.
IngoB
@IngoB да, извините, этот комментарий был неверным и должен быть удален - я не совсем понял, что происходит, когда писал этот комментарий. См. Ответ, на который я ссылался ранее, который я с тех пор обновил - я провел дополнительное расследование и теперь понимаю его лучше.
pcdev
16
Хотя это может быть полезно в тех случаях, когда метод добавления службы не имеет перегрузки фабрики реализации (например, здесь ), использование BuildServiceProviderвызывает предупреждение, если оно используется в коде приложения, например, так ConfigureServicesкак это приводит к созданию дополнительной копии одноэлементных служб. создан. Ответ Эхсана Мирсаиди здесь - самое идеальное решение для подобных случаев.
Neo
76

Лучший способ создания экземпляров классов, которые зависят от других служб, - использовать перегрузку Add XXX, которая предоставляет вам IServiceProvider . Таким образом, вам не нужно создавать промежуточного поставщика услуг.

В следующих примерах показано, как можно использовать эту перегрузку в методах 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;
});
Эхсан Мирсаиди
источник
19
Используйте это решение, а не принятый ответ для .Net Core 3 или выше!
Джошит
11
@Joshit Я не уверен, что это жизнеспособная замена принятому ответу во всех сценариях. IServiceProvider доступен, например, для AddSingleton, AddScoped, AddTransient. Но есть много других методов Add, которые не обеспечивают эту перегрузку, например AddCors, AddAuthentication, AddAuthorization.
Jpsy 06
1
@Jpsy Вы путаете несвязанные вещи. AddCors, AddAuthentication и т. Д. - это помощники, которые вызывают под методами регистрации для подключения различного базового промежуточного программного обеспечения. AddTransient, AddSingleton, AddScoped - это три регистрации (с тремя обычно используемыми сроками жизни)
Fab
Это не касается всех случаев. Обратитесь к моему ответу, чтобы узнать, как это работает.
Ян Кемп
11

Самый простой и правильный способ добиться этого во всех версиях ASP.NET Core - реализовать IConfigureOptions<TOptions>интерфейс. Хотя это существует с .NET Core 1.0, кажется, что мало кто знает, как это делает вещи Just Work ™ .

В качестве примера вы хотите добавить настраиваемый валидатор модели, который зависит от одной из других служб вашего приложения. Первоначально это кажется невозможным - нет способа решить, IMyServiceDependencyпотому что у вас нет доступа к IServiceProvider:

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

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

Но «волшебство» IConfigureOptions<TOptions>делает это так просто:

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

По сути, любая настройка, которую вы сделали бы в Add***(***Options)делегатах, ConfigureServicesтеперь перемещается в метод вашего IConfigureOptions<TOptions>класса Configure. Затем вы регистрируете параметры так же, как и любую другую службу, и готово!

Для получения более подробной информации, а также информации о том, как это работает за кулисами, я отсылаю вас к всегда превосходному Эндрю Локку .

Ян Кемп
источник
1

Вы ищете что-то вроде подписчиков? Вы можете посмотреть мои комментарии в коде:

// 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
Киран Чалла
источник
-1

Хотите помочь другим, которые выглядят так же, но и при использовании Autofac.

Если вы хотите получить ILifetimeScope (т.е. контейнер текущей области видимости), вам нужно вызвать app.ApplicationServices.GetAutofacRoot()метод, Configure(IApplicationBuilder app)который вернет экземпляр ILifetimeScope, который вы можете использовать для разрешения служб.

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

        ILifetimeScope autofacRoot = app.ApplicationServices.GetAutofacRoot();
        var repository = autofacRoot.Resolve<IRepository>();
    }
просто хорошо
источник
2
Этот ответ слишком специфичен для AutoFac, который не входит в рамки этого вопроса.
Pure.Krome,
Я пришел сюда, задав этот вопрос с префиксом autofac, и, к сожалению, не нашел конкретной темы. Поэтому я ожидаю, что другие, которые также зададут этот вопрос, борясь с этой проблемой, смогут найти ответ.
просто хорошо