Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Note
Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.
Warning
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущей версии см. версию .NET 10 этой статьи.
Авторы: Кирк Ларкин (Kirk Larkin), Стив Смит (Steve Smith) и Брэндон Далер (Brandon Dahler)
ASP.NET Core поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями.
В этой статье содержатся сведения о di в веб-приложениях ASP.NET Core. Сведения о di во всех типах приложений, включая приложения, отличные от веб-приложений, см. в разделе внедрения зависимостей в .NET.
Инструкции, добавляющие или заменяющие инструкции в этой статье, приведены в следующих статьях:
- Внедрение зависимостей Blazor в ASP.NET Core
- Внедрение зависимостей в контроллеры в ASP.NET Core
- Настройка параметров с помощью служб DI
Примеры кода в этой статье основаны на Blazor. Сведения о Razor примерах страниц см. в версии 7.0 этой статьи.
Просмотреть или скачать образец кода (описание загрузки)
При использовании примера кода в этой статье в локальной Blazor Web App среде для демонстрационных целей используется интерактивный режим отрисовки.
Общие сведения о внедрении зависимостей
Зависимость — это любой объект, от которого зависит другой объект. Рассмотрим следующий MyDependency класс с методом WriteMessage :
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage: {message}");
}
}
Класс может создать экземпляр MyDependency класса для вызова метода WriteMessage . В следующем примере MyDependency класс является зависимостью Razor компонента.
Pages/DependencyExample1.razor:
@page "/dependency-example-1"
<button @onclick="WriteMessage">Write message</button>
@code {
private readonly MyDependency dependency = new MyDependency();
private void WriteMessage() =>
dependency.WriteMessage("DependencyExample1.WriteMessage called");
}
Класс может создать экземпляр MyDependency класса для вызова метода WriteMessage . В следующем примере MyDependency класс является зависимостью IndexModel класса страницы.
Pages/Index.cshtml.cs:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet called");
}
}
Потребляющий класс создает и напрямую зависит от класса MyDependency. Принимая прямую зависимость, например в предыдущем примере, проблематична и должна быть избегаема по следующим причинам:
- Чтобы заменить
MyDependencyдругой реализацией, необходимо изменить используемый класс. - Если у
MyDependencyесть зависимости, они также должны быть настроены классом, который их использует. В большом проекте с несколькими классами, зависящими отMyDependency, код конфигурации становится разбросанным по приложению. - Реализацию сложно протестировать модульно.
DI решает эти проблемы с помощью следующих методов:
- Используется интерфейс или базовый класс для абстрагирования реализации зависимостей.
- Регистрация зависимостей в контейнере службы также называется контейнером DI. ASP.NET Core предоставляет встроенный контейнер служб, IServiceProvider. Службы обычно регистрируются в файле приложения
Program(.NET 6 или более поздней версии) или в файле приложенияStartup(.NET 5 или более ранней версии). - Внедрение службы в классы, где она используется. Платформа создает экземпляры зависимостей и удаляет их, когда они больше не требуются.
В следующем примере интерфейс IMyDependency определяет сигнатуру метода WriteMessage.
Interfaces/IMyDependency.cs:
public interface IMyDependency
{
void WriteMessage(string message);
}
Предыдущий интерфейс реализуется следующим конкретным типом MyDependency.
Services/MyDependency.cs:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage: {message}");
}
}
Приложение регистрирует IMyDependency службу с конкретным типом MyDependency , в котором службы добавляются в контейнер службы, обычно в Program файле (.NET 6 или более поздней версии) или Startup.ConfigureServices методе (.NET 5 или более ранней версии). Метод AddScoped регистрирует службу с заданной областью существования, которая является временем существования Blazor канала (.NET 8 или более поздней версии) или одним запросом в приложении MVC или Razor Pages.
Сроки службы службы описаны ниже в этой статье.
builder.Services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency, MyDependency>();
Служба IMyDependency запрашивается и используется для вызова метода WriteMessage, как демонстрирует следующий компонент Razor.
Pages/DependencyExample2.razor:
@page "/dependency-example-2"
@inject IMyDependency Dependency
<button @onclick="WriteMessage">Write message</button>
@code {
private void WriteMessage() =>
Dependency.WriteMessage("DependencyExample2.WriteMessage called");
}
IMyDependency Сервис запрашивается и используется для вызова метода WriteMessage, как показано в следующем классе модели страницы.
Pages/Index.cshtml.cs:
public class IndexModel(IMyDependency dependency) : PageModel
{
public void OnGet()
{
dependency.WriteMessage("IndexModel.OnGet called");
}
}
Используя шаблон DI, класс, потребляющий зависимость:
- не использует конкретный тип
MyDependency, только интерфейсIMyDependency, который он реализует. Это упрощает изменение реализации без изменения потребителя. - Не создает экземпляр
MyDependencyи не уничтожает его непосредственно. Зависимость создается и уничтожается контейнером службы.
Реализация IMyDependency интерфейса может быть улучшена с помощью встроенного API ведения журнала, который внедряется в качестве зависимости в следующем примере.
Services/MyDependency.cs:
public class MyDependency(ILogger<MyDependency> logger) : IMyDependency
{
public void WriteMessage(string message)
{
logger.LogInformation($"MyDependency.WriteMessage: {message}");
}
}
MyDependency
ILogger<TCategoryName>зависит от службы, предоставляемой платформой.
Обычно DI используется цепочечным образом. Каждая из запрашиваемых зависимостей поочерёдно запрашивает собственные зависимости. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу. Весь набор зависимостей, которые нужно разрешить, обычно называют деревом зависимостей, графом зависимостей или графом объектов.
Контейнер разрешает ILogger<TCategoryName>, используя преимущества (универсальных) открытых типов, что устраняет необходимость регистрации каждого (универсального) сконструированного типа.
В терминологии DI служба:
- Обычно это объект, предоставляющий службу другим объектам, таким как предыдущая
IMyDependencyслужба. - Не связан с веб-службой, хотя служба может использовать веб-службу.
Реализации IMyDependency , показанные в предыдущих примерах, были написаны для демонстрации общих принципов DI, а не для реализации ведения журнала. Большинству приложений не нужно создавать средства ведения журнала, как показано в предыдущих примерах. Следующий код демонстрирует прямое использование встроенного API логирования фреймворка, который не требует регистрации пользовательской службы (IMyDependency).
Pages/LoggingExample.razor:
@page "/logging-example"
@inject ILogger<LoggingExample> Logger
<button @onclick="WriteMessage">Write message</button>
@code {
private void WriteMessage() =>
Logger.LogInformation("LoggingExample.WriteMessage called");
}
Pages/IndexModel.cshtml.cs:
public class IndexModel(ILogger<IndexModel> logger) : PageModel
{
public void OnGet()
{
logger.LogInformation("IndexModel.OnGet called");
}
}
Службы, внедренные в Startup
Службы можно внедрить в конструктор Startup и метод Startup.Configure.
Универсальный узел (IHostBuilder) ASP.NET Core 3.0 или более поздней версии использует один контейнер службы в течение жизненного цикла приложения после того, как временный поставщик служб root использует основные службы для запуска и настройки основного контейнера службы приложения. Большинство служб, включая пользовательские службы и службы платформы, не участвующие в запуске узла, не настроены или доступны в контейнере службы при вызове конструктора Startup . При использовании универсального узла в конструктор Startup можно внедрить только следующие службы:
Ограничивая доступные услуги в конструкторе класса Startup, универсальный узел не позволяет использовать услугу до её создания или доступности, а также препятствует созданию нескольких экземпляров пользовательских или платформенных служб-одиночек, где служба-одиночка, созданная во временном контейнере служб, может отличаться от той, которая создана в окончательном контейнере служеб.
В метод можно внедрить любую зарегистрированную в контейнере службу Startup.Configure. В следующем примере внедряется элемент: ILogger<TCategoryName>
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
...
}
Дополнительные сведения см. в статьях Запуск приложения в ASP.NET Core и Доступ к конфигурации во время запуска.
Методы регистрации службы
Общие рекомендации по регистрации служб см. в разделе "Регистрация служб".
Обычно при выборе типов для тестирования используется несколько реализаций. Дополнительные сведения см. в разделе Интеграционные тесты в ASP.NET Core.
Регистрация службы только с типом реализации эквивалентна регистрации службы с тем же типом реализации и типа службы:
builder.Services.AddSingleton<MyDependency>();
services.AddSingleton<MyDependency>();
Методы регистрации службы можно использовать для регистрации нескольких экземпляров службы одного типа службы. В следующем примере метод AddSingleton вызывается дважды с типом службы IMyDependency. Второй вызов AddSingleton переопределяет предыдущий, если он разрешается как IMyDependency, и добавляет к предыдущему, если несколько служб разрешаются через IEnumerable<IMyDependency>.
builder.Services.AddSingleton<IMyDependency, MyDependency>();
builder.Services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Регистрация групп сервисов с помощью методов расширения
Соглашение в ASP.NET Core для регистрации группы связанных служб состоит в том, чтобы использовать один Add{GROUP NAME} метод расширения для регистрации всех служб, необходимых для функции платформы, где {GROUP NAME} заполнитель является описательным именем группы. Например, метод расширения AddRazorComponents регистрирует сервисы, необходимые для серверной отрисовки компонентов Razor.
Рассмотрим следующий пример, который настраивает параметры и регистрирует службы:
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
Связанные группы регистраций можно переместить в метод расширения для регистрации служб. В следующем примере :
- Метод
AddConfigрасширения привязывает данные конфигурации к строго типизированным классам C# и регистрирует классы в контейнере службы. - Метод
AddDependencyGroupрасширения добавляет дополнительные зависимости класса (службы).
namespace Microsoft.Extensions.DependencyInjection;
public static class ConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
Код ниже вызывает методы расширения AddConfig и AddDependencyGroup, чтобы зарегистрировать службы.
builder.Services
.AddConfig(builder.Configuration)
.AddDependencyGroup();
services
.AddConfig(builder.Configuration)
.AddDependencyGroup();
Мы рекомендуем приложениям следовать соглашению по именованию при создании методов расширения в пространстве имен Microsoft.Extensions.DependencyInjection, которые:
- Инкапсулирует группы регистраций сервисов.
- Предоставляет удобный доступ к службе с помощью IntelliSense.
Время существования службы
Общие рекомендации по времени существования службы см. в разделе "Время существования службы". Дополнительные рекомендации по времени существования службы, применимые к Blazor приложениям, см. в разделе внедрение зависимостей в ASP.NET Core Blazor.
Используйте службы ограниченной области в промежуточном ПО, применяя один из следующих подходов:
- Внедрите сервис в метод
InvokeилиInvokeAsyncПО промежуточного слоя. С помощью внедрите конструктор создается исключение времени выполнения, поскольку оно заставляет службу с заданной областью вести себя как одноэлементный объект. В примере в разделе Параметры времени существования и регистрации демонстрируется подходInvokeAsync. - Используйте фабричное промежуточное ПО. ПО промежуточного слоя, зарегистрированное с использованием этого подхода, активируется при каждом клиентском запросе (подключении), что позволяет внедрять службы с заданной областью в конструктор ПО промежуточного слоя.
Дополнительные сведения см. на следующих ресурсах:
- Написание пользовательского промежуточного ПО ASP.NET Core
- Дополнительные сведения об использовании ключевых служб с Razor компонентами см. в разделе о внедрении зависимостей ASP.NET CoreBlazor.
Сервисы, связанные с ключами
Ключи служб регистрируются и извлекаются службы с помощью ключей. Служба связана с ключом, вызывая любой из следующих методов расширения для регистрации службы:
В следующем примере показаны ключи служб с помощью следующего API:
-
IStringCache— это интерфейс службы с сигнатуройGetметода. -
StringCache1иStringCache2являются конкретными реализациями служб дляIStringCache.
Interfaces/IStringCache.cs:
public interface IStringCache
{
string Get(int key);
}
Services/StringCache1.cs:
public class StringCache1 : IStringCache
{
public string Get(int key) => $"Resolving {key} from StringCache1.";
}
Services/StringCache2.cs:
public class StringCache2 : IStringCache
{
public string Get(int key) => $"Resolving {key} from StringCache2.";
}
Доступ к зарегистрированной службе с помощью указания ключа через атрибут [FromKeyedServices].
Привязанный сервис и примеры конечных точек минимального API в файле Program.
builder.Services.AddKeyedSingleton<IStringCache, StringCache1>("cache1");
builder.Services.AddKeyedSingleton<IStringCache, StringCache2>("cache2");
...
app.MapGet("/cache1", ([FromKeyedServices("cache1")] IStringCache stringCache1) =>
stringCache1.Get(1));
app.MapGet("/cache2", ([FromKeyedServices("cache2")] IStringCache stringCache2) =>
stringCache2.Get(2));
Пример использования в компонменте Razor (Pages/KeyedServicesExample.razor) с помощью атрибута [Inject] . Используйте свойство InjectAttribute.Key, чтобы указать ключ для внедрения службы.
@page "/keyed-services-example"
@Cache?.Get(3)
@code {
[Inject(Key = "cache1")]
public IStringCache? Cache { get; set; }
}
Дополнительные сведения об использовании служебных ключей с Razor компонентами см. в разделе внедрение зависимостей в ASP.NET Core Blazor.
Пример использования в концентраторе SignalR (Hubs/MyHub1.cs) с внедрением первичного конструктора:
using Microsoft.AspNetCore.SignalR;
public class MyHub1([FromKeyedServices("cache2")] IStringCache cache) : Hub
{
public void Method()
{
Console.WriteLine(cache.Get(4));
}
}
Пример использования в концентраторе (SignalR) с внедрением метода (Hubs/MyHub2.cs):
using Microsoft.AspNetCore.SignalR;
public class MyHub2 : Hub
{
public void Method([FromKeyedServices("cache2")] IStringCache cache)
{
Console.WriteLine(cache.Get(5));
}
}
Промежуточное ПО поддерживает ключевые сервисы как в своем конструкторе, так и в методе Invoke/InvokeAsync.
internal class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next,
[FromKeyedServices("cache1")] IStringCache cache)
{
_next = next;
Console.WriteLine(cache.Get(6));
}
public Task Invoke(HttpContext context,
[FromKeyedServices("cache2")] IStringCache cache)
{
Console.WriteLine(cache.Get(7));
return _next(context);
}
}
В конвейере обработки приложения файла Program (.NET 6 или более поздней версии) или метода Startup.Configure (.NET 5 или более ранней версии):
app.UseMiddleware<MyMiddleware>();
Дополнительные сведения о создании пользовательского ПО промежуточного слоя см. в "Запись пользовательского ПО промежуточного слоя ASP.NET Core".
Поведение внедрения через конструктор
Дополнительные сведения о поведении внедрения зависимостей через конструктор см. на следующих ресурсах:
Контексты Entity Framework
Рекомендации по EF Core приложениям на стороне Blazor сервера см. в разделе ASP.NET Core Blazor с Entity Framework Core (EF Core).
По умолчанию контексты Entity Framework добавляются в контейнер службы с помощью ограниченного по области видимости времени существования, поскольку операции базы данных в веб-приложении обычно ограничены клиентским запросом. Чтобы использовать другое время существования, укажите его с помощью перегрузки AddDbContext. Службы с заданным временем жизни не должны использовать контекст базы данных с более коротким временем жизни, чем у самой службы.
Варианты срока действия и регистрации
Чтобы продемонстрировать различия между указанными вариантами времени существования и регистрации службы, рассмотрим интерфейсы, представляющие задачу в виде операции с идентификатором OperationId. В зависимости от того, как время существования службы операции настроено для следующих интерфейсов, контейнер предоставляет одинаковые или разные экземпляры службы при запросе класса.
IOperation.cs:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Следующий класс Operation реализует все предыдущие интерфейсы. Конструктор Operation создает GUID и сохраняет последние четыре символа в свойстве OperationId .
Operation.cs:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Следующий код создает несколько регистраций Operation класса в соответствии с именованными сроками существования.
Где зарегистрированы службы:
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
В следующем примере показано время существования объектов как внутри, так и между запросами. Компонент Operation и промежуточный слой IOperation запрашивают каждый тип IOperation и регистрируют OperationId для каждого.
Pages/OperationExample.razor:
@page "/operation-example"
@inject IOperationTransient TransientOperation
@inject IOperationScoped ScopedOperation
@inject IOperationSingleton SingletonOperation
<ul>
<li>Transient: @TransientOperation.OperationId</li>
<li>Scoped: @ScopedOperation.OperationId</li>
<li>Singleton: @SingletonOperation.OperationId</li>
</ul>
В следующем примере показано время существования объектов как внутри, так и между запросами.
IndexModel и промежуточное программное обеспечение запрашивают каждый тип IOperation и фиксируют OperationId для каждого.
IndexModel.cshtml.cs:
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationScoped _scopedOperation;
private readonly IOperationSingleton _singletonOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation($"Transient: {_transientOperation.OperationId}");
_logger.LogInformation($"Scoped: {_scopedOperation.OperationId}");
_logger.LogInformation($"Singleton: {_singletonOperation.OperationId}");
}
}
Промежуточное ПО также может разрешать и использовать те же службы. В методе InvokeAsync должны быть разрешены сервисы области видимости и временные.
MyMiddleware.cs:
public class MyMiddleware(ILogger<IndexModel> logger,
IOperationSingleton singletonOperation)
{
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
logger.LogInformation($"Transient: {transientOperation.OperationId}");
logger.LogInformation($"Scoped: {scopedOperation.OperationId}");
logger.LogInformation($"Singleton: {singletonOperation.OperationId}");
await _next(context);
}
}
public class MyMiddleware
{
private readonly ILogger<IndexModel> _logger;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(ILogger<IndexModel> logger,
IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
}
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation($"Transient: {transientOperation.OperationId}");
_logger.LogInformation($"Scoped: {scopedOperation.OperationId}");
_logger.LogInformation($"Singleton: {_singletonOperation.OperationId}");
await _next(context);
}
}
В конвейере обработки приложения для файла Program (.NET 6 или более поздней версии) или метода Startup.Configure (.NET 5 или более ранней версии):
app.UseMiddleware<MyMiddleware>();
Дополнительные сведения о создании пользовательского ПО промежуточного слоя см. в "Запись пользовательского ПО промежуточного слоя ASP.NET Core".
Выходные данные из предыдущих примеров показаны:
-
Временные объекты всегда разные. Временное
OperationIdзначение отличается для компонента Razor и в промежуточном слое. - Объекты с ограниченной областью видимости одинаковы для конкретного запроса, но отличаются между новыми Blazor контурами.
- Одноэлементные объекты одинаковы для каждого запроса или Blazor канала.
-
Временные объекты всегда разные. Временное
OperationIdзначение отличается для страницы и в промежуточном программном обеспечении. - Объекты Scope одинаковы для данного запроса, но отличаются в новых запросах.
- Одноэлементные объекты одинаковы для каждого запроса.
Разрешение службы при запуске приложения
В следующем коде показано, как разрешить службу с областью действия в течение ограниченного времени при запуске приложения:
var app = builder.Build();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var dependency = services.GetRequiredService<IMyDependency>();
dependency.WriteMessage("Call services from main");
}
Подтверждение границ
Рекомендации по проверке области см. в следующих ресурсах:
Запрос услуг
Службы и их зависимости в запросе ASP.NET Core предоставляются с помощью свойства HttpContext.RequestServices.
Фреймворк создает область для каждого запроса, а RequestServices предоставляет поставщика услуг в рамках области. Все службы с заданной областью действительны до тех пор, пока запрос активен.
Note
Предпочтительнее запрашивать зависимости в качестве параметров конструктора, а не получать службы из RequestServices. Запрос зависимостей в виде параметров конструктора приводит к созданию классов, которые проще тестировать.
Проектирование сервисов для внедрения зависимостей
При проектировании служб для DI:
- Избегайте статических классов и членов с отслеживанием состояния. Избегайте создания глобального состояния. Для этого проектируйте приложения для использования отдельных служб.
- Избегайте прямого создания экземпляров зависимых классов внутри служб. Прямое создание экземпляров связывает код с определенной реализацией.
- Сделайте службы небольшими, хорошо структурированными и легко тестируемыми.
Если класс имеет много внедренных зависимостей, это может быть признаком того, что класс имеет слишком много обязанностей и нарушает принцип единой ответственности (SRP). Попробуйте выполнить рефакторинг класса и перенести часть его обязанностей в новые классы. Помните, что в классах модели страниц Razor Pages и классах контроллера MVC должны преимущественно выполняться задачи, связанные с пользовательским интерфейсом.
Прекращение оказания услуг
Контейнер вызывает Dispose для создаваемых им типов IDisposable. Службы, разрешенные из контейнера, никогда не должны удаляться разработчиком. Если тип или фабрика зарегистрированы как синглтон, контейнер автоматически удаляет синглтон.
В следующем примере службы создаются контейнером сервисов и удаляются автоматически.
Services/Service1.cs:
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
{
return;
}
Console.WriteLine("Service1.Dispose");
_disposed = true;
GC.SuppressFinalize(this);
}
}
Services/Service2.cs:
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
{
return;
}
Console.WriteLine("Service2.Dispose");
_disposed = true;
GC.SuppressFinalize(this);
}
}
Services/Service3.cs:
public interface IService3
{
public void Write(string message);
}
public class Service3(string myKey) : IService3, IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, Key = {myKey}");
}
public void Dispose()
{
if (_disposed)
{
return;
}
Console.WriteLine("Service3.Dispose");
_disposed = true;
GC.SuppressFinalize(this);
}
}
В appsettings.Development.json:
"Key": "Value from appsettings.Development.json"
Где службы зарегистрированы приложением:
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var key = builder.Configuration["Key"] ?? string.Empty;
builder.Services.AddSingleton<IService3>(sp => new Service3(key));
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
var myKey = builder.Configuration["Key"] ?? string.Empty;
services.AddSingleton<IService3>(sp => new Service3(myKey));
Pages/DisposalExample.razor:
@page "/disposal-example"
@inject Service1 Service1
@inject Service2 Service2
@inject IService3 Service3
@code {
protected override void OnInitialized()
{
Service1.Write("DisposalExample.OnInitialized");
Service2.Write("DisposalExample.OnInitialized");
Service3.Write("DisposalExample.OnInitialized");
}
}
После каждого обновления страницы индекса в консоли отладки отображаются следующие выходные данные:
Service1: DisposalExample.OnInitialized
Service2: DisposalExample.OnInitialized
Service3: DisposalExample.OnInitialized, Key = Value from appsettings.Development.json
Service1.Dispose
Чтобы просмотреть запись для удаления Service1, перейдите из компонента DisposalExample, чтобы инициировать его удаление.
Pages/Index.cshtml.cs:
public class IndexModel(
Service1 service1, Service2 service2, IService3 service3)
: PageModel
{
public void OnGet()
{
service1.Write("IndexModel.OnGet");
service2.Write("IndexModel.OnGet");
service3.Write("IndexModel.OnGet");
}
}
После каждого обновления страницы индекса в консоли отладки отображаются следующие выходные данные:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, Key = Value from appsettings.Development.json
Service1.Dispose
Службы, не созданные контейнером службы
Рассмотрим следующий код:
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
services.AddSingleton(new Service1());
services.AddSingleton(new Service2());
В приведенном выше коде:
- Экземпляры служб не создаются контейнером.
- Платформа не удаляет службы автоматически.
- Разработчик отвечает за управление сервисами.
IDisposable руководство по временным и общим инстанциям
Для получения дополнительной информации см. руководство по внедрению зависимостей в .NET: рекомендации по IDisposable для временных и общих экземпляров.
Замена стандартного контейнера служб
Дополнительные сведения см. в статье об внедрении зависимостей в .NET: замена контейнера службы по умолчанию.
Recommendations
Дополнительные сведения см. в рекомендациях по внедрению зависимостей: Рекомендации.
Старайтесь не использовать шаблон локатора службы. Например, не вызывайте GetService для получения экземпляра службы, если можно использовать внедрение зависимостей (DI):
Incorrect:
Correct:
public class MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
{
public void MyMethod()
{
var option = optionsMonitor.CurrentValue.Option;
...
}
}
Другой вариацией паттерна локатора сервиса, которой следует избегать, является внедрение фабрики, решающей зависимости во время выполнения. Оба метода смешивают стратегии инверсии управления.
Избегайте статического доступа к HttpContext (например, IHttpContextAccessor.HttpContext).
DI является альтернативой для доступа к статическим или глобальным объектам. Возможно, вы не сможете реализовать преимущества DI, если вы смешиваете его со статическим доступом к объектам.
Рекомендуемые шаблоны для мультитенантности при внедрении зависимостей
Orchard Core — это платформа приложений для создания модульных мультитенантных приложений на ASP.NET Core. Дополнительные сведения см. в документации по Orchard Core.
Примеры создания модульных и мультитенантных приложений с использованием только платформы Orchard Core Framework без функций CMS см. в примерах Orchard Core.
Предоставляемые платформой службы
Файл Program (.NET 6 или более поздней версии) или Startup файл (.NET 5 или более ранний) регистрирует службы, используемые приложением, включая функции платформы, такие как Entity Framework Core и службы для поддержки Razor компонентов ( Blazor .NET 8 или более поздней версии). Изначально службы, определенные платформой, зависят от того, IServiceCollectionкак был настроен узел. Для приложений, основанных на шаблонах ASP.NET Core, платформа регистрирует более 250 служб.
В следующей таблице описывается небольшой пример служб, зарегистрированных в рамках.
| тип услуги; | Lifetime |
|---|---|
| Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Transient |
| IHostApplicationLifetime | Singleton |
| IWebHostEnvironment | Singleton |
| Microsoft.AspNetCore.Hosting.IStartup | Singleton |
| Microsoft.AspNetCore.Hosting.IStartupFilter | Transient |
| Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
| Microsoft.AspNetCore.Http.IHttpContextFactory | Transient |
| Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
| Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
| Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
| Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Transient |
| Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
| System.Diagnostics.DiagnosticSource | Singleton |
| System.Diagnostics.DiagnosticListener | Singleton |
Дополнительные ресурсы
- Внедрение зависимостей Blazor в ASP.NET Core
- Внедрение зависимостей в представления в ASP.NET Core
- Внедрение зависимостей в контроллеры в ASP.NET Core
- Внедрение зависимостей в обработчики требований в ASP.NET Core
- Шаблоны конференций NDC для разработки приложений с внедрением зависимостей
- Запуск приложения в ASP.NET Core
- Активация ПО промежуточного слоя на основе фабрики в ASP.NET Core
- Основные сведения о внедрении зависимостей в .NET
- Рекомендации по внедрению зависимостей
- Руководство. Использование внедрения зависимостей в .NET
- Внедрение зависимостей .NET
- ASP.NET внедрение основных зависимостей: что такое IServiceCollection?
- Четыре способа управления объектами IDisposable в ASP.NET Core
- Написание чистого кода в ASP.NET Core с внедрением зависимостей (MSDN)
- Принцип явных зависимостей
- Контейнеры с инверсией управления и паттерн внедрения зависимостей (Мартин Фаулер)
- Регистрация службы с несколькими интерфейсами в ASP.NET Core DI (Эндрю Lock)
- Избегайте внедрения службы Startup в ASP.NET Core 3 (Эндрю Lock)
ASP.NET Core