Як вирішити екземпляр всередині ConfigureServices в ASP.NET Core


103

Чи можна вирішити екземпляр IOptions<AppSettings>з ConfigureServicesметоду в автозавантаженні? Зазвичай ви можете використовувати IServiceProviderдля ініціалізації екземплярів, але у вас його немає на цьому етапі, коли ви реєструєте служби.

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

    // How can I resolve IOptions<AppSettings> here?
}

Відповіді:


154

Ви можете побудувати постачальника послуг, використовуючи 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 (), чи не так?
Рей

1
@Ray, тоді ви можете використовувати механізми введення залежностей за замовчуванням, такі як введення конструктора. Це питання стосується вирішення служб всередині ConfigureServicesметоду.
Henk Mollema

@pcdev Коли ви це робите, ви отримуєте NULL, а потім намагаєтеся вирішити екземпляр. Спочатку потрібно додати послугу.
IngoB

@IngoB так, вибачте, цей коментар був неправильним і його слід видалити - я не розумів належним чином, що відбувалося, коли писав цей коментар. Будь ласка, перегляньте відповідь, на яку я посилався раніше, яку з тих пір я оновлював - Я ще трохи розслідував і тепер краще розумію її.
pcdev

15
Хоча це може бути корисним у випадках, коли метод додавання служби не має заводського перевантаження реалізації (наприклад, тут ), використання BuildServiceProviderвикликає попередження, якщо воно використовується в коді програми, наприклад, ConfigureServicesоскільки це призводить до додаткової копії одиночних служб створений. Відповідь Ехсана Мірсаєді тут є найбільш ідеальним рішенням для таких випадків.
Нео,

67

Найкращий спосіб створення екземплярів класів, які залежать від інших служб, - це використання перевантаження 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;
});

16
Використовуйте це рішення, а не прийняту відповідь для .Net Core 3 або новішої версії!
Джошіт

7
@Joshit Я не настільки впевнений, що це життєздатна заміна прийнятої відповіді у всіх сценаріях. IServiceProvider доступний для тобто AddSingleton, AddScoped, AddTransient. Але є багато інших методів Add, які не забезпечують цього перевантаження, тобто AddCors, AddAuthentication, AddAuthorization.
Jpsy

1
@Jpsy Ви змішуєте речі, не пов'язані між собою. AddCors, AddAuthentication тощо - це помічники, які викликають під реєстраційними правами з'єднання різних базових проміжних програм. AddTransient, AddSingleton, AddScoped - це три реєстрації (із трьома загальновживаними життями)
Fab

Це охоплює не всі випадки. Будь ласка, зверніться до моєї відповіді, щоб знайти таке рішення.
Ян Кемп

8

Найпростіший і правильний спосіб досягти цього у всіх версіях ASP.NET Core - це реалізація IConfigureOptions<TOptions>інтерфейсу. Хоча це існує з .NET Core 1.0, здається, мало хто знає про те, як це робить речі просто робочими .

Як приклад, ви хочете додати власний валідатор моделі, який залежить від однієї з інших служб вашої програми. Спочатку це здається неможливим - неможливо вирішити проблему, 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>();
    }

1
Ця відповідь занадто конкретна для AutoFac, яка не входить у сферу цього питання.
Pure.Krome

Я прийшов сюди, погугливши це питання з префіксом autofac, і, на жаль, не знайшов жодної конкретної теми. Тож я сподіваюся, що інші, хто також прийде до цього питання, борючись із цією проблемою, можуть знайти відповідь.
просто добре
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.