Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Важно!
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
ASP.NET Core поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями.
Инструкции Blazor по di, добавляющие или заменяющие инструкции в этой статье, смBlazor зависимостей Core.
Дополнительные сведения об использовании внедрения зависимостей в приложениях (кроме веб-приложений) см. в статье Внедрение зависимостей в .NET.
Сведения о внедрении зависимостей см. в шаблоне параметров в ASP.NET Core.
В этой статье содержатся сведения о внедрении зависимостей в ASP.NET Core. Основная документация по использованию внедрения зависимостей указана в статье Внедрение зависимостей в .NET.
Зависимость — это любой объект, от которого зависит другой объект. Рассмотрим следующий класс MyDependency с методом WriteMessage, от которого зависят другие классы:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Класс может создать экземпляр класса MyDependency, чтобы использовать его метод WriteMessage. В следующем примере класс MyDependency выступает зависимостью класса IndexModel:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Этот класс создает MyDependency и напрямую зависит от этого класса. Зависимости в коде, как в предыдущем примере, представляют собой определенную проблему. Их следует избегать по следующим причинам.
Чтобы заменить MyDependency другой реализацией, класс IndexModel необходимо изменить.
Если у MyDependency есть зависимости, их конфигурацию должен выполнять класс IndexModel. В больших проектах, когда от MyDependency зависят многие классы, код конфигурации растягивается по всему приложению.
Такая реализация плохо подходит для модульных тестов.
Внедрение зависимостей устраняет эти проблемы следующим образом:
Используется интерфейс или базовый класс для абстрагирования реализации зависимостей.
Зависимость регистрируется в контейнере служб. ASP.NET Core предоставляет встроенный контейнер служб, IServiceProvider. Службы обычно регистрируются в файле приложения Program.cs .
Служба внедряется в конструктор класса там, где он используется. Платформа берет на себя создание экземпляра зависимости и его удаление, когда он больше не нужен.
В примере приложения интерфейс IMyDependency определяет метод WriteMessage:
public interface IMyDependency
{
void WriteMessage(string message);
}
Этот интерфейс реализуется конкретным типом, MyDependency.
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Пример приложения регистрирует службу IMyDependency с конкретным типом MyDependency. Метод AddScoped регистрирует службу с заданной областью времени существования, временем существования одного запроса.
Сроки службы службы описаны ниже в этой статье.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
В примере приложения запрашивается служба IMyDependency, которая затем используется для вызова метода WriteMessage:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Используя шаблон внедрения зависимостей, контроллер или страницу Razor:
не использует конкретный тип MyDependency, только интерфейс IMyDependency, который он реализует. Это упрощает изменение реализации без изменения контроллера или страницы Razor.
не создает экземпляр MyDependency, он создается контейнером внедрения зависимостей.
Реализацию интерфейса IMyDependency можно улучшить с помощью встроенного API ведения журнала:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Обновленный метод Program.cs регистрирует новую реализацию IMyDependency:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
Использование цепочки внедрений зависимостей не является чем-то необычным. Каждая запрашиваемая зависимость запрашивает собственные зависимости. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу. Весь набор зависимостей, которые нужно разрешить, обычно называют деревом зависимостей, графом зависимостей или графом объектов.
Обычно является объектом, предоставляющим службу для других объектов, например службу IMyDependency.
Не связан с веб-службой, хотя служба может использовать веб-службу.
Платформа предоставляет эффективную систему ведения журнала. Реализации IMyDependency, приведенные в предыдущем примере были написаны для демонстрации базового внедрения зависимостей, а не для реализации ведения журнала. Большинству приложений не нужно писать средства ведения журнала. В следующем коде показано использование журнала по умолчанию, для которого не требуется регистрация служб:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Предыдущий код работает правильно, не изменяя ничего в Program.cs, так как ведение журнала предоставляется платформой.
Регистрация групп служб с помощью методов расширения
Для регистрации группы связанных служб на платформе ASP.NET Core используется соглашение. Соглашение заключается в использовании одного метода расширения Add{GROUP_NAME} для регистрации всех служб, необходимых компоненту платформы. Например, метод расширения AddControllers регистрирует службы, необходимые контроллерам MVC.
Следующий код создается шаблоном Razor Pages с использованием отдельных учетных записей пользователей. Он демонстрирует, как добавить дополнительные службы в контейнер с помощью методов расширения AddDbContext и AddDefaultIdentity:
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Рассмотрим следующий код, который регистрирует службы и настраивает параметры:
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
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>();
var app = builder.Build();
Связанные группы регистраций можно переместить в метод расширения для регистрации служб. Например, службы конфигурации добавляются в следующий класс:
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
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 AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
Остальные службы регистрируются в аналогичном классе. Следующий код использует новые методы расширения для регистрации служб:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Примечание. Каждый services.Add{GROUP_NAME} метод расширения добавляет и потенциально настраивает службы. Например, AddControllersWithViews добавляет контроллеры MVC служб с необходимыми представлениями, а AddRazorPages — службы, требуемые для работы Razor Pages.
Используйте службы с заданной областью в ПО промежуточного слоя, применяя один из следующих подходов:
Внедрите службу в метод Invoke или InvokeAsync ПО промежуточного слоя. С помощью внедрите конструктор создается исключение времени выполнения, поскольку оно заставляет службу с заданной областью вести себя как одноэлементный объект. В примере в разделе Параметры времени существования и регистрации демонстрируется подход InvokeAsync.
Используйте фабричное ПО промежуточного слоя. ПО промежуточного слоя, зарегистрированное с использованием этого подхода, активируется при каждом клиентском запросе (подключении), что позволяет внедрять службы с заданной областью в конструктор ПО промежуточного слоя.
Регистрация службы только с типом реализации эквивалентна регистрации этой службы с той же реализацией и типом службы. Именно поэтому несколько реализаций службы не могут быть зарегистрированы с помощью методов, которые не принимают явный тип службы. Эти методы могут регистрировать несколько экземпляров службы, но все они имеют одинаковый тип реализации.
Любой из этих методов регистрации службы можно использовать для регистрации нескольких экземпляров службы одного типа службы. В следующем примере метод AddSingleton вызывается дважды с типом службы IMyDependency. Второй вызов AddSingleton переопределяет предыдущий, если он разрешается как IMyDependency, и добавляет к предыдущему, если несколько служб разрешаются через IEnumerable<IMyDependency>. Службы отображаются в том порядке, в котором они были зарегистрированы при разрешении через IEnumerable<{SERVICE}>.
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);
}
}
Ключи служб
Термин «службы с ключами» относится к механизму регистрации и поиска служб внедрения зависимостей с использованием ключей. Служба связана с ключом путем вызова AddKeyedSingleton (или AddKeyedScopedAddKeyedTransient) для регистрации. Доступ к зарегистрированной службе путем указания ключа с атрибутом [FromKeyedServices] . В следующем коде показано, как использовать ключи служб:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
smallCache.Get("date"));
app.MapControllers();
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
{
return cache.Get("data-mvc");
}
}
public class MyHub : Hub
{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
Ключи служб в ПО промежуточного слоя
ПО промежуточного слоя поддерживает службы Keyed как в конструкторе, так и в методе Invoke/InvokeAsync :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");
var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();
internal class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next,
[FromKeyedServices("test")] MySingletonClass service)
{
_next = next;
}
public Task Invoke(HttpContext context,
[FromKeyedServices("test2")]
MyScopedClass scopedService) => _next(context);
}
По умолчанию контексты Entity Framework добавляются в контейнер службы с помощью времени существования с заданной областью, поскольку операции базы данных в веб-приложении обычно относятся к области клиентского запроса. Чтобы использовать другое время существования, укажите его с помощью перегрузки AddDbContext. Службы данного времени существования не должны использовать контекст базы данных с временем существования короче, чем у службы.
Параметры времени существования и регистрации
Чтобы продемонстрировать различия между указанными вариантами времени существования и регистрации службы, рассмотрим интерфейсы, представляющие задачу в виде операции с идентификатором OperationId. В зависимости от того, как время существования службы операции настроено для этих интерфейсов, при запросе из класса контейнер предоставляет тот же или другой экземпляр службы.
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Следующий класс Operation реализует все предыдущие интерфейсы. Конструктор Operation создает идентификатор GUID и сохраняет последние 4 символа в свойстве OperationId:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Следующий код создает несколько регистраций класса Operation в соответствии с именованным временем существования:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
В примере приложения показано время существования объектов в пределах запросов и между запросами.
IndexModel и ПО промежуточного слоя запрашивают каждый тип IOperation и регистрируют OperationId для каждого из них:
Службы и их зависимости в запросе ASP.NET Core предоставляются с помощью свойства HttpContext.RequestServices.
Платформа создает область для каждого запроса, а RequestServices предоставляет поставщик услуг с заданной областью. Все службы с заданной областью действительны до тех пор, пока запрос активен.
Примечание
Предпочтительнее запрашивать зависимости в качестве параметров конструктора, а не разрешать службы из RequestServices. Таким образом вы получите классы, которые проще тестировать.
Проектирование служб для внедрения зависимостей
При разработке служб для внедрения зависимостей придерживайтесь следующих рекомендаций:
Избегайте статических классов и членов с отслеживанием состояния. Избегайте создания глобального состояния. Для этого проектируйте приложения для использования отдельных служб.
Избегайте прямого создания экземпляров зависимых классов внутри служб. Прямое создание экземпляров обязывает использовать в коде определенную реализацию.
Сделайте службы приложения небольшими, хорошо организованными и удобными в тестировании.
Если класс имеет много внедренных зависимостей, это может быть признаком того, что класс имеет слишком много обязанностей и нарушает принцип единой ответственности (SRP). Попробуйте выполнить рефакторинг класса и перенести часть его обязанностей в новые классы. Помните, что в классах модели страниц Razor Pages и классах контроллера MVC должны преимущественно выполняться задачи, связанные с пользовательским интерфейсом.
Удаление служб
Контейнер вызывает Dispose для создаваемых им типов IDisposable. Службы, разрешенные из контейнера, никогда не должны удаляться разработчиком. Если тип или фабрика зарегистрированы как одноэлементный объект, контейнер автоматически удалит одноэлементные объекты.
В следующем примере службы создаются контейнером службы и автоматически удаляются:
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;
}
}
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;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
Старайтесь не использовать шаблон обнаружения служб. Например, не вызывайте GetService для получения экземпляра службы, когда можно использовать внедрение зависимостей:
Неправильно:
Правильное.
public class MyClass
{
private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
}
public void MyMethod()
{
var option = _optionsMonitor.CurrentValue.Option;
...
}
}
Другой вариант указателя службы, позволяющий избежать этого, — внедрение фабрики, которая разрешает зависимости во время выполнения. Оба метода смешивают стратегии инверсии управления.
Внедрение зависимостей является альтернативой для шаблонов доступа к статическим или глобальным объектам. Возможно, вы не сможете реализовать преимущества DI, если вы смешиваете его со статическим доступом к объектам.
Рекомендуемые подходы к мультитенантности при внедрении зависимостей
Orchard Core — это платформа приложений для создания модульных мультитенантных приложений на ASP.NET Core. Дополнительные сведения см. в документации по Orchard Core.
Примеры создания модульных и мультитенантных приложений с использованием только Orchard Core Framework без каких-либо особых функций CMS см. здесь.
Платформенные службы
Program.cs регистрирует службы, которые использует приложение, включая такие компоненты, как Entity Framework Core и ASP.NET Core MVC. Изначально коллекция IServiceCollection, предоставленная для Program.cs, содержит определенные платформой службы (в зависимости от настройки узла). Для приложений, основанных на шаблонах ASP.NET Core, платформа регистрирует более 250 служб.
В следующей таблице перечислены некоторые примеры этих зарегистрированных платформой служб.
ASP.NET Core поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями.
В этой статье приводятся сведения о внедрении зависимостей в ASP.NET Core. Основная документация по использованию внедрения зависимостей указана в статье Внедрение зависимостей в .NET.
Зависимость — это любой объект, от которого зависит другой объект. Рассмотрим следующий класс MyDependency с методом WriteMessage, от которого зависят другие классы:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Класс может создать экземпляр класса MyDependency, чтобы использовать его метод WriteMessage. В следующем примере класс MyDependency выступает зависимостью класса IndexModel:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Этот класс создает MyDependency и напрямую зависит от этого класса. Зависимости в коде, как в предыдущем примере, представляют собой определенную проблему. Их следует избегать по следующим причинам.
Чтобы заменить MyDependency другой реализацией, класс IndexModel необходимо изменить.
Если у MyDependency есть зависимости, их конфигурацию должен выполнять класс IndexModel. В больших проектах, когда от MyDependency зависят многие классы, код конфигурации растягивается по всему приложению.
Такая реализация плохо подходит для модульных тестов.
Внедрение зависимостей устраняет эти проблемы следующим образом:
Используется интерфейс или базовый класс для абстрагирования реализации зависимостей.
Зависимость регистрируется в контейнере служб. ASP.NET Core предоставляет встроенный контейнер служб, IServiceProvider. Службы обычно регистрируются в файле приложения Program.cs .
Служба внедряется в конструктор класса там, где он используется. Платформа берет на себя создание экземпляра зависимости и его удаление, когда он больше не нужен.
В примере приложения интерфейс IMyDependency определяет метод WriteMessage:
public interface IMyDependency
{
void WriteMessage(string message);
}
Этот интерфейс реализуется конкретным типом, MyDependency.
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Пример приложения регистрирует службу IMyDependency с конкретным типом MyDependency. Метод AddScoped регистрирует службу с заданной областью времени существования, временем существования одного запроса. Подробнее о времени существования служб мы поговорим далее в этой статье.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
В примере приложения запрашивается служба IMyDependency, которая затем используется для вызова метода WriteMessage:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Используя шаблон внедрения зависимостей, контроллер или страницу Razor:
не использует конкретный тип MyDependency, только интерфейс IMyDependency, который он реализует. Это упрощает изменение реализации без изменения контроллера или страницы Razor.
не создает экземпляр MyDependency, он создается контейнером внедрения зависимостей.
Реализацию интерфейса IMyDependency можно улучшить с помощью встроенного API ведения журнала:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Обновленный метод Program.cs регистрирует новую реализацию IMyDependency:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
Использование цепочки внедрений зависимостей не является чем-то необычным. Каждая запрашиваемая зависимость запрашивает собственные зависимости. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу. Весь набор зависимостей, которые нужно разрешить, обычно называют деревом зависимостей, графом зависимостей или графом объектов.
Обычно является объектом, предоставляющим службу для других объектов, например службу IMyDependency.
Не относится к веб-службе, хотя служба может использовать веб-службу.
Платформа предоставляет эффективную систему ведения журнала. Реализации IMyDependency, приведенные в предыдущем примере были написаны для демонстрации базового внедрения зависимостей, а не для реализации ведения журнала. Большинству приложений не нужно писать средства ведения журнала. В следующем коде показано использование журнала по умолчанию, для которого не требуется регистрация служб:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Используя приведенный выше код, не нужно обновлять Program.cs, поскольку платформа предоставляет возможность ведения журнала.
Регистрация групп служб с помощью методов расширения
Для регистрации группы связанных служб на платформе ASP.NET Core используется соглашение. Соглашение заключается в использовании одного метода расширения Add{GROUP_NAME} для регистрации всех служб, необходимых компоненту платформы. Например, метод расширения AddControllers регистрирует службы, необходимые контроллерам MVC.
Следующий код создается шаблоном Razor Pages с использованием отдельных учетных записей пользователей. Он демонстрирует, как добавить дополнительные службы в контейнер с помощью методов расширения AddDbContext и AddDefaultIdentity:
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Рассмотрим следующий код, который регистрирует службы и настраивает параметры:
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
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>();
var app = builder.Build();
Связанные группы регистраций можно переместить в метод расширения для регистрации служб. Например, службы конфигурации добавляются в следующий класс:
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
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 AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
Остальные службы регистрируются в аналогичном классе. Следующий код использует новые методы расширения для регистрации служб:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Примечание. Каждый services.Add{GROUP_NAME} метод расширения добавляет и потенциально настраивает службы. Например, AddControllersWithViews добавляет контроллеры MVC служб с необходимыми представлениями, а AddRazorPages — службы, требуемые для работы Razor Pages.
Используйте службы с заданной областью в ПО промежуточного слоя, применяя один из следующих подходов:
Внедрите службу в метод Invoke или InvokeAsync ПО промежуточного слоя. С помощью внедрите конструктор создается исключение времени выполнения, поскольку оно заставляет службу с заданной областью вести себя как одноэлементный объект. В примере в разделе Параметры времени существования и регистрации демонстрируется подход InvokeAsync.
Используйте фабричное ПО промежуточного слоя. ПО промежуточного слоя, зарегистрированное с использованием этого подхода, активируется при каждом клиентском запросе (подключении), что позволяет внедрять службы с заданной областью в конструктор ПО промежуточного слоя.
Регистрация службы только с типом реализации эквивалентна регистрации этой службы с той же реализацией и типом службы. Именно поэтому несколько реализаций службы не могут быть зарегистрированы с помощью методов, которые не принимают явный тип службы. Эти методы могут регистрировать несколько экземпляров службы, но все они будут иметь одинаковую реализацию типа.
Любой из указанных выше методов регистрации службы можно использовать для регистрации нескольких экземпляров службы одного типа службы. В следующем примере метод AddSingleton вызывается дважды с типом службы IMyDependency. Второй вызов AddSingleton переопределяет предыдущий, если он разрешается как IMyDependency, и добавляет к предыдущему, если несколько служб разрешаются через IEnumerable<IMyDependency>. Службы отображаются в том порядке, в котором они были зарегистрированы при разрешении через IEnumerable<{SERVICE}>.
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);
}
}
Ключи служб
Ключи служб относятся к механизму регистрации и получения служб внедрения зависимостей (DI) с помощью ключей. Служба связана с ключом путем вызова AddKeyedSingleton (или AddKeyedScopedAddKeyedTransient) для регистрации. Доступ к зарегистрированной службе путем указания ключа с атрибутом [FromKeyedServices] . В следующем коде показано, как использовать ключи служб:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
smallCache.Get("date"));
app.MapControllers();
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
{
return cache.Get("data-mvc");
}
}
public class MyHub : Hub
{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
По умолчанию контексты Entity Framework добавляются в контейнер службы с помощью времени существования с заданной областью, поскольку операции базы данных в веб-приложении обычно относятся к области клиентского запроса. Чтобы использовать другое время существования, укажите его с помощью перегрузки AddDbContext. Службы данного времени существования не должны использовать контекст базы данных с временем существования короче, чем у службы.
Параметры времени существования и регистрации
Чтобы продемонстрировать различия между указанными вариантами времени существования и регистрации службы, рассмотрим интерфейсы, представляющие задачу в виде операции с идентификатором OperationId. В зависимости от того, как время существования службы операции настроено для этих интерфейсов, при запросе из класса контейнер предоставляет тот же или другой экземпляр службы.
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Следующий класс Operation реализует все предыдущие интерфейсы. Конструктор Operation создает идентификатор GUID и сохраняет последние 4 символа в свойстве OperationId:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Следующий код создает несколько регистраций класса Operation в соответствии с именованным временем существования:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
В примере приложения показано время существования объектов в пределах запросов и между запросами.
IndexModel и ПО промежуточного слоя запрашивают каждый тип IOperation и регистрируют OperationId для каждого из них:
Службы и их зависимости в запросе ASP.NET Core предоставляются с помощью свойства HttpContext.RequestServices.
Платформа создает область для каждого запроса, а RequestServices предоставляет поставщик услуг с заданной областью. Все службы с заданной областью действительны до тех пор, пока запрос активен.
Примечание
Предпочтительнее запрашивать зависимости в качестве параметров конструктора, а не разрешать службы из RequestServices. Таким образом вы получите классы, которые проще тестировать.
Проектирование служб для внедрения зависимостей
При разработке служб для внедрения зависимостей придерживайтесь следующих рекомендаций:
Избегайте статических классов и членов с отслеживанием состояния. Избегайте создания глобального состояния. Для этого проектируйте приложения для использования отдельных служб.
Избегайте прямого создания экземпляров зависимых классов внутри служб. Прямое создание экземпляров обязывает использовать в коде определенную реализацию.
Сделайте службы приложения небольшими, хорошо организованными и удобными в тестировании.
Если класс имеет слишком много внедренных зависимостей, это может указывать на то, что у класса слишком много задач и он нарушает принцип единственной обязанности. Попробуйте выполнить рефакторинг класса и перенести часть его обязанностей в новые классы. Помните, что в классах модели страниц Razor Pages и классах контроллера MVC должны преимущественно выполняться задачи, связанные с пользовательским интерфейсом.
Удаление служб
Контейнер вызывает Dispose для создаваемых им типов IDisposable. Службы, разрешенные из контейнера, никогда не должны удаляться разработчиком. Если тип или фабрика зарегистрированы как одноэлементный объект, контейнер автоматически удалит одноэлементные объекты.
В следующем примере службы создаются контейнером службы и удаляются автоматически: dependency-injection\samples\6.x\DIsample2\DIsample2\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;
}
}
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;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
Старайтесь не использовать шаблон обнаружения служб. Например, не вызывайте GetService для получения экземпляра службы, когда можно использовать внедрение зависимостей:
Неправильно:
Правильное.
public class MyClass
{
private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
}
public void MyMethod()
{
var option = _optionsMonitor.CurrentValue.Option;
...
}
}
Другой вариант указателя службы, позволяющий избежать этого, — внедрение фабрики, которая разрешает зависимости во время выполнения. Оба метода смешивают стратегии инверсии управления.
Внедрение зависимостей является альтернативой для шаблонов доступа к статическим или глобальным объектам. Вы не сможете воспользоваться преимуществами внедрения зависимостей, если будете сочетать его с доступом к статическим объектам.
Рекомендуемые подходы к мультитенантности при внедрении зависимостей
Orchard Core — это платформа приложений для создания модульных мультитенантных приложений в ASP.NET Core. Дополнительные сведения см. в документации по Orchard Core.
Примеры создания модульных и мультитенантных приложений с использованием только Orchard Core Framework без каких-либо особых функций CMS см. здесь.
Платформенные службы
Program.cs регистрирует службы, которые использует приложение, включая такие компоненты, как Entity Framework Core и ASP.NET Core MVC. Изначально коллекция IServiceCollection, предоставленная для Program.cs, содержит определенные платформой службы (в зависимости от настройки узла). Для приложений, основанных на шаблонах ASP.NET Core, платформа регистрирует более 250 служб.
В следующей таблице перечислены некоторые примеры этих зарегистрированных платформой служб.
ASP.NET Core поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями.
В этой статье приводятся сведения о внедрении зависимостей в ASP.NET Core. Основная документация по использованию внедрения зависимостей указана в статье Внедрение зависимостей в .NET.
Зависимость — это любой объект, от которого зависит другой объект. Рассмотрим следующий класс MyDependency с методом WriteMessage, от которого зависят другие классы:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Класс может создать экземпляр класса MyDependency, чтобы использовать его метод WriteMessage. В следующем примере класс MyDependency выступает зависимостью класса IndexModel:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Этот класс создает MyDependency и напрямую зависит от этого класса. Зависимости в коде, как в предыдущем примере, представляют собой определенную проблему. Их следует избегать по следующим причинам.
Чтобы заменить MyDependency другой реализацией, класс IndexModel необходимо изменить.
Если у MyDependency есть зависимости, их конфигурацию должен выполнять класс IndexModel. В больших проектах, когда от MyDependency зависят многие классы, код конфигурации растягивается по всему приложению.
Такая реализация плохо подходит для модульных тестов.
Внедрение зависимостей устраняет эти проблемы следующим образом:
Используется интерфейс или базовый класс для абстрагирования реализации зависимостей.
Зависимость регистрируется в контейнере служб. ASP.NET Core предоставляет встроенный контейнер служб, IServiceProvider. Службы обычно регистрируются в файле приложения Program.cs .
Служба внедряется в конструктор класса там, где он используется. Платформа берет на себя создание экземпляра зависимости и его удаление, когда он больше не нужен.
В примере приложения интерфейс IMyDependency определяет метод WriteMessage:
public interface IMyDependency
{
void WriteMessage(string message);
}
Этот интерфейс реализуется конкретным типом, MyDependency.
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Пример приложения регистрирует службу IMyDependency с конкретным типом MyDependency. Метод AddScoped регистрирует службу с заданной областью времени существования, временем существования одного запроса. Подробнее о времени существования служб мы поговорим далее в этой статье.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
В примере приложения запрашивается служба IMyDependency, которая затем используется для вызова метода WriteMessage:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Используя шаблон внедрения зависимостей, контроллер или страницу Razor:
не использует конкретный тип MyDependency, только интерфейс IMyDependency, который он реализует. Это упрощает изменение реализации без изменения контроллера или страницы Razor.
не создает экземпляр MyDependency, он создается контейнером внедрения зависимостей.
Реализацию интерфейса IMyDependency можно улучшить с помощью встроенного API ведения журнала:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Обновленный метод Program.cs регистрирует новую реализацию IMyDependency:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
Использование цепочки внедрений зависимостей не является чем-то необычным. Каждая запрашиваемая зависимость запрашивает собственные зависимости. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу. Весь набор зависимостей, которые нужно разрешить, обычно называют деревом зависимостей, графом зависимостей или графом объектов.
Обычно является объектом, предоставляющим службу для других объектов, например службу IMyDependency.
Не относится к веб-службе, хотя служба может использовать веб-службу.
Платформа предоставляет эффективную систему ведения журнала. Реализации IMyDependency, приведенные в предыдущем примере были написаны для демонстрации базового внедрения зависимостей, а не для реализации ведения журнала. Большинству приложений не нужно писать средства ведения журнала. В следующем коде показано использование журнала по умолчанию, для которого не требуется регистрация служб:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Используя приведенный выше код, не нужно обновлять Program.cs, поскольку платформа предоставляет возможность ведения журнала.
Регистрация групп служб с помощью методов расширения
Для регистрации группы связанных служб на платформе ASP.NET Core используется соглашение. Соглашение заключается в использовании одного метода расширения Add{GROUP_NAME} для регистрации всех служб, необходимых компоненту платформы. Например, метод расширения AddControllers регистрирует службы, необходимые контроллерам MVC.
Следующий код создается шаблоном Razor Pages с использованием отдельных учетных записей пользователей. Он демонстрирует, как добавить дополнительные службы в контейнер с помощью методов расширения AddDbContext и AddDefaultIdentity:
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Рассмотрим следующий код, который регистрирует службы и настраивает параметры:
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
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>();
var app = builder.Build();
Связанные группы регистраций можно переместить в метод расширения для регистрации служб. Например, службы конфигурации добавляются в следующий класс:
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
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 AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
Остальные службы регистрируются в аналогичном классе. Следующий код использует новые методы расширения для регистрации служб:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Примечание. Каждый services.Add{GROUP_NAME} метод расширения добавляет и потенциально настраивает службы. Например, AddControllersWithViews добавляет контроллеры MVC служб с необходимыми представлениями, а AddRazorPages — службы, требуемые для работы Razor Pages.
Используйте службы с заданной областью в ПО промежуточного слоя, применяя один из следующих подходов:
Внедрите службу в метод Invoke или InvokeAsync ПО промежуточного слоя. С помощью внедрите конструктор создается исключение времени выполнения, поскольку оно заставляет службу с заданной областью вести себя как одноэлементный объект. В примере в разделе Параметры времени существования и регистрации демонстрируется подход InvokeAsync.
Используйте фабричное ПО промежуточного слоя. ПО промежуточного слоя, зарегистрированное с использованием этого подхода, активируется при каждом клиентском запросе (подключении), что позволяет внедрять службы с заданной областью в конструктор ПО промежуточного слоя.
Регистрация службы только с типом реализации эквивалентна регистрации этой службы с той же реализацией и типом службы. Именно поэтому несколько реализаций службы не могут быть зарегистрированы с помощью методов, которые не принимают явный тип службы. Эти методы могут регистрировать несколько экземпляров службы, но все они будут иметь одинаковую реализацию типа.
Любой из указанных выше методов регистрации службы можно использовать для регистрации нескольких экземпляров службы одного типа службы. В следующем примере метод AddSingleton вызывается дважды с типом службы IMyDependency. Второй вызов AddSingleton переопределяет предыдущий, если он разрешается как IMyDependency, и добавляет к предыдущему, если несколько служб разрешаются через IEnumerable<IMyDependency>. Службы отображаются в том порядке, в котором они были зарегистрированы при разрешении через IEnumerable<{SERVICE}>.
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);
}
}
По умолчанию контексты Entity Framework добавляются в контейнер службы с помощью времени существования с заданной областью, поскольку операции базы данных в веб-приложении обычно относятся к области клиентского запроса. Чтобы использовать другое время существования, укажите его с помощью перегрузки AddDbContext. Службы данного времени существования не должны использовать контекст базы данных с временем существования короче, чем у службы.
Параметры времени существования и регистрации
Чтобы продемонстрировать различия между указанными вариантами времени существования и регистрации службы, рассмотрим интерфейсы, представляющие задачу в виде операции с идентификатором OperationId. В зависимости от того, как время существования службы операции настроено для этих интерфейсов, при запросе из класса контейнер предоставляет тот же или другой экземпляр службы.
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Следующий класс Operation реализует все предыдущие интерфейсы. Конструктор Operation создает идентификатор GUID и сохраняет последние 4 символа в свойстве OperationId:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Следующий код создает несколько регистраций класса Operation в соответствии с именованным временем существования:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
В примере приложения показано время существования объектов в пределах запросов и между запросами.
IndexModel и ПО промежуточного слоя запрашивают каждый тип IOperation и регистрируют OperationId для каждого из них:
Службы и их зависимости в запросе ASP.NET Core предоставляются с помощью свойства HttpContext.RequestServices.
Платформа создает область для каждого запроса, а RequestServices предоставляет поставщик услуг с заданной областью. Все службы с заданной областью действительны до тех пор, пока запрос активен.
Примечание
Предпочтительнее запрашивать зависимости в качестве параметров конструктора, а не разрешать службы из RequestServices. Таким образом вы получите классы, которые проще тестировать.
Проектирование служб для внедрения зависимостей
При разработке служб для внедрения зависимостей придерживайтесь следующих рекомендаций:
Избегайте статических классов и членов с отслеживанием состояния. Избегайте создания глобального состояния. Для этого проектируйте приложения для использования отдельных служб.
Избегайте прямого создания экземпляров зависимых классов внутри служб. Прямое создание экземпляров обязывает использовать в коде определенную реализацию.
Сделайте службы приложения небольшими, хорошо организованными и удобными в тестировании.
Если класс имеет слишком много внедренных зависимостей, это может указывать на то, что у класса слишком много задач и он нарушает принцип единственной обязанности. Попробуйте выполнить рефакторинг класса и перенести часть его обязанностей в новые классы. Помните, что в классах модели страниц Razor Pages и классах контроллера MVC должны преимущественно выполняться задачи, связанные с пользовательским интерфейсом.
Удаление служб
Контейнер вызывает Dispose для создаваемых им типов IDisposable. Службы, разрешенные из контейнера, никогда не должны удаляться разработчиком. Если тип или фабрика зарегистрированы как одноэлементный объект, контейнер автоматически удалит одноэлементные объекты.
В следующем примере службы создаются контейнером службы и удаляются автоматически: dependency-injection\samples\6.x\DIsample2\DIsample2\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;
}
}
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;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
Старайтесь не использовать шаблон обнаружения служб. Например, не вызывайте GetService для получения экземпляра службы, когда можно использовать внедрение зависимостей:
Неправильно:
Правильное.
public class MyClass
{
private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
}
public void MyMethod()
{
var option = _optionsMonitor.CurrentValue.Option;
...
}
}
Другой вариант указателя службы, позволяющий избежать этого, — внедрение фабрики, которая разрешает зависимости во время выполнения. Оба метода смешивают стратегии инверсии управления.
Внедрение зависимостей является альтернативой для шаблонов доступа к статическим или глобальным объектам. Вы не сможете воспользоваться преимуществами внедрения зависимостей, если будете сочетать его с доступом к статическим объектам.
Рекомендуемые подходы к мультитенантности при внедрении зависимостей
Orchard Core — это платформа приложений для создания модульных мультитенантных приложений в ASP.NET Core. Дополнительные сведения см. в документации по Orchard Core.
Примеры создания модульных и мультитенантных приложений с использованием только Orchard Core Framework без каких-либо особых функций CMS см. здесь.
Платформенные службы
Program.cs регистрирует службы, которые использует приложение, включая такие компоненты, как Entity Framework Core и ASP.NET Core MVC. Изначально коллекция IServiceCollection, предоставленная для Program.cs, содержит определенные платформой службы (в зависимости от настройки узла). Для приложений, основанных на шаблонах ASP.NET Core, платформа регистрирует более 250 служб.
В следующей таблице перечислены некоторые примеры этих зарегистрированных платформой служб.
ASP.NET Core поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями.
В этой статье приводятся сведения о внедрении зависимостей в ASP.NET Core. Основная документация по использованию внедрения зависимостей указана в статье Внедрение зависимостей в .NET.
Зависимость — это любой объект, от которого зависит другой объект. Рассмотрим следующий класс MyDependency с методом WriteMessage, от которого зависят другие классы:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Класс может создать экземпляр класса MyDependency, чтобы использовать его метод WriteMessage. В следующем примере класс MyDependency выступает зависимостью класса IndexModel:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet created this message.");
}
}
Этот класс создает MyDependency и напрямую зависит от этого класса. Зависимости в коде, как в предыдущем примере, представляют собой определенную проблему. Их следует избегать по следующим причинам.
Чтобы заменить MyDependency другой реализацией, класс IndexModel необходимо изменить.
Если у MyDependency есть зависимости, их конфигурацию должен выполнять класс IndexModel. В больших проектах, когда от MyDependency зависят многие классы, код конфигурации растягивается по всему приложению.
Такая реализация плохо подходит для модульных тестов. В приложении нужно использовать имитацию или заглушку в виде класса MyDependency, что при таком подходе невозможно.
Внедрение зависимостей устраняет эти проблемы следующим образом:
Используется интерфейс или базовый класс для абстрагирования реализации зависимостей.
Зависимость регистрируется в контейнере служб. ASP.NET Core предоставляет встроенный контейнер служб, IServiceProvider. Как правило, службы регистрируются в приложении в методе Startup.ConfigureServices.
Служба внедряется в конструктор класса там, где он используется. Платформа берет на себя создание экземпляра зависимости и его удаление, когда он больше не нужен.
В примере приложения интерфейс IMyDependency определяет метод WriteMessage:
public interface IMyDependency
{
void WriteMessage(string message);
}
Этот интерфейс реализуется конкретным типом, MyDependency.
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Пример приложения регистрирует службу IMyDependency с конкретным типом MyDependency. Метод AddScoped регистрирует службу с заданной областью времени существования, временем существования одного запроса. Подробнее о времени существования служб мы поговорим далее в этой статье.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddRazorPages();
}
В примере приложения запрашивается служба IMyDependency, которая затем используется для вызова метода WriteMessage:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Используя шаблон внедрения зависимостей, контроллер:
не использует конкретный тип MyDependency, только интерфейс IMyDependency, который он реализует. Это упрощает изменение реализации, используемой контроллером, без изменения контроллера.
не создает экземпляр MyDependency, он создается контейнером внедрения зависимостей.
Реализацию интерфейса IMyDependency можно улучшить с помощью встроенного API ведения журнала:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Обновленный метод ConfigureServices регистрирует новую реализацию IMyDependency:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency2>();
services.AddRazorPages();
}
Использование цепочки внедрений зависимостей не является чем-то необычным. Каждая запрашиваемая зависимость запрашивает собственные зависимости. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу. Весь набор зависимостей, которые нужно разрешить, обычно называют деревом зависимостей, графом зависимостей или графом объектов.
Обычно является объектом, предоставляющим службу для других объектов, например службу IMyDependency.
Не относится к веб-службе, хотя служба может использовать веб-службу.
Платформа предоставляет эффективную систему ведения журнала. Реализации IMyDependency, приведенные в предыдущем примере были написаны для демонстрации базового внедрения зависимостей, а не для реализации ведения журнала. Большинству приложений не нужно писать средства ведения журнала. В следующем коде показано использование журнала по умолчанию, для которого не требуется регистрация служб в ConfigureServices:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; }
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Используя приведенный выше код, не нужно обновлять ConfigureServices, поскольку платформа предоставляет возможность ведения журнала.
Службы, внедренные в конструктор Startup
Службы можно внедрить в конструктор Startup и метод Startup.Configure.
При использовании универсального узла (Startup) в конструктор IHostBuilder могут внедряться только следующие службы:
Регистрация групп служб с помощью методов расширения
Для регистрации группы связанных служб на платформе ASP.NET Core используется соглашение. Соглашение заключается в использовании одного метода расширения Add{GROUP_NAME} для регистрации всех служб, необходимых компоненту платформы. Например, метод расширения AddControllers регистрирует службы, необходимые контроллерам MVC.
Следующий код создается шаблоном Razor Pages с использованием отдельных учетных записей пользователей. Он демонстрирует, как добавить дополнительные службы в контейнер с помощью методов расширения AddDbContext и AddDefaultIdentity:
Связанные группы регистраций можно переместить в метод расширения для регистрации служб. Например, службы конфигурации добавляются в следующий класс:
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
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 AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
Остальные службы регистрируются в аналогичном классе. Следующий метод ConfigureServices использует новые методы расширения для регистрации служб:
public void ConfigureServices(IServiceCollection services)
{
services.AddConfig(Configuration)
.AddMyDependencyGroup();
services.AddRazorPages();
}
Примечание. Каждый services.Add{GROUP_NAME} метод расширения добавляет и потенциально настраивает службы. Например, AddControllersWithViews добавляет контроллеры MVC служб с необходимыми представлениями, а AddRazorPages — службы, требуемые для работы Razor Pages. Рекомендуется соблюдать в приложениях соглашение об именовании создаваемых методов расширения в пространстве имен Microsoft.Extensions.DependencyInjection. Создание методов расширения в пространстве имен Microsoft.Extensions.DependencyInjection:
Инкапсулирует группы регистраций служб.
Предоставляет удобный доступ к службе с помощью IntelliSense.
Используйте службы с заданной областью в ПО промежуточного слоя, применяя один из следующих подходов:
Внедрите службу в метод Invoke или InvokeAsync ПО промежуточного слоя. С помощью внедрите конструктор создается исключение времени выполнения, поскольку оно заставляет службу с заданной областью вести себя как одноэлементный объект. В примере в разделе Параметры времени существования и регистрации демонстрируется подход InvokeAsync.
Используйте фабричное ПО промежуточного слоя. ПО промежуточного слоя, зарегистрированное с использованием этого подхода, активируется при каждом клиентском запросе (подключении), что позволяет внедрять службы с заданной областью в метод InvokeAsync ПО промежуточного слоя.
Регистрация службы только с типом реализации эквивалентна регистрации этой службы с той же реализацией и типом службы. Именно поэтому несколько реализаций службы не могут быть зарегистрированы с помощью методов, которые не принимают явный тип службы. Эти методы могут регистрировать несколько экземпляров службы, но все они будут иметь одинаковую реализацию типа.
Любой из указанных выше методов регистрации службы можно использовать для регистрации нескольких экземпляров службы одного типа службы. В следующем примере метод AddSingleton вызывается дважды с типом службы IMyDependency. Второй вызов AddSingleton переопределяет предыдущий, если он разрешается как IMyDependency, и добавляет к предыдущему, если несколько служб разрешаются через IEnumerable<IMyDependency>. Службы отображаются в том порядке, в котором они были зарегистрированы при разрешении через IEnumerable<{SERVICE}>.
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);
}
}
По умолчанию контексты Entity Framework добавляются в контейнер службы с помощью времени существования с заданной областью, поскольку операции базы данных в веб-приложении обычно относятся к области клиентского запроса. Чтобы использовать другое время существования, укажите его с помощью перегрузки AddDbContext. Службы данного времени существования не должны использовать контекст базы данных с временем существования короче, чем у службы.
Параметры времени существования и регистрации
Чтобы продемонстрировать различия между указанными вариантами времени существования и регистрации службы, рассмотрим интерфейсы, представляющие задачу в виде операции с идентификатором OperationId. В зависимости от того, как время существования службы операции настроено для этих интерфейсов, при запросе из класса контейнер предоставляет тот же или другой экземпляр службы.
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Следующий класс Operation реализует все предыдущие интерфейсы. Конструктор Operation создает идентификатор GUID и сохраняет последние 4 символа в свойстве OperationId:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Метод Startup.ConfigureServices создает несколько регистраций класса Operation в соответствии с именованным временем существования:
В примере приложения показано время существования объектов в пределах запросов и между запросами.
IndexModel и ПО промежуточного слоя запрашивают каждый тип IOperation и регистрируют OperationId для каждого из них:
Создайте IServiceScope с IServiceScopeFactory.CreateScope для разрешения службы с заданной областью в области приложения. Этот способ позволит получить доступ к службе с заданной областью при запуске для выполнения задач по инициализации.
В следующем примере показано, как получить доступ к службе IMyDependency с заданной областью и вызвать ее метод WriteMessage в Program.Main:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Службы и их зависимости в запросе ASP.NET Core предоставляются с помощью свойства HttpContext.RequestServices.
Платформа создает область для каждого запроса, а RequestServices предоставляет поставщик услуг с заданной областью. Все службы с заданной областью действительны до тех пор, пока запрос активен.
Примечание
Предпочтительнее запрашивать зависимости в качестве параметров конструктора, а не разрешать службы из RequestServices. Таким образом вы получите классы, которые проще тестировать.
Проектирование служб для внедрения зависимостей
При разработке служб для внедрения зависимостей придерживайтесь следующих рекомендаций:
Избегайте статических классов и членов с отслеживанием состояния. Избегайте создания глобального состояния. Для этого проектируйте приложения для использования отдельных служб.
Избегайте прямого создания экземпляров зависимых классов внутри служб. Прямое создание экземпляров обязывает использовать в коде определенную реализацию.
Сделайте службы приложения небольшими, хорошо организованными и удобными в тестировании.
Если класс имеет слишком много внедренных зависимостей, это может указывать на то, что у класса слишком много задач и он нарушает принцип единственной обязанности. Попробуйте выполнить рефакторинг класса и перенести часть его обязанностей в новые классы. Помните, что в классах модели страниц Razor Pages и классах контроллера MVC должны преимущественно выполняться задачи, связанные с пользовательским интерфейсом.
Удаление служб
Контейнер вызывает Dispose для создаваемых им типов IDisposable. Службы, разрешенные из контейнера, никогда не должны удаляться разработчиком. Если тип или фабрика зарегистрированы как одноэлементный объект, контейнер автоматически удалит одноэлементные объекты.
В следующем примере службы создаются контейнером службы и автоматически удаляются:
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;
}
}
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;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
var myKey = Configuration["MyKey"];
services.AddSingleton<IService3>(sp => new Service3(myKey));
services.AddRazorPages();
}
Старайтесь не использовать шаблон обнаружения служб. Например, не вызывайте GetService для получения экземпляра службы, когда можно использовать внедрение зависимостей:
Неправильно:
Правильное.
public class MyClass
{
private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
}
public void MyMethod()
{
var option = _optionsMonitor.CurrentValue.Option;
...
}
}
Другой вариант указателя службы, позволяющий избежать этого, — внедрение фабрики, которая разрешает зависимости во время выполнения. Оба метода смешивают стратегии инверсии управления.
Избегайте вызовов BuildServiceProvider в ConfigureServices. Вызов BuildServiceProvider обычно происходит, когда разработчику необходимо разрешить службу в ConfigureServices. Например, рассмотрим случай, когда LoginPath загружается из конфигурации. Добавьте следующий код:
На предыдущем рисунке при выборе строки, отмеченной зеленой волнистой линией в разделе services.BuildServiceProvider, отображается следующее предупреждение ASP0000:
ASP0000. Вызов BuildServiceProvider из кода приложения приводит к созданию дополнительной копии создаваемых одноэлементных служб. В качестве параметров для Configure можно использовать альтернативные варианты, такие как службы внедрения зависимостей.
При вызове BuildServiceProvider создается второй контейнер, который может создавать разорванные одноэлементные экземпляры и ссылаться на графы объектов в нескольких контейнерах.
Правильный способ получения LoginPath — использование встроенной поддержки шаблона параметров для внедрения зависимостей:
Контейнер собирает удаляемые временные службы для удаления. Это может привести к утечке памяти, если она разрешена из контейнера верхнего уровня.
Включите проверку области, чтобы убедиться, что в приложении нет отдельных объектов, записывающих службы с заданной областью. Дополнительные сведения см. в разделе Проверка области.
Как и с любыми рекомендациями, у вас могут возникнуть ситуации, когда нужно отступить от одного из правил. Исключения возникают редко, — как правило, это особые случаи, связанные с самой платформой.
Внедрение зависимостей является альтернативой для шаблонов доступа к статическим или глобальным объектам. Вы не сможете воспользоваться преимуществами внедрения зависимостей, если будете сочетать его с доступом к статическим объектам.
Рекомендуемые подходы к мультитенантности при внедрении зависимостей
Orchard Core — это платформа приложений для создания модульных мультитенантных приложений в ASP.NET Core. Дополнительные сведения см. в документации по Orchard Core.
Примеры создания модульных и мультитенантных приложений с использованием только Orchard Core Framework без каких-либо особых функций CMS см. здесь.
Платформенные службы
Метод Startup.ConfigureServices регистрирует службы, которые использует приложение, включая такие компоненты, как Entity Framework Core и ASP.NET Core MVC. Изначально коллекция IServiceCollection, предоставленная для ConfigureServices, содержит определенные платформой службы (в зависимости от настройки узла). Для приложений, основанных на шаблонах ASP.NET Core, платформа регистрирует более 250 служб.
В следующей таблице перечислены некоторые примеры этих зарегистрированных платформой служб.
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Отзыв о ASP.NET Core
ASP.NET Core — это проект с открытым исходным кодом. Выберите ссылку, чтобы оставить отзыв:
Изучите и реализуйте внедрение зависимостей в приложении ASP.NET Core. Используйте встроенный контейнер службы Core ASP.NET для управления зависимостями. Зарегистрируйте службы в контейнере службы.
Узнайте, как использовать внедрение зависимостей в приложениях .NET. Узнайте, как регистрировать службы, определять время существования службы и экспресс-зависимости в C#.