Внедрение зависимостей в ASP.NET CoreDependency injection in ASP.NET Core

Авторы: Кирк Ларкин (Kirk Larkin), Стив Смит (Steve Smith), Скотт Эдди (Scott Addie) и Брэндон Далер (Brandon Dahler)By Kirk Larkin, Steve Smith, Scott Addie, and Brandon Dahler

ASP.NET Core поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями.ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

Дополнительные сведения о внедрении зависимостей в контроллерах MVC см. в статье Внедрение зависимостей в контроллеры в ASP.NET Core.For more information specific to dependency injection within MVC controllers, see Внедрение зависимостей в контроллеры в ASP.NET Core.

Дополнительные сведения об использовании внедрения зависимостей в приложениях (кроме веб-приложений) см. в статье Внедрение зависимостей в .NET.For information on using dependency injection in applications other than web apps, see Dependency injection in .NET.

Дополнительные сведения о внедрении параметров зависимостей см. в разделе Шаблон параметров в ASP.NET Core.For more information on dependency injection of options, see Шаблон параметров в ASP.NET Core.

В этой статье приводятся сведения о внедрении зависимостей в ASP.NET Core.This topic provides information on dependency injection in ASP.NET Core. Основная документация по использованию внедрения зависимостей указана в статье Внедрение зависимостей в .NET.The primary documentation on using dependency injection is contained in Dependency injection in .NET.

Просмотреть или скачать образец кода (как скачивать)View or download sample code (how to download)

Общие сведения о внедрении зависимостейOverview of dependency injection

Зависимость — это любой объект, от которого зависит другой объект.A dependency is an object that another object depends on. Рассмотрим следующий класс MyDependency с методом WriteMessage, от которого зависят другие классы:Examine the following MyDependency class with a WriteMessage method that other classes depend on:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Класс может создать экземпляр класса MyDependency, чтобы использовать его метод WriteMessage.A class can create an instance of the MyDependency class to make use of its WriteMessage method. В следующем примере класс MyDependency выступает зависимостью класса IndexModel:In the following example, the MyDependency class is a dependency of the IndexModel class:

public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet created this message.");
    }
}

Этот класс создает MyDependency и напрямую зависит от этого класса.The class creates and directly depends on the MyDependency class. Зависимости в коде, как в предыдущем примере, представляют собой определенную проблему. Их следует избегать по следующим причинам.Code dependencies, such as in the previous example, are problematic and should be avoided for the following reasons:

  • Чтобы заменить MyDependency другой реализацией, класс IndexModel необходимо изменить.To replace MyDependency with a different implementation, the IndexModel class must be modified.
  • Если у MyDependency есть зависимости, их конфигурацию должен выполнять класс IndexModel.If MyDependency has dependencies, they must also be configured by the IndexModel class. В больших проектах, когда от MyDependency зависят многие классы, код конфигурации растягивается по всему приложению.In a large project with multiple classes depending on MyDependency, the configuration code becomes scattered across the app.
  • Такая реализация плохо подходит для модульных тестов.This implementation is difficult to unit test. В приложении нужно использовать имитацию или заглушку в виде класса MyDependency, что при таком подходе невозможно.The app should use a mock or stub MyDependency class, which isn't possible with this approach.

Внедрение зависимостей устраняет эти проблемы следующим образом:Dependency injection addresses these problems through:

  • Используется интерфейс или базовый класс для абстрагирования реализации зависимостей.The use of an interface or base class to abstract the dependency implementation.
  • Зависимость регистрируется в контейнере служб.Registration of the dependency in a service container. ASP.NET Core предоставляет встроенный контейнер служб, IServiceProvider.ASP.NET Core provides a built-in service container, IServiceProvider. Как правило, службы регистрируются в приложении в методе Startup.ConfigureServices.Services are typically registered in the app's Startup.ConfigureServices method.
  • Служба внедряется в конструктор класса там, где он используется.Injection of the service into the constructor of the class where it's used. Платформа берет на себя создание экземпляра зависимости и его удаление, когда он больше не нужен.The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.

В примере приложения интерфейс IMyDependency определяет метод WriteMessage:In the sample app, the IMyDependency interface defines the WriteMessage method:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Этот интерфейс реализуется конкретным типом, MyDependency.This interface is implemented by a concrete type, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Пример приложения регистрирует службу IMyDependency с конкретным типом MyDependency.The sample app registers the IMyDependency service with the concrete type MyDependency. Метод AddScoped регистрирует службу с заданной областью времени существования, временем существования одного запроса.The AddScoped method registers the service with a scoped lifetime, the lifetime of a single request. Подробнее о времени существования служб мы поговорим далее в этой статье.Service lifetimes are described later in this topic.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();

    services.AddRazorPages();
}

В примере приложения запрашивается служба IMyDependency, которая затем используется для вызова метода WriteMessage:In the sample app, the IMyDependency service is requested and used to call the WriteMessage method:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Используя шаблон внедрения зависимостей, контроллер:By using the DI pattern, the controller:

  • не использует конкретный тип MyDependency, только интерфейс IMyDependency, который он реализует.Doesn't use the concrete type MyDependency, only the IMyDependency interface it implements. Это упрощает изменение реализации, используемой контроллером, без изменения контроллера.That makes it easy to change the implementation that the controller uses without modifying the controller.
  • не создает экземпляр MyDependency, он создается контейнером внедрения зависимостей.Doesn't create an instance of MyDependency, it's created by the DI container.

Реализацию интерфейса IMyDependency можно улучшить с помощью встроенного API ведения журнала:The implementation of the IMyDependency interface can be improved by using the built-in logging 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:The updated ConfigureServices method registers the new IMyDependency implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency2>();

    services.AddRazorPages();
}

MyDependency2 зависит от ILogger<TCategoryName>, который запрашивается в конструкторе.MyDependency2 depends on ILogger<TCategoryName>, which it requests in the constructor. ILogger<TCategoryName> — это предоставленная платформой служба.ILogger<TCategoryName> is a framework-provided service.

Использование цепочки внедрений зависимостей не является чем-то необычным.It's not unusual to use dependency injection in a chained fashion. Каждая запрашиваемая зависимость запрашивает собственные зависимости.Each requested dependency in turn requests its own dependencies. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу.The container resolves the dependencies in the graph and returns the fully resolved service. Весь набор зависимостей, которые нужно разрешить, обычно называют деревом зависимостей, графом зависимостей или графом объектов.The collective set of dependencies that must be resolved is typically referred to as a dependency tree, dependency graph, or object graph.

Контейнер разрешает ILogger<TCategoryName>, используя преимущества (универсальных) открытых типов, что устраняет необходимость регистрации каждого (универсального) сконструированного типа.The container resolves ILogger<TCategoryName> by taking advantage of (generic) open types, eliminating the need to register every (generic) constructed type.

В терминологии внедрения зависимостей — служба:In dependency injection terminology, a service:

  • Обычно является объектом, предоставляющим службу для других объектов, например службу IMyDependency.Is typically an object that provides a service to other objects, such as the IMyDependency service.
  • Не относится к веб-службе, хотя служба может использовать веб-службу.Is not related to a web service, although the service may use a web service.

Платформа предоставляет эффективную систему ведения журнала.The framework provides a robust logging system. Реализации IMyDependency, приведенные в предыдущем примере были написаны для демонстрации базового внедрения зависимостей, а не для реализации ведения журнала.The IMyDependency implementations shown in the preceding examples were written to demonstrate basic DI, not to implement logging. Большинству приложений не нужно писать средства ведения журнала.Most apps shouldn't need to write loggers. В следующем коде показано использование журнала по умолчанию, для которого не требуется регистрация служб в ConfigureServices:The following code demonstrates using the default logging, which doesn't require any services to be registered in 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, поскольку платформа предоставляет возможность ведения журнала.Using the preceding code, there is no need to update ConfigureServices, because logging is provided by the framework.

Службы, внедренные в конструктор StartupServices injected into Startup

Службы можно внедрить в конструктор Startup и метод Startup.Configure.Services can be injected into the Startup constructor and the Startup.Configure method.

При использовании универсального узла (IHostBuilder) в конструктор Startup могут внедряться только следующие службы:Only the following services can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

Любая служба, зарегистрированная в контейнере внедрения зависимостей, может быть внедрена в метод Startup.Configure:Any service registered with the DI container can be injected into the Startup.Configure method:

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    ...
}

Дополнительные сведения см. в разделах Запуск приложения в ASP.NET Core и Доступ к конфигурации во время запуска.For more information, see Запуск приложения в ASP.NET Core and Access configuration in Startup.

Регистрация групп служб с помощью методов расширенияRegister groups of services with extension methods

Для регистрации группы связанных служб на платформе ASP.NET Core используется соглашение.The ASP.NET Core framework uses a convention for registering a group of related services. Соглашение заключается в использовании одного метода расширения Add{GROUP_NAME} для регистрации всех служб, необходимых компоненту платформы.The convention is to use a single Add{GROUP_NAME} extension method to register all of the services required by a framework feature. Например, метод расширения AddControllers регистрирует службы, необходимые контроллерам MVC.For example, the AddControllers extension method registers the services required for MVC controllers.

Следующий код создается шаблоном Razor Pages с использованием отдельных учетных записей пользователей. Он демонстрирует, как добавить дополнительные службы в контейнер с помощью методов расширения AddDbContext и AddDefaultIdentity:The following code is generated by the Razor Pages template using individual user accounts and shows how to add additional services to the container using the extension methods AddDbContext and AddDefaultIdentity:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

Рассмотрим следующий метод ConfigureServices, который регистрирует службы и настраивает параметры:Consider the following ConfigureServices method, which registers services and configures options:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
        Configuration.GetSection(PositionOptions.Position));
    services.Configure<ColorOptions>(
        Configuration.GetSection(ColorOptions.Color));

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddScoped<IMyDependency2, MyDependency2>();

    services.AddRazorPages();
}

Связанные группы регистраций можно переместить в метод расширения для регистрации служб.Related groups of registrations can be moved to an extension method to register services. Например, службы конфигурации добавляются в следующий класс:For example, the configuration services are added to the following class:

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

Остальные службы регистрируются в аналогичном классе.The remaining services are registered in a similar class. Следующий метод ConfigureServices использует новые методы расширения для регистрации служб:The following ConfigureServices method uses the new extension methods to register the services:

public void ConfigureServices(IServiceCollection services)
{
    services.AddConfig(Configuration)
            .AddMyDependencyGroup();

    services.AddRazorPages();
}

Примечание. Каждый метод расширения services.Add{GROUP_NAME} добавляет и, возможно, настраивает службы.Note: Each services.Add{GROUP_NAME} extension method adds and potentially configures services. Например, AddControllersWithViews добавляет контроллеры MVC служб с необходимыми представлениями, а AddRazorPages — службы, требуемые для работы Razor Pages.For example, AddControllersWithViews adds the services MVC controllers with views require, and AddRazorPages adds the services Razor Pages requires. Рекомендуем придерживаться этого соглашения об именовании в приложениях.We recommended that apps follow this naming convention. Поместите методы расширения в пространство имен Microsoft.Extensions.DependencyInjection, чтобы инкапсулировать группы зарегистрированных служб.Place extension methods in the Microsoft.Extensions.DependencyInjection namespace to encapsulate groups of service registrations.

Время существования службService lifetimes

См. раздел Время существования службы в статье Внедрение зависимостей в .NET.See Service lifetimes in Dependency injection in .NET

Используйте службы с заданной областью в ПО промежуточного слоя, применяя один из следующих подходов:To use scoped services in middleware, use one of the following approaches:

  • Внедрите службу в метод Invoke или InvokeAsync ПО промежуточного слоя.Inject the service into the middleware's Invoke or InvokeAsync method. С помощью внедрите конструктор создается исключение времени выполнения, поскольку оно заставляет службу с заданной областью вести себя как одноэлементный объект.Using constructor injection throws a runtime exception because it forces the scoped service to behave like a singleton. В примере в разделе Параметры времени существования и регистрации демонстрируется подход InvokeAsync.The sample in the Lifetime and registration options section demonstrates the InvokeAsync approach.
  • Используйте фабричное ПО промежуточного слоя.Use Factory-based middleware. ПО промежуточного слоя, зарегистрированное с использованием этого подхода, активируется при каждом клиентском запросе (подключении), что позволяет внедрять службы с заданной областью в метод InvokeAsync ПО промежуточного слоя.Middleware registered using this approach is activated per client request (connection), which allows scoped services to be injected into the middleware's InvokeAsync method.

Для получения дополнительной информации см. Написание пользовательского ПО промежуточного слоя ASP.NET Core.For more information, see Написание пользовательского ПО промежуточного слоя ASP.NET Core.

Методы регистрации службыService registration methods

См. раздел Методы регистрации службы в статье Внедрение зависимостей в .NET.See Service registration methods in Dependency injection in .NET

Распространенный сценарий для использования нескольких реализаций — макетирование типов для тестирования.It's common to use multiple implementations when mocking types for testing.

Регистрация службы только с типом реализации эквивалентна регистрации этой службы с той же реализацией и типом службы.Registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type. Именно поэтому несколько реализаций службы не могут быть зарегистрированы с помощью методов, которые не принимают явный тип службы.This is why multiple implementations of a service cannot be registered using the methods that don't take an explicit service type. Эти методы могут регистрировать несколько экземпляров службы, но все они будут иметь одинаковую реализацию типа.These methods can register multiple instances of a service, but they will all have the same implementation type.

Любой из указанных выше методов регистрации службы можно использовать для регистрации нескольких экземпляров службы одного типа службы.Any of the above service registration methods can be used to register multiple service instances of the same service type. В следующем примере метод AddSingleton вызывается дважды с типом службы IMyDependency.In the following example, AddSingleton is called twice with IMyDependency as the service type. Второй вызов AddSingleton переопределяет предыдущий, если он разрешается как IMyDependency, и добавляет к предыдущему, если несколько служб разрешаются через IEnumerable<IMyDependency>.The second call to AddSingleton overrides the previous one when resolved as IMyDependency and adds to the previous one when multiple services are resolved via IEnumerable<IMyDependency>. Службы отображаются в том порядке, в котором они были зарегистрированы при разрешении через IEnumerable<{SERVICE}>.Services appear in the order they were registered when resolved via 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);
    }
}

Поведение внедрения через конструкторConstructor injection behavior

См. раздел Поведение при внедрении конструктора в статье Внедрение зависимостей в .NET.See Constructor injection behavior in Dependency injection in .NET

Контексты Entity FrameworkEntity Framework contexts

По умолчанию контексты Entity Framework добавляются в контейнер службы с помощью времени существования с заданной областью, поскольку операции базы данных в веб-приложении обычно относятся к области клиентского запроса.By default, Entity Framework contexts are added to the service container using the scoped lifetime because web app database operations are normally scoped to the client request. Чтобы использовать другое время существования, укажите его с помощью перегрузки AddDbContext.To use a different lifetime, specify the lifetime by using an AddDbContext overload. Службы данного времени существования не должны использовать контекст базы данных с временем существования короче, чем у службы.Services of a given lifetime shouldn't use a database context with a lifetime that's shorter than the service's lifetime.

Параметры времени существования и регистрацииLifetime and registration options

Чтобы продемонстрировать различия между указанными вариантами времени существования и регистрации службы, рассмотрим интерфейсы, представляющие задачу в виде операции с идентификатором OperationId.To demonstrate the difference between service lifetimes and their registration options, consider the following interfaces that represent a task as an operation with an identifier, OperationId. В зависимости от того, как время существования службы операции настроено для этих интерфейсов, при запросе из класса контейнер предоставляет тот же или другой экземпляр службы.Depending on how the lifetime of an operation's service is configured for the following interfaces, the container provides either the same or different instances of the service when requested by a class:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Следующий класс Operation реализует все предыдущие интерфейсы.The following Operation class implements all of the preceding interfaces. Конструктор Operation создает идентификатор GUID и сохраняет последние 4 символа в свойстве OperationId:The Operation constructor generates a GUID and stores the last 4 characters in the OperationId property:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Метод Startup.ConfigureServices создает несколько регистраций класса Operation в соответствии с именованным временем существования:The Startup.ConfigureServices method creates multiple registrations of the Operation class according to the named lifetimes:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();

    services.AddRazorPages();
}

В примере приложения показано время существования объектов в пределах запросов и между запросами.The sample app demonstrates object lifetimes both within and between requests. IndexModel и ПО промежуточного слоя запрашивают каждый тип IOperation и регистрируют OperationId для каждого из них:The IndexModel and the middleware request each kind of IOperation type and log the OperationId for each:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

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

Аналогично IndexModel, ПО промежуточного слоя и разрешает те же службы:Similar to the IndexModel, the middleware resolves the same services:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationTransient transientOperation,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Службы с заданной областью должны быть разрешены в методе InvokeAsync:Scoped services must be resolved in the InvokeAsync method:

public async Task InvokeAsync(HttpContext context,
    IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + _transientOperation.OperationId);
    _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

Выходные данные средства ведения журнала содержат:The logger output shows:

  • Временные объекты всегда разные.Transient objects are always different. Значение временного OperationId отличается в IndexModel и ПО промежуточного слоя.The transient OperationId value is different in the IndexModel and in the middleware.
  • Объекты с заданной областью остаются неизменными в пределах одного запроса, но в разных запросах используются разные объекты.Scoped objects are the same for each request but different across each request.
  • Одноэлементные объекты одинаковы для каждого запроса.Singleton objects are the same for every request.

Чтобы уменьшить объем выводимых данных журнала, задайте в файле appsettings.Development.json параметр "Logging:LogLevel:Microsoft:Error".To reduce the logging output, set "Logging:LogLevel:Microsoft:Error" in the appsettings.Development.json file:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Вызов служб из функции mainCall services from main

Создайте IServiceScope с IServiceScopeFactory.CreateScope для разрешения службы с заданной областью в области приложения.Create an IServiceScope with IServiceScopeFactory.CreateScope to resolve a scoped service within the app's scope. Этот способ позволит получить доступ к службе с заданной областью при запуске для выполнения задач по инициализации.This approach is useful to access a scoped service at startup to run initialization tasks.

В следующем примере показано, как получить доступ к службе IMyDependency с заданной областью и вызвать ее метод WriteMessage в Program.Main:The following example shows how to access the scoped IMyDependency service and call its WriteMessage method in 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>();
            });
}

Проверка областиScope validation

См. раздел Поведение при внедрении конструктора в статье Внедрение зависимостей в .NET.See Constructor injection behavior in Dependency injection in .NET

Дополнительные сведения см. в разделе Проверка области.For more information, see Scope validation.

Службы запросовRequest Services

Службы, доступные в пределах запроса ASP.NET Core, предоставляются через коллекцию HttpContext.RequestServices.The services available within an ASP.NET Core request are exposed through the HttpContext.RequestServices collection. При запросе в рамках запроса службы и их зависимости разрешаются из коллекции RequestServices.When services are requested from inside of a request, the services and their dependencies are resolved from the RequestServices collection.

Платформа создает область для каждого запроса, а RequestServices предоставляет поставщик услуг с заданной областью.The framework creates a scope per request and RequestServices exposes the scoped service provider. Все службы с заданной областью действительны до тех пор, пока запрос активен.All scoped services are valid for as long as the request is active.

Примечание

Предпочтительнее запрашивать зависимости в качестве параметров конструктора, а не разрешать службы из коллекции RequestServices.Prefer requesting dependencies as constructor parameters to resolving services from the RequestServices collection. В результате создаются классы, которые легче тестировать.This results in classes that are easier to test.

Проектирование служб для внедрения зависимостейDesign services for dependency injection

При разработке служб для внедрения зависимостей придерживайтесь следующих рекомендаций:When designing services for dependency injection:

  • Избегайте статических классов и членов с отслеживанием состояния.Avoid stateful, static classes and members. Избегайте создания глобального состояния. Для этого проектируйте приложения для использования отдельных служб.Avoid creating global state by designing apps to use singleton services instead.
  • Избегайте прямого создания экземпляров зависимых классов внутри служб.Avoid direct instantiation of dependent classes within services. Прямое создание экземпляров обязывает использовать в коде определенную реализацию.Direct instantiation couples the code to a particular implementation.
  • Сделайте службы приложения небольшими, хорошо организованными и удобными в тестировании.Make services small, well-factored, and easily tested.

Если класс имеет слишком много внедренных зависимостей, это может указывать на то, что у класса слишком много задач и он нарушает принцип единственной обязанности.If a class has a lot of injected dependencies, it might be a sign that the class has too many responsibilities and violates the Single Responsibility Principle (SRP). Попробуйте выполнить рефакторинг класса и перенести часть его обязанностей в новые классы.Attempt to refactor the class by moving some of its responsibilities into new classes. Помните, что в классах модели страниц Razor Pages и классах контроллера MVC должны преимущественно выполняться задачи, связанные с пользовательским интерфейсом.Keep in mind that Razor Pages page model classes and MVC controller classes should focus on UI concerns.

Удаление службDisposal of services

Контейнер вызывает Dispose для создаваемых им типов IDisposable.The container calls Dispose for the IDisposable types it creates. Службы, разрешенные из контейнера, никогда не должны удаляться разработчиком.Services resolved from the container should never be disposed by the developer. Если тип или фабрика зарегистрированы как одноэлементный объект, контейнер автоматически удалит одноэлементные объекты.If a type or factory is registered as a singleton, the container disposes the singleton automatically.

В следующем примере службы создаются контейнером службы и автоматически удаляются:In the following example, the services are created by the service container and disposed automatically:

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();
}
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

После каждого обновления страницы индекса в консоли отладки отображаются следующие выходные данные:The debug console shows the following output after each refresh of the Index page:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet
Service1.Dispose

Службы, не созданные контейнером службыServices not created by the service container

Рассмотрим следующий код.Consider the following code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());

    services.AddRazorPages();
}

В приведенном выше коде:In the preceding code:

  • Экземпляры службы не создаются контейнером службы.The service instances aren't created by the service container.
  • Платформа не удаляет службы автоматически.The framework doesn't dispose of the services automatically.
  • За удаление служб отвечает разработчик.The developer is responsible for disposing the services.

Руководство по применению временных и общих экземпляров IDisposableIDisposable guidance for Transient and shared instances

См. раздел Рекомендации по IDisposable при использовании промежуточного и общего экземпляра в статье Внедрение зависимостей в .NET.See IDisposable guidance for Transient and shared instance in Dependency injection in .NET

Замена стандартного контейнера службDefault service container replacement

См. раздел Замена контейнера службы по умолчанию в статье Внедрение зависимостей в .NET.See Default service container replacement in Dependency injection in .NET

РекомендацииRecommendations

См. раздел Рекомендации в статье Внедрение зависимостей в .NET.See Recommendations in Dependency injection in .NET

  • Старайтесь не использовать схему указателя служб.Avoid using the service locator pattern. Например, не вызывайте GetService для получения экземпляра службы, когда можно использовать внедрение зависимостей:For example, don't invoke GetService to obtain a service instance when you can use DI instead:

    Неправильно:Incorrect:

    Неверный код

    Правильно:Correct:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Другой вариант указателя службы, позволяющий избежать этого, — внедрение фабрики, которая разрешает зависимости во время выполнения.Another service locator variation to avoid is injecting a factory that resolves dependencies at runtime. Оба метода смешивают стратегии инверсии управления.Both of these practices mix Inversion of Control strategies.

  • Не используйте статический доступ к HttpContext (например, IHttpContextAccessor.HttpContext).Avoid static access to HttpContext (for example, IHttpContextAccessor.HttpContext).

  • Избегайте вызовов BuildServiceProvider в ConfigureServices.Avoid calls to BuildServiceProvider in ConfigureServices. Вызов BuildServiceProvider обычно происходит, когда разработчику необходимо разрешить службу в ConfigureServices.Calling BuildServiceProvider typically happens when the developer wants to resolve a service in ConfigureServices. Например, рассмотрим случай, когда LoginPath загружается из конфигурации.For example, consider the case where the LoginPath is loaded from configuration. Добавьте следующий код:Avoid the following approach:

    Неверный код при вызове BuildServiceProvider

    На предыдущем рисунке при выборе строки, отмеченной зеленой волнистой линией в разделе services.BuildServiceProvider, отображается следующее предупреждение ASP0000:In the preceding image, selecting the green wavy line under services.BuildServiceProvider shows the following ASP0000 warning:

    ASP0000. Вызов BuildServiceProvider из кода приложения приводит к созданию дополнительной копии создаваемых одноэлементных служб.ASP0000 Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. В качестве параметров для Configure можно использовать альтернативные варианты, такие как службы внедрения зависимостей.Consider alternatives such as dependency injecting services as parameters to 'Configure'.

    При вызове BuildServiceProvider создается второй контейнер, который может создавать разорванные одноэлементные экземпляры и ссылаться на графы объектов в нескольких контейнерах.Calling BuildServiceProvider creates a second container, which can create torn singletons and cause references to object graphs across multiple containers.

    Правильный способ получения LoginPath — использование встроенной поддержки шаблона параметров для внедрения зависимостей:A correct way to get LoginPath is to use the options pattern's built-in support for DI:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie();
    
        services.AddOptions<CookieAuthenticationOptions>(
                            CookieAuthenticationDefaults.AuthenticationScheme)
            .Configure<IMyService>((options, myService) =>
            {
                options.LoginPath = myService.GetLoginPath();
            });
    
        services.AddRazorPages();
    }
    
  • Неудаляемые временные службы фиксируются контейнером для их удаления.Disposable transient services are captured by the container for disposal. Это может привести к утечке памяти, если она разрешена из контейнера верхнего уровня.This can turn into a memory leak if resolved from the top level container.

  • Включите проверку области, чтобы убедиться, что в приложении нет отдельных объектов, записывающих службы с заданной областью.Enable scope validation to make sure the app doesn't have singletons that capture scoped services. Дополнительные сведения см. в разделе Проверка области.For more information, see Scope validation.

Как и с любыми рекомендациями, у вас могут возникнуть ситуации, когда нужно отступить от одного из правил.Like all sets of recommendations, you may encounter situations where ignoring a recommendation is required. Исключения возникают редко, — как правило, это особые случаи, связанные с самой платформой.Exceptions are rare, mostly special cases within the framework itself.

Внедрение зависимостей является альтернативой для шаблонов доступа к статическим или глобальным объектам.DI is an alternative to static/global object access patterns. Вы не сможете воспользоваться преимуществами внедрения зависимостей, если будете сочетать его с доступом к статическим объектам.You may not be able to realize the benefits of DI if you mix it with static object access.

Orchard Core — это платформа приложений для создания модульных мультитенантных приложений в ASP.NET Core.Orchard Core is an application framework for building modular, multi-tenant applications on ASP.NET Core. Дополнительные сведения см. в документации по Orchard Core.For more information, see the Orchard Core Documentation.

Примеры создания модульных и мультитенантных приложений с использованием только Orchard Core Framework без каких-либо особых функций CMS см. здесь.See the Orchard Core samples for examples of how to build modular and multi-tenant apps using just the Orchard Core Framework without any of its CMS-specific features.

Платформенные службыFramework-provided services

Метод Startup.ConfigureServices регистрирует службы, которые использует приложение, включая такие компоненты, как Entity Framework Core и ASP.NET Core MVC.The Startup.ConfigureServices method registers services that the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC. Изначально коллекция IServiceCollection, предоставленная для ConfigureServices, содержит определенные платформой службы (в зависимости от настройки узла).Initially, the IServiceCollection provided to ConfigureServices has services defined by the framework depending on how the host was configured. Для приложений, основанных на шаблонах ASP.NET Core, платформа регистрирует более 250 служб.For apps based on the ASP.NET Core templates, the framework registers more than 250 services.

В следующей таблице перечислены некоторые примеры этих зарегистрированных платформой служб.The following table lists a small sample of these framework-registered services:

Тип службыService Type Время существования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

Дополнительные ресурсыAdditional resources

Авторы: Стив Смит (Steve Smith), Скотт Эдди (Scott Addie) и Брэндон Далер (Brandon Dahler)By Steve Smith, Scott Addie, and Brandon Dahler

ASP.NET Core поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями.ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

Дополнительные сведения о внедрении зависимостей в контроллерах MVC см. в статье Внедрение зависимостей в контроллеры в ASP.NET Core.For more information specific to dependency injection within MVC controllers, see Внедрение зависимостей в контроллеры в ASP.NET Core.

Просмотреть или скачать образец кода (как скачивать)View or download sample code (how to download)

Общие сведения о внедрении зависимостейOverview of dependency injection

Зависимость — это любой объект, который требуется другому объекту.A dependency is any object that another object requires. Рассмотрим класс MyDependency с методом WriteMessage, от которого зависят другие классы в приложении.Examine the following MyDependency class with a WriteMessage method that other classes in an app depend upon:

public class MyDependency
{
    public MyDependency()
    {
    }

    public Task WriteMessage(string message)
    {
        Console.WriteLine(
            $"MyDependency.WriteMessage called. Message: {message}");

        return Task.FromResult(0);
    }
}

Чтобы сделать метод WriteMessage доступным какому-то классу, можно создать экземпляр класса MyDependency.An instance of the MyDependency class can be created to make the WriteMessage method available to a class. Класс MyDependency выступает зависимостью класса IndexModel.The MyDependency class is a dependency of the IndexModel class:

public class IndexModel : PageModel
{
    MyDependency _dependency = new MyDependency();

    public async Task OnGetAsync()
    {
        await _dependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}

Этот класс создает экземпляр MyDependency и напрямую зависит от него.The class creates and directly depends on the MyDependency instance. Зависимости в коде (как в предыдущем примере) представляют собой определенную проблему. Их следует избегать по следующим причинам:Code dependencies (such as the previous example) are problematic and should be avoided for the following reasons:

  • Чтобы заменить MyDependency другой реализацией, класс необходимо изменить.To replace MyDependency with a different implementation, the class must be modified.
  • Если у MyDependency есть зависимости, их конфигурацию должен выполнять класс.If MyDependency has dependencies, they must be configured by the class. В больших проектах, когда от MyDependency зависят многие классы, код конфигурации растягивается по всему приложению.In a large project with multiple classes depending on MyDependency, the configuration code becomes scattered across the app.
  • Такая реализация плохо подходит для модульных тестов.This implementation is difficult to unit test. В приложении нужно использовать имитацию или заглушку в виде класса MyDependency, что при таком подходе невозможно.The app should use a mock or stub MyDependency class, which isn't possible with this approach.

Внедрение зависимостей устраняет эти проблемы следующим образом:Dependency injection addresses these problems through:

  • Используется интерфейс или базовый класс для абстрагирования реализации зависимостей.The use of an interface or base class to abstract the dependency implementation.
  • Зависимость регистрируется в контейнере служб.Registration of the dependency in a service container. ASP.NET Core предоставляет встроенный контейнер служб, IServiceProvider.ASP.NET Core provides a built-in service container, IServiceProvider. Службы регистрируются в приложении в методе Startup.ConfigureServices.Services are registered in the app's Startup.ConfigureServices method.
  • Служба внедряется в конструктор класса там, где он используется.Injection of the service into the constructor of the class where it's used. Платформа берет на себя создание экземпляра зависимости и его удаление, когда он больше не нужен.The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.

В примере приложения интерфейс IMyDependency определяет метод, который служба предоставляет приложению.In the sample app, the IMyDependency interface defines a method that the service provides to the app:

public interface IMyDependency
{
    Task WriteMessage(string message);
}

Этот интерфейс реализуется конкретным типом, MyDependency.This interface is implemented by a concrete type, MyDependency:

public class MyDependency : IMyDependency
{
    private readonly ILogger<MyDependency> _logger;

    public MyDependency(ILogger<MyDependency> logger)
    {
        _logger = logger;
    }

    public Task WriteMessage(string message)
    {
        _logger.LogInformation(
            "MyDependency.WriteMessage called. Message: {Message}", 
            message);

        return Task.FromResult(0);
    }
}

MyDependency запрашивает ILogger<TCategoryName> в своем конструкторе.MyDependency requests an ILogger<TCategoryName> in its constructor. Использование цепочки внедрений зависимостей не является чем-то необычным.It's not unusual to use dependency injection in a chained fashion. Каждая запрашиваемая зависимость запрашивает собственные зависимости.Each requested dependency in turn requests its own dependencies. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу.The container resolves the dependencies in the graph and returns the fully resolved service. Весь набор зависимостей, которые нужно разрешить, обычно называют деревом зависимостей, графом зависимостей или графом объектов.The collective set of dependencies that must be resolved is typically referred to as a dependency tree, dependency graph, or object graph.

IMyDependency и ILogger<TCategoryName> должны быть зарегистрированы в контейнере служб.IMyDependency and ILogger<TCategoryName> must be registered in the service container. IMyDependency регистрируется в Startup.ConfigureServices.IMyDependency is registered in Startup.ConfigureServices. Службу ILogger<TCategoryName> регистрирует инфраструктура абстракций ведения журналов, поэтому такая служба является платформенной, что означает, что ее по умолчанию регистрирует платформа.ILogger<TCategoryName> is registered by the logging abstractions infrastructure, so it's a framework-provided service registered by default by the framework.

Контейнер разрешает ILogger<TCategoryName>, используя преимущества (универсальных) открытых типов, что устраняет необходимость регистрации каждого (универсального) сконструированного типа.The container resolves ILogger<TCategoryName> by taking advantage of (generic) open types, eliminating the need to register every (generic) constructed type:

services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));

В примере приложения служба IMyDependency зарегистрирована с конкретным типом MyDependency.In the sample app, the IMyDependency service is registered with the concrete type MyDependency. Регистрация регулирует время существования службы согласно времени существования одного запроса.The registration scopes the service lifetime to the lifetime of a single request. Подробнее о времени существования служб мы поговорим далее в этой статье.Service lifetimes are described later in this topic.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));

    // OperationService depends on each of the other Operation types.
    services.AddTransient<OperationService, OperationService>();
}

Примечание

Каждый метод расширения services.Add{SERVICE_NAME} добавляет и, возможно, настраивает службы.Each services.Add{SERVICE_NAME} extension method adds, and potentially configures, services. Например, services.AddControllersWithViews, services.AddRazorPages и services.AddControllers добавляют службы, необходимые приложениям ASP.NET Core.For example, services.AddControllersWithViews, services.AddRazorPages, and services.AddControllers adds the services ASP.NET Core apps require. Рекомендуем придерживаться такого подхода в приложениях.We recommended that apps follow this convention. Поместите методы расширения в пространство имен Microsoft.Extensions.DependencyInjection, чтобы инкапсулировать группы зарегистрированных служб.Place extension methods in the Microsoft.Extensions.DependencyInjection namespace to encapsulate groups of service registrations. Включение части Microsoft.Extensions.DependencyInjection пространства имен для методов расширения внедрения зависимостей также:Including the namespace portion Microsoft.Extensions.DependencyInjection for DI extension methods also:

  • позволяет отображать их в IntelliSense без добавления дополнительных блоков using;Allows them to be displayed in IntelliSense without adding additional using blocks.
  • позволяет избежать чрезмерного количества инструкций using в классе Startup, из которого обычно вызываются эти методы расширения.Prevents excessive using statements in the Startup class where these extension methods are typically called from.

Если конструктору службы требуется встроенный тип, например string, его можно внедрить с помощью конфигурации или шаблона параметров.If the service's constructor requires a built in type, such as a string, the type can be injected by using configuration or the options pattern:

public class MyDependency : IMyDependency
{
    public MyDependency(IConfiguration config)
    {
        var myStringValue = config["MyStringKey"];

        // Use myStringValue
    }

    ...
}

Экземпляр службы запрашивается через конструктор класса, в котором эта служба используется и назначается скрытому полю.An instance of the service is requested via the constructor of a class where the service is used and assigned to a private field. Поле используется во всем классе для доступа к службе (по мере необходимости).The field is used to access the service as necessary throughout the class.

В примере приложения запрашивается экземпляр IMyDependency, который затем используется для вызова метода службы WriteMessage.In the sample app, the IMyDependency instance is requested and used to call the service's WriteMessage method:

public class IndexModel : PageModel
{
    private readonly IMyDependency _myDependency;

    public IndexModel(
        IMyDependency myDependency, 
        OperationService operationService,
        IOperationTransient transientOperation,
        IOperationScoped scopedOperation,
        IOperationSingleton singletonOperation,
        IOperationSingletonInstance singletonInstanceOperation)
    {
        _myDependency = myDependency;
        OperationService = operationService;
        TransientOperation = transientOperation;
        ScopedOperation = scopedOperation;
        SingletonOperation = singletonOperation;
        SingletonInstanceOperation = singletonInstanceOperation;
    }

    public OperationService OperationService { get; }
    public IOperationTransient TransientOperation { get; }
    public IOperationScoped ScopedOperation { get; }
    public IOperationSingleton SingletonOperation { get; }
    public IOperationSingletonInstance SingletonInstanceOperation { get; }

    public async Task OnGetAsync()
    {
        await _myDependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}

Службы, внедренные в конструктор StartupServices injected into Startup

При использовании универсального узла (IHostBuilder) в конструктор Startup могут внедряться только следующие типы служб:Only the following service types can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

Службы можно внедрить в Startup.Configure:Services can be injected into Startup.Configure:

public void Configure(IApplicationBuilder app, IOptions<MyOptions> options)
{
    ...
}

Дополнительные сведения см. в разделе Запуск приложения в ASP.NET Core.For more information, see Запуск приложения в ASP.NET Core.

Платформенные службыFramework-provided services

Метод Startup.ConfigureServices отвечает за определение служб, которые использует приложение, включая такие компоненты, как Entity Framework Core и ASP.NET Core MVC.The Startup.ConfigureServices method is responsible for defining the services that the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC. Изначально коллекция IServiceCollection, предоставленная для ConfigureServices, содержит определенные платформой службы (в зависимости от настройки узла).Initially, the IServiceCollection provided to ConfigureServices has services defined by the framework depending on how the host was configured. Приложение, основанное на шаблоне ASP.NET Core, часто содержит сотни служб, зарегистрированных платформой.It's not uncommon for an app based on an ASP.NET Core template to have hundreds of services registered by the framework. В следующей таблице перечислены некоторые примеры зарегистрированных платформой служб.A small sample of framework-registered services is listed in the following table.

Тип службыService Type Время существованияLifetime
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory ВременныйTransient
Microsoft.AspNetCore.Hosting.IApplicationLifetime ОдноэлементныйSingleton
Microsoft.AspNetCore.Hosting.IHostingEnvironment Одноэлементный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

Регистрация дополнительных служб с помощью методов расширенияRegister additional services with extension methods

Если зарегистрировать службу (включая ее зависимые службы, если нужно) можно, используя метод расширения коллекции служб, для регистрации всех служб, необходимых этой службе, рекомендуется использовать один метод расширения Add{SERVICE_NAME}.When a service collection extension method is available to register a service (and its dependent services, if required), the convention is to use a single Add{SERVICE_NAME} extension method to register all of the services required by that service. Ниже приведен пример того, как добавить дополнительные службы в контейнер с помощью методов расширения AddDbContext<TContext> и AddIdentityCore:The following code is an example of how to add additional services to the container using the extension methods AddDbContext<TContext> and AddIdentityCore:

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    ...
}

Дополнительные сведения см. в разделе о классе ServiceCollection в документации по API-интерфейсам.For more information, see the ServiceCollection class in the API documentation.

Время существования службService lifetimes

Для каждой зарегистрированной службы выбирайте подходящее время существования.Choose an appropriate lifetime for each registered service. Для служб ASP.NET можно настроить следующие параметры времени существования:ASP.NET Core services can be configured with the following lifetimes:

ВременныйTransient

Временные службы времени существования (AddTransient) создаются при каждом их запросе из контейнера служб.Transient lifetime services (AddTransient) are created each time they're requested from the service container. Это время существования лучше всего подходит для простых служб без отслеживания состояния.This lifetime works best for lightweight, stateless services.

В приложениях, обрабатывающих запросы, временные службы удаляются в конце запроса.In apps that process requests, transient services are disposed at the end of the request.

Область действияScoped

Службы времени существования с заданной областью (AddScoped) создаются один раз для каждого клиентского запроса (подключения).Scoped lifetime services (AddScoped) are created once per client request (connection).

В приложениях, обрабатывающих запросы, службы с заданной областью удаляются в конце запроса.In apps that process requests, scoped services are disposed at the end of the request.

Предупреждение

При использовании такой службы в ПО промежуточного слоя внедрите ее в метод Invoke или InvokeAsync.When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Не внедряйте службу с помощью внедрения через конструктор, поскольку в таком случае служба будет вести себя как одноэлементный объект.Don't inject via constructor injection because it forces the service to behave like a singleton. Дополнительные сведения см. в разделе Написание пользовательского ПО промежуточного слоя ASP.NET Core.For more information, see Написание пользовательского ПО промежуточного слоя ASP.NET Core.

ОдноэлементныйSingleton

Отдельные службы времени существования (AddSingleton) создаются при первом запросе (или при выполнении Startup.ConfigureServices, когда экземпляр указан во время регистрации службы).Singleton lifetime services (AddSingleton) are created the first time they're requested (or when Startup.ConfigureServices is run and an instance is specified with the service registration). Созданный экземпляр используют все последующие запросы.Every subsequent request uses the same instance. Если в приложении нужно использовать одинарные службы, рекомендуется разрешить контейнеру служб управлять временем их существования.If the app requires singleton behavior, allowing the service container to manage the service's lifetime is recommended. Реализовывать конструктивный шаблон с одинарными службами и предоставлять пользовательский код для управления временем существования объекта в классе категорически не рекомендуется.Don't implement the singleton design pattern and provide user code to manage the object's lifetime in the class.

В приложениях, обрабатывающих запросы, отдельные службы удаляются, когда ServiceProvider удаляется по завершении работы приложения.In apps that process requests, singleton services are disposed when the ServiceProvider is disposed at app shutdown.

Предупреждение

Разрешать регулируемую службу из одиночной нельзя.It's dangerous to resolve a scoped service from a singleton. При обработке последующих запросов это может вызвать неправильное состояние службы.It may cause the service to have incorrect state when processing subsequent requests.

Методы регистрации службыService registration methods

Методы расширения регистрации службы предлагают перегрузки, которые полезны в определенных сценариях.Service registration extension methods offer overloads that are useful in specific scenarios.

МетодMethod АвтоматическиAutomatic
objectobject
удалениеdisposal
НесколькоMultiple
реализацииimplementations
Передача аргументовPass args
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()
ПримерExample:
services.AddSingleton<IMyDep, MyDep>();
ДаYes ДаYes НетNo
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
Примеры:Examples:
services.AddSingleton<IMyDep>(sp => new MyDep());
services.AddSingleton<IMyDep>(sp => new MyDep("A string!"));
ДаYes ДаYes ДаYes
Add{LIFETIME}<{IMPLEMENTATION}>()
ПримерExample:
services.AddSingleton<MyDep>();
ДаYes НетNo НетNo
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})
Примеры:Examples:
services.AddSingleton<IMyDep>(new MyDep());
services.AddSingleton<IMyDep>(new MyDep("A string!"));
НетNo ДаYes ДаYes
AddSingleton(new {IMPLEMENTATION})
Примеры:Examples:
services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep("A string!"));
НетNo НетNo ДаYes

Дополнительные сведения об удалении типа см. в разделе Удаление служб.For more information on type disposal, see the Disposal of services section. Распространенный сценарий для нескольких реализаций — макеты типов для тестирования.A common scenario for multiple implementations is mocking types for testing.

Регистрация службы только с типом реализации эквивалентна регистрации этой службы с той же реализацией и типом службы.Registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type. Именно поэтому несколько реализаций службы не могут быть зарегистрированы с помощью методов, которые не принимают явный тип службы.This is why multiple implementations of a service cannot be registered using the methods that don't take an explicit service type. Эти методы могут регистрировать несколько экземпляров службы, но все они будут иметь одинаковую реализацию типа.These methods can register multiple instances of a service, but they will all have the same implementation type.

Любой из указанных выше методов регистрации службы можно использовать для регистрации нескольких экземпляров службы одного типа службы.Any of the above service registration methods can be used to register multiple service instances of the same service type. В следующем примере метод AddSingleton вызывается дважды с типом службы IMyDependency.In the following example, AddSingleton is called twice with IMyDependency as the service type. Второй вызов AddSingleton переопределяет предыдущий, если он разрешается как IMyDependency, и добавляет к предыдущему, если несколько служб разрешаются через IEnumerable<IMyDependency>.The second call to AddSingleton overrides the previous one when resolved as IMyDependency and adds to the previous one when multiple services are resolved via IEnumerable<IMyDependency>. Службы отображаются в том порядке, в котором они были зарегистрированы при разрешении через IEnumerable<{SERVICE}>.Services appear in the order they were registered when resolved via 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);
    }
}

Платформа также предоставляет методы расширения TryAdd{LIFETIME}, которые регистрируют службу только в том случае, если реализация еще не зарегистрирована.The framework also provides TryAdd{LIFETIME} extension methods, which register the service only if there isn't already an implementation registered.

В следующем примере вызов AddSingleton регистрирует MyDependency как реализацию для IMyDependency.In the following example, the call to AddSingleton registers MyDependency as an implementation for IMyDependency. Вызов TryAddSingleton ничего не делает, поскольку у IMyDependency уже есть зарегистрированная реализация.The call to TryAddSingleton has no effect because IMyDependency already has a registered implementation.

services.AddSingleton<IMyDependency, MyDependency>();
// The following line has no effect:
services.TryAddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
        IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is MyDependency);
        Trace.Assert(myDependencies.Single() is MyDependency);
    }
}

Дополнительные сведения см. в разделе:For more information, see:

Методы TryAddEnumerable(ServiceDescriptor) регистрируют службу только в том случае, если еще не существует реализации того же типа.TryAddEnumerable(ServiceDescriptor) methods only register the service if there isn't already an implementation of the same type. Несколько служб разрешается через IEnumerable<{SERVICE}>.Multiple services are resolved via IEnumerable<{SERVICE}>. При регистрации служб разработчик хочет добавить экземпляр только в том случае, если экземпляр такого типа еще не был добавлен.When registering services, the developer only wants to add an instance if one of the same type hasn't already been added. Как правило, этот метод используется авторами библиотек, чтобы избежать регистрации двух копий экземпляра в контейнере.Generally, this method is used by library authors to avoid registering two copies of an instance in the container.

В следующем примере первая строка регистрирует MyDep для IMyDep1.In the following example, the first line registers MyDep for IMyDep1. Вторая строка регистрирует MyDep для IMyDep2.The second line registers MyDep for IMyDep2. Третья строка ничего не делает, поскольку у IMyDep1 уже есть зарегистрированная реализация MyDep:The third line has no effect because IMyDep1 already has a registered implementation of MyDep:

public interface IMyDep1 {}
public interface IMyDep2 {}

public class MyDep : IMyDep1, IMyDep2 {}

services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep1, MyDep>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep2, MyDep>());
// Two registrations of MyDep for IMyDep1 is avoided by the following line:
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep1, MyDep>());

Поведение внедрения через конструкторConstructor injection behavior

Службы можно разрешать двумя методами:Services can be resolved by two mechanisms:

  • IServiceProvider
  • ActivatorUtilities. Позволяет создавать объекты без регистрации службы в контейнере внедрения зависимостей.ActivatorUtilities: Permits object creation without service registration in the dependency injection container. ActivatorUtilities используется с абстракциями пользовательского уровня, например со вспомогательными функциями для тегов, контроллерами MVC, и связывателями моделей.ActivatorUtilities is used with user-facing abstractions, such as Tag Helpers, MVC controllers, and model binders.

Конструкторы могут принимать аргументы, которые не предоставляются внедрением зависимостей, но эти аргументы должны назначать значения по умолчанию.Constructors can accept arguments that aren't provided by dependency injection, but the arguments must assign default values.

Когда разрешение служб выполняется через IServiceProvider или ActivatorUtilities, для внедрения через конструктор требуется открытый конструктор.When services are resolved by IServiceProvider or ActivatorUtilities, constructor injection requires a public constructor.

Когда разрешение служб выполняется через ActivatorUtilities, для внедрения через конструктор требуется наличие только одного соответствующего конструктора.When services are resolved by ActivatorUtilities, constructor injection requires that only one applicable constructor exists. Перегрузки конструктора поддерживаются, но может существовать всего одна перегрузка, все аргументы которой могут быть обработаны с помощью внедрения зависимостей.Constructor overloads are supported, but only one overload can exist whose arguments can all be fulfilled by dependency injection.

Контексты Entity FrameworkEntity Framework contexts

Контексты Entity Framework обычно добавляются в контейнер службы с помощью времени существования с заданной областью, поскольку операции базы данных в веб-приложении обычно относятся к области клиентского запроса.Entity Framework contexts are usually added to the service container using the scoped lifetime because web app database operations are normally scoped to the client request. Время существования по умолчанию имеет заданную область, если время существования не указано в перегрузке AddDbContext<TContext> при регистрации контекста базы данных.The default lifetime is scoped if a lifetime isn't specified by an AddDbContext<TContext> overload when registering the database context. Службы данного времени существования не должны использовать контекст базы данных с временем существования короче, чем у службы.Services of a given lifetime shouldn't use a database context with a shorter lifetime than the service.

Параметры времени существования и регистрацииLifetime and registration options

Чтобы продемонстрировать различия между указанными вариантами времени существования и регистрации, рассмотрим интерфейсы, представляющие задачи в виде операции с уникальным идентификатором OperationId.To demonstrate the difference between the lifetime and registration options, consider the following interfaces that represent tasks as an operation with a unique identifier, OperationId. В зависимости от того, как время существования службы операции настроено для этих интерфейсов, при запросе из класса контейнер предоставляет тот же или другой экземпляр службы.Depending on how the lifetime of an operations service is configured for the following interfaces, the container provides either the same or a different instance of the service when requested by a class:

public interface IOperation
{
    Guid OperationId { get; }
}

public interface IOperationTransient : IOperation
{
}

public interface IOperationScoped : IOperation
{
}

public interface IOperationSingleton : IOperation
{
}

public interface IOperationSingletonInstance : IOperation
{
}

Интерфейсы реализованы в классе Operation.The interfaces are implemented in the Operation class. Если идентификатор GUID не предоставлен, конструктор Operation создает его.The Operation constructor generates a GUID if one isn't supplied:

public class Operation : IOperationTransient, 
    IOperationScoped, 
    IOperationSingleton, 
    IOperationSingletonInstance
{
    public Operation() : this(Guid.NewGuid())
    {
    }

    public Operation(Guid id)
    {
        OperationId = id;
    }

    public Guid OperationId { get; private set; }
}

Регистрируется служба OperationService, которая зависит от каждого из других типов Operation.An OperationService is registered that depends on each of the other Operation types. Когда служба OperationService запрашивается через внедрение зависимостей, она получает либо новый экземпляр каждой службы, либо экземпляр уже существующей службы в зависимости от времени существования зависимой службы.When OperationService is requested via dependency injection, it receives either a new instance of each service or an existing instance based on the lifetime of the dependent service.

  • Если при запросе из контейнера создаются временные службы, OperationId службы IOperationTransient отличается от OperationId службы OperationService.When transient services are created when requested from the container, the OperationId of the IOperationTransient service is different than the OperationId of the OperationService. OperationService получает новый экземпляр класса IOperationTransient.OperationService receives a new instance of the IOperationTransient class. Новый экземпляр возвращает другой идентификатор OperationId.The new instance yields a different OperationId.
  • Если при каждом клиентском запросе создаются регулируемые службы, идентификатор OperationId службы IOperationScoped будет таким же, как для службы OperationService в клиентском запросе.When scoped services are created per client request, the OperationId of the IOperationScoped service is the same as that of OperationService within a client request. В разных клиентских запросах обе службы используют разные значения OperationId.Across client requests, both services share a different OperationId value.
  • Если одинарные службы и службы с одинарным экземпляром создаются один раз и используются во всех клиентских запросах и службах, идентификатор OperationId будет одинаковым во всех запросах служб.When singleton and singleton-instance services are created once and used across all client requests and all services, the OperationId is constant across all service requests.
public class OperationService
{
    public OperationService(
        IOperationTransient transientOperation,
        IOperationScoped scopedOperation,
        IOperationSingleton singletonOperation,
        IOperationSingletonInstance instanceOperation)
    {
        TransientOperation = transientOperation;
        ScopedOperation = scopedOperation;
        SingletonOperation = singletonOperation;
        SingletonInstanceOperation = instanceOperation;
    }

    public IOperationTransient TransientOperation { get; }
    public IOperationScoped ScopedOperation { get; }
    public IOperationSingleton SingletonOperation { get; }
    public IOperationSingletonInstance SingletonInstanceOperation { get; }
}

В Startup.ConfigureServices каждый тип добавляется в контейнер согласно его времени существования.In Startup.ConfigureServices, each type is added to the container according to its named lifetime:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));

    // OperationService depends on each of the other Operation types.
    services.AddTransient<OperationService, OperationService>();
}

Служба IOperationSingletonInstance использует определенный экземпляр с известным идентификатором Guid.Empty.The IOperationSingletonInstance service is using a specific instance with a known ID of Guid.Empty. Понятно, когда этот тип используется (его GUID — все нули).It's clear when this type is in use (its GUID is all zeroes).

В примере приложения показано время существования объектов в пределах одного и нескольких запросов.The sample app demonstrates object lifetimes within and between individual requests. В примере приложения IndexModel запрашивает каждый вид типа IOperation, а также OperationService.The sample app's IndexModel requests each kind of IOperation type and the OperationService. После этого на странице отображаются все значения OperationId службы и класса модели страниц в виде назначенных свойств.The page then displays all of the page model class's and service's OperationId values through property assignments:

public class IndexModel : PageModel
{
    private readonly IMyDependency _myDependency;

    public IndexModel(
        IMyDependency myDependency, 
        OperationService operationService,
        IOperationTransient transientOperation,
        IOperationScoped scopedOperation,
        IOperationSingleton singletonOperation,
        IOperationSingletonInstance singletonInstanceOperation)
    {
        _myDependency = myDependency;
        OperationService = operationService;
        TransientOperation = transientOperation;
        ScopedOperation = scopedOperation;
        SingletonOperation = singletonOperation;
        SingletonInstanceOperation = singletonInstanceOperation;
    }

    public OperationService OperationService { get; }
    public IOperationTransient TransientOperation { get; }
    public IOperationScoped ScopedOperation { get; }
    public IOperationSingleton SingletonOperation { get; }
    public IOperationSingletonInstance SingletonInstanceOperation { get; }

    public async Task OnGetAsync()
    {
        await _myDependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}

Результаты двух запросов будут выглядеть так:Two following output shows the results of two requests:

Первый запросFirst request:

Операции контроллера:Controller operations:

Transient: d233e165-f417-469b-a866-1cf1935d2518Transient: d233e165-f417-469b-a866-1cf1935d2518
Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19
Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
Instance: 00000000-0000-0000-0000-000000000000Instance: 00000000-0000-0000-0000-000000000000

Операции OperationService:OperationService operations:

Transient: c6b049eb-1318-4e31-90f1-eb2dd849ff64Transient: c6b049eb-1318-4e31-90f1-eb2dd849ff64
Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19
Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
Instance: 00000000-0000-0000-0000-000000000000Instance: 00000000-0000-0000-0000-000000000000

Второй запросSecond request:

Операции контроллера:Controller operations:

Transient: b63bd538-0a37-4ff1-90ba-081c5138dda0Transient: b63bd538-0a37-4ff1-90ba-081c5138dda0
Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4
Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
Instance: 00000000-0000-0000-0000-000000000000Instance: 00000000-0000-0000-0000-000000000000

Операции OperationService:OperationService operations:

Transient: c4cbacb8-36a2-436d-81c8-8c1b78808aafTransient: c4cbacb8-36a2-436d-81c8-8c1b78808aaf
Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4
Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
Instance: 00000000-0000-0000-0000-000000000000Instance: 00000000-0000-0000-0000-000000000000

Проанализируйте, какие значения OperationId изменяются в пределах одного и нескольких запросов.Observe which of the OperationId values vary within a request and between requests:

  • Временные объекты всегда разные.Transient objects are always different. Временное значение OperationId отличается в первом и втором клиентских запросах, а также в обеих операциях OperationService.The transient OperationId value for both the first and second client requests are different for both OperationService operations and across client requests. Каждому запросу службы и каждому клиентскому запросу предоставляется новый экземпляр.A new instance is provided to each service request and client request.
  • Ограниченные по области объекты одинаковы в рамках клиентского запроса, но различаются для разных запросов.Scoped objects are the same within a client request but different across client requests.
  • Одинарные объекты остаются неизменными для всех объектов и запросов независимо от того, предоставляется ли в Startup.ConfigureServices экземпляр Operation.Singleton objects are the same for every object and every request regardless of whether an Operation instance is provided in Startup.ConfigureServices.

Вызов служб из функции mainCall services from main

Создайте IServiceScope с IServiceScopeFactory.CreateScope для разрешения службы с заданной областью в области приложения.Create an IServiceScope with IServiceScopeFactory.CreateScope to resolve a scoped service within the app's scope. Этот способ позволит получить доступ к службе с заданной областью при запуске для выполнения задач по инициализации.This approach is useful to access a scoped service at startup to run initialization tasks. В следующем примере показано, как получить контекст для MyScopedService в Program.Main:The following example shows how to obtain a context for the MyScopedService in Program.Main:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class Program
{
    public static async Task Main(string[] args)
    {
        var host = CreateWebHostBuilder(args).Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var serviceContext = services.GetRequiredService<MyScopedService>();
                // Use the context here
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred.");
            }
        }

        await host.RunAsync();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

Проверка областиScope validation

Когда приложение выполняется в среде разработки, поставщик службы по умолчанию проверяет следующее:When the app is running in the Development environment, the default service provider performs checks to verify that:

  • Службы с заданной областью не разрешаются из корневого поставщика службы, прямо или косвенно.Scoped services aren't directly or indirectly resolved from the root service provider.
  • Службы с заданной областью не вводятся в одноэлементные объекты, прямо или косвенно.Scoped services aren't directly or indirectly injected into singletons.

Корневой поставщик службы создается при вызове BuildServiceProvider.The root service provider is created when BuildServiceProvider is called. Время существования корневого поставщика службы соответствует времени существования приложения или сервера — поставщик запускается с приложением и удаляется, когда приложение завершает работу.The root service provider's lifetime corresponds to the app/server's lifetime when the provider starts with the app and is disposed when the app shuts down.

Службы с заданной областью удаляются создавшим их контейнером.Scoped services are disposed by the container that created them. Если служба с заданной областью создается в корневом контейнере, время существования службы повышается до уровня одноэлементного объекта, поскольку она удаляется только корневым контейнером при завершении работы приложения или сервера.If a scoped service is created in the root container, the service's lifetime is effectively promoted to singleton because it's only disposed by the root container when app/server is shut down. Проверка областей службы перехватывает эти ситуации при вызове BuildServiceProvider.Validating service scopes catches these situations when BuildServiceProvider is called.

Дополнительные сведения см. в разделе Веб-узел ASP.NET Core.For more information, see Веб-узел ASP.NET Core.

Службы запросовRequest Services

Службы, доступные в пределах запроса ASP.NET Core из HttpContext, предоставляются через коллекцию HttpContext.RequestServices.The services available within an ASP.NET Core request from HttpContext are exposed through the HttpContext.RequestServices collection.

Службы запросов — это все настроенные и запрашиваемые в приложении службы.Request Services represent the services configured and requested as part of the app. Когда объекты указывают зависимости, они удовлетворяются за счет типов из RequestServices, а не из ApplicationServices.When the objects specify dependencies, these are satisfied by the types found in RequestServices, not ApplicationServices.

Как правило, использовать эти свойства в приложении напрямую не следует.Generally, the app shouldn't use these properties directly. Вместо этого запрашивайте требуемые для классов типы через конструкторы классов и разрешите платформе внедрять зависимости.Instead, request the types that classes require via class constructors and allow the framework inject the dependencies. Этот процесс создает классы, которые легче тестировать.This yields classes that are easier to test.

Примечание

Предпочтительнее запрашивать зависимости в качестве параметров конструктора, а не обращаться к коллекции RequestServices.Prefer requesting dependencies as constructor parameters to accessing the RequestServices collection.

Проектирование служб для внедрения зависимостейDesign services for dependency injection

Придерживайтесь следующих рекомендаций:Best practices are to:

  • Проектируйте службы так, чтобы для получения зависимостей они использовали внедрение зависимостей.Design services to use dependency injection to obtain their dependencies.
  • Избегайте статических классов и членов с отслеживанием состояния.Avoid stateful, static classes and members. Вместо этого следует проектировать приложения для использования отдельных служб, что позволяет избежать создания глобального состояния.Design apps to use singleton services instead, which avoid creating global state.
  • Избегайте прямого создания экземпляров зависимых классов внутри служб.Avoid direct instantiation of dependent classes within services. Прямое создание экземпляров обязывает использовать в коде определенную реализацию.Direct instantiation couples the code to a particular implementation.
  • Сделайте классы приложения небольшими, хорошо организованными и удобными в тестировании.Make app classes small, well-factored, and easily tested.

Если класс имеет слишком много внедренных зависимостей, обычно это указывает на то, что у класса слишком много задач и он не соответствует принципу единственной обязанности.If a class seems to have too many injected dependencies, this is generally a sign that the class has too many responsibilities and is violating the Single Responsibility Principle (SRP). Попробуйте выполнить рефакторинг класса и перенести часть его обязанностей в новый класс.Attempt to refactor the class by moving some of its responsibilities into a new class. Помните, что в классах модели страниц Razor Pages и классах контроллера MVC должны преимущественно выполняться задачи, связанные с пользовательским интерфейсом.Keep in mind that Razor Pages page model classes and MVC controller classes should focus on UI concerns. Бизнес-правила и реализация доступа к данным должны обрабатываться в классах, которые предназначены для этих целей.Business rules and data access implementation details should be kept in classes appropriate to these separate concerns.

Удаление службDisposal of services

Контейнер вызывает Dispose для создаваемых им типов IDisposable.The container calls Dispose for the IDisposable types it creates. Если экземпляр добавлен в контейнер с помощью пользовательского кода, он не будет удален автоматически.If an instance is added to the container by user code, it isn't disposed automatically.

В следующем примере службы создаются контейнером службы и автоматически удаляются:In the following example, the services are created by the service container and disposed automatically:

public class Service1 : IDisposable {}
public class Service2 : IDisposable {}

public interface IService3 {}
public class Service3 : IService3, IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    services.AddSingleton<IService3>(sp => new Service3());
}

В следующем примере:In the following example:

  • Экземпляры службы не создаются контейнером службы.The service instances aren't created by the service container.
  • Платформе неизвестно предполагаемое время жизни службы.The intended service lifetimes aren't known by the framework.
  • Платформа не удаляет службы автоматически.The framework doesn't dispose of the services automatically.
  • Если службы не удаляются явным образом в коде разработчика, они сохраняются до завершения работы приложения.If the services aren't explicitly disposed in developer code, they persist until the app shuts down.
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<Service1>(new Service1());
    services.AddSingleton(new Service2());
}

Руководство по применению временных и общих экземпляров IDisposableIDisposable guidance for Transient and shared instances

Временный экземпляр, ограниченное время существованияTransient, limited lifetime

СценарийScenario

Приложению требуется экземпляр IDisposable с ограниченным временем существования для реализации любого из следующих сценариев:The app requires an IDisposable instance with a transient lifetime for either of the following scenarios:

  • Экземпляр разрешается в корневой области.The instance is resolved in the root scope.
  • Экземпляр должен быть удален до завершения области.The instance should be disposed before the scope ends.

РешениеSolution

Используйте шаблон фабрики для создания экземпляра за пределами родительской области.Use the factory pattern to create an instance outside of the parent scope. В этом случае приложение обычно имеет метод Create, который непосредственно вызывает конструктор окончательного типа.In this situation, the app would generally have a Create method that calls the final type's constructor directly. Если окончательный тип имеет другие зависимости, фабрика позволяет:If the final type has other dependencies, the factory can:

Общий экземпляр, ограниченное время существованияShared Instance, limited lifetime

СценарийScenario

Приложению требуется общий экземпляр IDisposable в нескольких службах, но для IDisposable требуется ограниченное время существования.The app requires a shared IDisposable instance across multiple services, but the IDisposable should have a limited lifetime.

РешениеSolution

Зарегистрируйте экземпляр с временем существования с заданной областью.Register the instance with a Scoped lifetime. Используйте IServiceScopeFactory.CreateScope для запуска и создания интерфейса IServiceScope.Use IServiceScopeFactory.CreateScope to start and create a new IServiceScope. Используйте IServiceProvider области для получения необходимых служб.Use the scope's IServiceProvider to get required services. Удалить область по истечении времени существования.Dispose the scope when the lifetime should end.

Общие рекомендацииGeneral Guidelines

  • Не регистрируйте экземпляры IDisposable с временной областью.Don't register IDisposable instances with a Transient scope. Вместо этого используйте шаблон фабрики.Use the factory pattern instead.
  • Не разрешайте временные экземпляры IDisposable или экземпляры с заданной областью в корневую область.Don't resolve Transient or Scoped IDisposable instances in the root scope. Единственное исключение — это когда приложение создает или повторно создает и удаляет IServiceProvider, что не является идеальным вариантом.The only general exception is when the app creates/recreates and disposes the IServiceProvider, which isn't an ideal pattern.
  • Для получения зависимости IDisposable через DI не требуется, чтобы получатель реализовывал сам интерфейс IDisposable.Receiving an IDisposable dependency via DI doesn't require that the receiver implement IDisposable itself. Получатель зависимости IDisposable не должен вызывать Dispose для этой зависимости.The receiver of the IDisposable dependency shouldn't call Dispose on that dependency.
  • Области должны использоваться для управления жизненным циклом служб.Scopes should be used to control lifetimes of services. Области не являются иерархическими, и между ними нет специальной связи.Scopes aren't hierarchical, and there's no special connection among scopes.

Замена стандартного контейнера службDefault service container replacement

Встроенный контейнер служб предназначен для удовлетворения потребностей платформы и большинства клиентских приложений.The built-in service container is designed to serve the needs of the framework and most consumer apps. Мы рекомендуем использовать встроенный контейнер, если только не требуется конкретная функциональная возможность, которую он не поддерживает, например:We recommend using the built-in container unless you need a specific feature that the built-in container doesn't support, such as:

  • Внедрение свойствProperty injection
  • Внедрение по имениInjection based on name
  • Дочерние контейнерыChild containers
  • Настраиваемое управление временем существованияCustom lifetime management
  • Func<T> поддерживает отложенную инициализациюFunc<T> support for lazy initialization
  • Регистрация на основе соглашенияConvention-based registration

С приложениями ASP.NET Core можно использовать следующие сторонние контейнеры:The following third-party containers can be used with ASP.NET Core apps:

ПотокобезопасностьThread safety

Создавайте потокобезопасные одноэлементные службы.Create thread-safe singleton services. Если одноэлементная служба имеет зависимость от временной службы, с учетом характера использования одноэлементной службой к этой временной службе также может предъявляться требование потокобезопасности.If a singleton service has a dependency on a transient service, the transient service may also require thread safety depending how it's used by the singleton.

Фабричный метод одной службы, например второй аргумент для AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), не обязательно должен быть потокобезопасным.The factory method of single service, such as the second argument to AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), doesn't need to be thread-safe. Как и конструктор типов (static), он гарантированно будет вызываться один раз одним потоком.Like a type (static) constructor, it's guaranteed to be called once by a single thread.

РекомендацииRecommendations

  • Разрешение служб на основе async/await и Task не поддерживается.async/await and Task based service resolution is not supported. C# не поддерживает асинхронные конструкторы, поэтому рекомендуем использовать асинхронные методы после асинхронного разрешения службы.C# does not support asynchronous constructors; therefore, the recommended pattern is to use asynchronous methods after synchronously resolving the service.

  • Не храните данные и конфигурацию непосредственно в контейнере служб.Avoid storing data and configuration directly in the service container. Например, обычно не следует добавлять корзину пользователя в контейнер служб.For example, a user's shopping cart shouldn't typically be added to the service container. Конфигурация должна использовать шаблон параметров.Configuration should use the options pattern. Аналогичным образом, избегайте объектов "хранения данных", которые служат лишь для доступа к некоторому другому объекту.Similarly, avoid "data holder" objects that only exist to allow access to some other object. Лучше запросить фактический элемент через внедрение зависимостей.It's better to request the actual item via DI.

  • Избегайте статического доступа к службам.Avoid static access to services. Например, не используйте везде IApplicationBuilder.ApplicationServices.For example, avoid statically-typing IApplicationBuilder.ApplicationServices for use elsewhere.

  • Старайтесь не использовать шаблон обнаружения служб, который использует разные стратегии инверсии управления.Avoid using the service locator pattern, which mixes Inversion of Control strategies.

    • Не вызывайте GetService для получения экземпляра службы, когда можно использовать внедрение зависимостей:Don't invoke GetService to obtain a service instance when you can use DI instead:

      Неправильно:Incorrect:

      public class MyClass()
      
        public void MyMethod()
        {
            var optionsMonitor = 
                _services.GetService<IOptionsMonitor<MyOptions>>();
            var option = optionsMonitor.CurrentValue.Option;
      
            ...
        }
      

      Правильно:Correct:

      public class MyClass
      {
          private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
      
          public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
          {
              _optionsMonitor = optionsMonitor;
          }
      
          public void MyMethod()
          {
              var option = _optionsMonitor.CurrentValue.Option;
      
              ...
          }
      }
      
  • Избегайте внедрения фабрики, которая разрешает зависимости во время выполнения с помощью GetService.Avoid injecting a factory that resolves dependencies at runtime using GetService.

  • Не используйте статический доступ к HttpContext (например, IHttpContextAccessor.HttpContext).Avoid static access to HttpContext (for example, IHttpContextAccessor.HttpContext).

Как и с любыми рекомендациями, у вас могут возникнуть ситуации, когда нужно отступить от одного из правил.Like all sets of recommendations, you may encounter situations where ignoring a recommendation is required. Исключения возникают редко, — как правило, это особые случаи, связанные с самой платформой.Exceptions are rare, mostly special cases within the framework itself.

Внедрение зависимостей является альтернативой для шаблонов доступа к статическим или глобальным объектам.DI is an alternative to static/global object access patterns. Вы не сможете воспользоваться преимуществами внедрения зависимостей, если будете сочетать его с доступом к статическим объектам.You may not be able to realize the benefits of DI if you mix it with static object access.

Дополнительные ресурсыAdditional resources