Вирішення примірників за допомогою ASP.NET Core DI


302

Як я можу вирішити тип вручну за допомогою вбудованої рамки введення залежності ASP.NET Core MVC?

Налаштування контейнера досить просто:

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

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

Але як я можу вирішити, ISomeServiceне виконуючи ін’єкції? Наприклад, я хочу це зробити:

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

У Росії таких методів немає IServiceCollection.



3
Ви хочете вирішити їх ConfigureServices()методом (за допомогою IServiceCollection) або просто будь-де в додатку?
Хенк Моллема

2
@HenkMollema: Фактично в будь-якій точці запуску.
Дейв Новий

Відповіді:


484

IServiceCollectionІнтерфейс використовується для побудови контейнера ін'єкції залежностей. Після того, як він повністю побудований, він складається в IServiceProviderекземпляр, який ви можете використовувати для вирішення служб. Ви можете ввести IServiceProviderв будь-який клас. Ці IApplicationBuilderта HttpContextкласи можуть надати постачальник послуг , а також, з допомогою їх ApplicationServicesабо RequestServicesвластивостей , відповідно.

IServiceProviderвизначає GetService(Type type)спосіб вирішення послуги:

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

Існує також кілька методів розширення зручності, наприклад serviceProvider.GetService<IFooService>()(додати usingдля Microsoft.Extensions.DependencyInjection).

Вирішення послуг всередині класу запуску

Ін'єкційні залежності

Хостинг провайдер середовища виконання може вводити певні послуги в конструктор Startupкласу, таких як IConfiguration, IWebHostEnvironment( IHostingEnvironmentу версії до 3.0), ILoggerFactoryі IServiceProvider. Зауважте, що останній - це екземпляр, побудований хостинг-шаром і містить лише необхідні сервіси для запуску програми .

ConfigureServices()Метод не дозволяє ін'єкційним послуги, вона тільки приймає IServiceCollectionаргумент. Це має сенс, оскільки ConfigureServices()ви реєструєте послуги, необхідні вашою заявкою. Однак ви можете використовувати послуги, введені в конструктор запуску, наприклад:

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

public IConfiguration Configuration { get; }

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

Будь-які сервіси, зареєстровані в, ConfigureServices()можуть бути введені в Configure()метод; після IApplicationBuilderпараметра можна додати довільну кількість послуг :

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

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

Розв’язування залежностей вручну

Якщо вам потрібно вручну послуги рішучості, ви повинні переважно використовувати ApplicationServicesпередбачено IApplicationBuilderв Configure()методі:

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

Можна передати та безпосередньо використати IServiceProviderв конструкторі вашого Startupкласу, але, як зазначено вище, він буде містити обмежений підмножина сервісів і, таким чином, має обмежену корисність:

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

Якщо ви повинні вирішити послуги ConfigureServices()методом, потрібен інший підхід. Ви можете створити проміжний IServiceProviderз IServiceCollectionекземпляра, який містить послуги, зареєстровані до цього моменту :

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

Зверніть увагу: як правило, ви повинні уникати вирішення служб всередині ConfigureServices()методу, оскільки це фактично місце, де ви налаштовуєте служби додатків. Іноді просто потрібен доступ до IOptions<MyOptions>екземпляра. Ви можете досягти цього, прив’язавши значення від IConfigurationекземпляра до екземпляра MyOptions(що по суті є тим, що робить рамка параметрів):

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

Служби, що вирішуються вручну (він же є "Локатор послуг", як правило, вважається анти-закономірністю . Хоча у нього є випадки використання (для фреймворків та / або інфраструктурних шарів), вам слід максимально уникати цього.


14
@HenkMollema, але що, якщо я не можу нічого вводити, я маю на увазі, що я не можу зробити IServiceCollectionін’єкцію, якийсь клас, який створюється вручну ( із середнього обсягу посуду ), у моєму випадку планувальник, який періодично потребує певних служб для створення і надіслати електронний лист.
Мердан Гочмурадов

52
Попередження, якщо вам потрібно вирішити послуги в ConfigureServicesслужбі, і ця послуга є однотонною, це буде інший однотонний, ніж той, який ви Controllerвикористовуєте! Я припускаю , що це тому , що вона використовує іншу IServiceProvider- щоб уникнути цього не дозволяє через BuildServiceProviderта замість того, щоб перемістити пошук в одноелементна від ConfigureServicesдо Configure(..other params, IServiceProvider serviceProvider)вStartup.cs
вали

3
@wal хороший пункт. Оскільки це інший IServiceProviderекземпляр, він створить новий одиночний екземпляр. Ви можете уникнути цього, повернувши примірник постачальника послуг з ConfigureServicesметоду, щоб він був контейнером, який використовує ваша програма.
Хенк Моллема

1
Спасибі collection.BuildServiceProvider();було те, що мені було потрібно, дякую!
Кріс Марісіч

2
@HenkMollema, як змусити його працювати лише з одним екземпляром постачальника послуг? Як правило, ви б 1) Зареєстрували деякі ваші залежності 2) побудували тимчасовий екземпляр постачальника послуг 3) Використовуйте цього постачальника послуг, щоб вирішити щось необхідне для реєстрації деяких інших залежностей. Після цього ви не можете повернути тимчасовий екземпляр, оскільки в ньому відсутні деякі ваші залежності (зареєстровані в 3). Я щось пропускаю?
Філіп

109

Вирішення екземплярів вручну включає використання IServiceProviderінтерфейсу:

Вирішення залежності в Startup.ConfigureServices

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

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

Вирішення залежностей у Startup.Configure

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

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

    // ...
}

Вирішення залежностей у Startup.Configure в ASP.NET Core 3

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

Використання служб, які вводяться під час виконання

Деякі типи можна вводити як параметри методу:

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

Вирішення залежностей в діях контролера

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";

1
@AfsharMohebbi, GetServiceякий є загальним, є методом розширення в Microsoft.Extensions.DependencyInjectionпросторі імен.
ahmadali shafiee

Про методи розширення: метод розширення - це статичний метод, який додає функціональність класу, ви можете оголосити загальнодоступний статичний TheReturnType TheMethodName (це TheTypeYouExtend theTypeYouExtend {// BODY}), а потім ви можете використовувати його як: TheTypeYouExtend.TheMethodName () стати дуже поширеним підходом до .NET Core, щоб розробники могли розширити базову функціональність ... хороші приклади тут: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Хуан

17

Якщо ви генеруєте програму з шаблоном, у вас буде щось подібне на Startupкласі:

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

    services.AddMvc();
}

Потім ви можете додати там залежності, наприклад:

services.AddTransient<ITestService, TestService>();

Якщо ви хочете отримати доступ ITestServiceдо свого контролера, ви можете додати IServiceProviderконструктор, і він буде введений:

public HomeController(IServiceProvider serviceProvider)

Тоді ви можете вирішити додану вами послугу:

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

Зауважте, що для використання загальної версії потрібно включити простір імен із розширеннями:

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

10

Якщо вам просто потрібно вирішити одну залежність для того, щоб передати її конструктору іншої залежності, яку ви реєструєте, ви можете це зробити.

Скажімо, у вас був сервіс, який взяв рядок та ISomeService.

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

Коли ви реєструєте це все в Startup.cs, вам потрібно буде зробити це:

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);

ОП не
вказала

1
Насправді це має бути прийнята відповідь ... Хоча відповідь Хенка Моллема є дуже показовою, на сьогоднішній день ваша відповідь є більш чіткою і не створює проблем, пов’язаних із побудовою проміжного IServiceProvider (різні екземпляри синглів ...). Ймовірно, це рішення було недоступне у 2015 році, коли Хенк відмовився, але тепер це шлях.
Vi100

Спробував це, але ISomeServiceдля мене все ще був недійсним.
ajbeaven

2 питання: 1) Якщо конструктор параметрів класу обслуговування AnotherService змінюється (видаляється або додається сервіс), мені потрібно змінити сегмент регістра служби IAbodyService і він постійно змінюється? 2) Натомість я можу додати лише один конструктор для AnotherService з 1 параметром, таким як загальний AnotherService (IServiceProvider serviceProvider) і отримати потрібні мені послуги від конструктора. І мені просто потрібно зареєструвати клас обслуговування AnotherService в класі запуску, наприклад services.AddTransient <IAbodyService, AnotherService> (sp => {var service = new AnotherService (sp); return return;});
Thomas.Benz

2

Ви можете вводити залежності в такі атрибути, як AuthorizeAttribute, таким чином

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));

Це те, що я шукав .. Спасибі
Реян Чоул

0

Я знаю, що це давнє питання, але я здивований, що тут досить очевидний і огидний злом.

Ви можете використовувати здатність визначати свою власну функцію ctor, щоб вибирати необхідні значення зі своїх служб під час їх визначення ... очевидно, що це буде виконуватися кожного разу, коли служба запитується, якщо ви явно не видалите / очистите і не додасте визначення ця послуга в рамках першої конструкції експлуатаційного ctor .

Цей метод має перевагу в тому, що ви не вимагаєте від вас створювати дерево обслуговування або використовувати його під час конфігурації послуги. Ви все ще визначаєте, як будуть налаштовані служби.

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

Способом виправити цю закономірність було б надати OtherServiceявну залежність від IServiceINeedToUse, а не неявно залежно від неї чи зворотного значення методу ... або вирішити цю залежність явно якимось іншим способом.


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

5
Ваші відповіді, швидше за все , щоб бути прийнятими і upvoted , якщо ви дати коротке пояснення того , чому це хороший відповідь, а не тільки фрагмент коду. Це також допомагає запитувачеві бути впевненим, що це насправді відповідає на питання, яке вони задали.
Jim L

Хтось неправильно позначив вашу відповідь неякісною. Ви повинні додати деякий супровідний текст, щоб пояснити, як працює ваша відповідь, щоб уникнути подальшого позначення та / або зворотного сигналу. Відповідь, кодована лише для коду, є неякісною . Чи намагається відповісти на запитання? Якщо ні, позначте як "не відповідь" або рекомендуйте видалити (якщо в черзі на огляд). б) Це технічно неправильно? Відхилення або коментар. З огляду .
Вай Ха Лі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.