Inserción de dependencias en ASP.NET CoreDependency injection in ASP.NET Core

Por Steve Smith, Scott Addie y Luke LathamBy Steve Smith, Scott Addie, and Luke Latham

ASP.NET Core admite el patrón de diseño de software de inserción de dependencias (DI), que es una técnica para conseguir la inversión de control (IoC) entre clases y sus dependencias.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.

Para más información específica sobre la inserción de dependencias en los controladores MVC, vea Inserción de dependencias en controladores en ASP.NET Core.For more information specific to dependency injection within MVC controllers, see Inserción de dependencias en controladores en ASP.NET Core.

Vea o descargue el código de ejemplo (cómo descargarlo)View or download sample code (how to download)

Información general sobre la inserción de dependenciasOverview of dependency injection

Una dependencia es cualquier objeto requerido por otro objeto.A dependency is any object that another object requires. Examine la siguiente clase MyDependency con un método WriteMessage del que dependen otras clases de una aplicación: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);
    }
}

Se puede crear una instancia de la clase MyDependency para hacer que el método WriteMessage esté disponible para una clase.An instance of the MyDependency class can be created to make the WriteMessage method available to a class. La clase MyDependency es una dependencia de la clase 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.");
    }
}

La clase crea y depende directamente de la instancia MyDependency.The class creates and directly depends on the MyDependency instance. Las dependencias de código (como en el ejemplo anterior) son problemáticas y deben evitarse por las siguientes razones:Code dependencies (such as the previous example) are problematic and should be avoided for the following reasons:

  • Para reemplazar MyDependency con una implementación diferente, se debe modificar la clase.To replace MyDependency with a different implementation, the class must be modified.
  • Si MyDependency tiene dependencias, deben configurarse según la clase.If MyDependency has dependencies, they must be configured by the class. En un proyecto grande con varias clases que dependen de MyDependency, el código de configuración se dispersa por la aplicación.In a large project with multiple classes depending on MyDependency, the configuration code becomes scattered across the app.
  • Esta implementación es difícil para realizar pruebas unitarias.This implementation is difficult to unit test. La aplicación debe usar una clase MyDependency como boceto o código auxiliar, que no es posible con este enfoque.The app should use a mock or stub MyDependency class, which isn't possible with this approach.

La inserción de dependencias aborda estos problemas mediante:Dependency injection addresses these problems through:

  • El uso de una interfaz para abstraer la implementación de dependencias.The use of an interface to abstract the dependency implementation.
  • Registro de la dependencia en un contenedor de servicios.Registration of the dependency in a service container. ASP.NET Core proporciona un contenedor de servicios integrado, IServiceProvider.ASP.NET Core provides a built-in service container, IServiceProvider. Los servicios se registran en el método Startup.ConfigureServices de la aplicación.Services are registered in the app's Startup.ConfigureServices method.
  • Inserción del servicio en el constructor de la clase en la que se usa.Injection of the service into the constructor of the class where it's used. El marco de trabajo asume la responsabilidad de crear una instancia de la dependencia y de desecharla cuando ya no es necesaria.The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.

En la aplicación de ejemplo, la interfaz IMyDependency define un método que el servicio proporciona a la aplicación:In the sample app, the IMyDependency interface defines a method that the service provides to the app:

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

Esta interfaz se implementa mediante un tipo concreto, 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 solicita una instancia de ILogger<TCategoryName> en su constructor.MyDependency requests an ILogger<TCategoryName> in its constructor. No es raro usar la inserción de dependencias de forma encadenada.It's not unusual to use dependency injection in a chained fashion. Cada dependencia solicitada a su vez solicita sus propias dependencias.Each requested dependency in turn requests its own dependencies. El contenedor resuelve las dependencias del gráfico y devuelve el servicio totalmente resuelto.The container resolves the dependencies in the graph and returns the fully resolved service. El conjunto colectivo de dependencias que deben resolverse suele denominarse árbol de dependencias, gráfico de dependencias o gráfico de objetos.The collective set of dependencies that must be resolved is typically referred to as a dependency tree, dependency graph, or object graph.

IMyDependency y ILogger<TCategoryName> deben estar registrados en el contenedor de servicios.IMyDependency and ILogger<TCategoryName> must be registered in the service container. IMyDependency está registrado en Startup.ConfigureServices.IMyDependency is registered in Startup.ConfigureServices. ILogger<TCategoryName> está registrado en la infraestructura de abstracciones de registros, por lo que se trata de un servicio proporcionado por el marco de trabajo registrado de forma predeterminada por el marco de trabajo.ILogger<TCategoryName> is registered by the logging abstractions infrastructure, so it's a framework-provided service registered by default by the framework.

El contenedor resuelve ILogger<TCategoryName> aprovechando las ventajas de los tipos abiertos (genéricos), lo que elimina la necesidad de registrar todos los tipos construidos (genéricos):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<T>), typeof(Logger<T>));

En la aplicación de ejemplo, el servicio IMyDependency está registrado con el tipo concreto MyDependency.In the sample app, the IMyDependency service is registered with the concrete type MyDependency. El registro abarca la duración del servicio como la duración de una única solicitud.The registration scopes the service lifetime to the lifetime of a single request. Las duraciones del servicio se describen más adelante en este tema.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>();
}

Nota

Cada método de extensión services.Add{SERVICE_NAME} agrega servicios (y potencialmente los configura).Each services.Add{SERVICE_NAME} extension method adds (and potentially configures) services. Por ejemplo, services.AddMvc() agrega los servicios que Razor Pages y MVC requieren.For example, services.AddMvc() adds the services Razor Pages and MVC require. Se recomienda que las aplicaciones sigan esta convención.We recommended that apps follow this convention. Coloque los métodos de extensión en el espacio de nombres Microsoft.Extensions.DependencyInjection para encapsular grupos de registros del servicio.Place extension methods in the Microsoft.Extensions.DependencyInjection namespace to encapsulate groups of service registrations.

Si el constructor del servicio requiere un tipo integrado, como string, se puede insertar mediante la configuración o el patrón de opciones: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
    }

    ...
}

Se solicita una instancia del servicio mediante el constructor de una clase, en la que se usa el servicio y se asigna a un campo privado.An instance of the service is requested via the constructor of a class where the service is used and assigned to a private field. El campo de utiliza para acceder al servicio, según sea necesario en la clase.The field is used to access the service as necessary throughout the class.

En la aplicación de ejemplo, la instancia IMyDependency se solicita y usa para llamar al método WriteMessage del servicio: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.");
    }
}

Servicios proporcionados por el marco de trabajoFramework-provided services

El método Startup.ConfigureServices se encarga de definir los servicios que la aplicación usa, incluidas las características de plataforma como Entity Framework Core y ASP.NET Core MVC.The Startup.ConfigureServices method is responsible for defining the services the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC. Inicialmente, el valor IServiceCollection proporcionado a ConfigureServices tiene los siguientes servicios definidos (en función de cómo se configurara el host):Initially, the IServiceCollection provided to ConfigureServices has the following services defined (depending on how the host was configured):

Tipo de servicioService Type Período de duraciónLifetime
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactoryMicrosoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory TransitorioTransient
Microsoft.AspNetCore.Hosting.IApplicationLifetimeMicrosoft.AspNetCore.Hosting.IApplicationLifetime SingletonSingleton
Microsoft.AspNetCore.Hosting.IHostingEnvironmentMicrosoft.AspNetCore.Hosting.IHostingEnvironment SingletonSingleton
Microsoft.AspNetCore.Hosting.IStartupMicrosoft.AspNetCore.Hosting.IStartup SingletonSingleton
Microsoft.AspNetCore.Hosting.IStartupFilterMicrosoft.AspNetCore.Hosting.IStartupFilter TransitorioTransient
Microsoft.AspNetCore.Hosting.Server.IServerMicrosoft.AspNetCore.Hosting.Server.IServer SingletonSingleton
Microsoft.AspNetCore.Http.IHttpContextFactoryMicrosoft.AspNetCore.Http.IHttpContextFactory TransitorioTransient
Microsoft.Extensions.Logging.ILogger<T>Microsoft.Extensions.Logging.ILogger<T> SingletonSingleton
Microsoft.Extensions.Logging.ILoggerFactoryMicrosoft.Extensions.Logging.ILoggerFactory SingletonSingleton
Microsoft.Extensions.ObjectPool.ObjectPoolProviderMicrosoft.Extensions.ObjectPool.ObjectPoolProvider SingletonSingleton
Microsoft.Extensions.Options.IConfigureOptions<T>Microsoft.Extensions.Options.IConfigureOptions<T> TransitorioTransient
Microsoft.Extensions.Options.IOptions<T>Microsoft.Extensions.Options.IOptions<T> SingletonSingleton
System.Diagnostics.DiagnosticSourceSystem.Diagnostics.DiagnosticSource SingletonSingleton
System.Diagnostics.DiagnosticListenerSystem.Diagnostics.DiagnosticListener SingletonSingleton

Cuando un método de extensión de la colección de servicio está disponible para registrar un servicio (y sus servicios dependientes, si es necesario), la convención consiste en usar un solo método de extensión Add{SERVICE_NAME} para registrar todos los servicios requeridos por dicho servicio.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. El código siguiente es un ejemplo de cómo agregar servicios adicionales al contenedor mediante los métodos de extensión AddDbContext, AddIdentity y AddMvc:The following code is an example of how to add additional services to the container using the extension methods AddDbContext, AddIdentity, and AddMvc:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

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

    services.AddMvc();
}

Para más información, vea la clase ServiceCollection en la documentación de la API.For more information, see the ServiceCollection Class in the API documentation.

Duraciones de serviciosService lifetimes

Elija una duración adecuada para cada servicio registrado.Choose an appropriate lifetime for each registered service. Los servicios de ASP.NET Core pueden configurarse con las duraciones siguientes:ASP.NET Core services can be configured with the following lifetimes:

TransitoriaTransient

Los servicios de duración transitoria se crean cada vez que el contenedor del servicio los solicita.Transient lifetime services are created each time they're requested from the service container. Esta duración funciona mejor para servicios sin estado ligeros.This lifetime works best for lightweight, stateless services.

Con ámbitoScoped

Los servicios de duración con ámbito se crean una vez por solicitud del cliente (conexión).Scoped lifetime services are created once per client request (connection).

Advertencia

Si usa un servicio con ámbito en un middleware, inserte el servicio en el método Invoke o InvokeAsync.When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. No lo inserte a través de la inserción de constructores, porque ello hace que el servicio se comporte como un singleton.Don't inject via constructor injection because it forces the service to behave like a singleton. Para obtener más información, vea Middleware de ASP.NET Core.For more information, see Middleware de ASP.NET Core.

SingletonSingleton

Los servicios con duración Singleton se crean la primera vez que se solicitan, o bien cuando se ejecuta ConfigureServices y se especifica una instancia con el registro del servicio.Singleton lifetime services are created the first time they're requested (or when ConfigureServices is run and an instance is specified with the service registration). Cada solicitud posterior usa la misma instancia.Every subsequent request uses the same instance. Si la aplicación requiere un comportamiento de singleton, se recomienda permitir que el contenedor de servicios administre la duración del servicio.If the app requires singleton behavior, allowing the service container to manage the service's lifetime is recommended. No implemente el patrón de diseño de singleton y proporcione el código de usuario para administrar la duración del objeto en la clase.Don't implement the singleton design pattern and provide user code to manage the object's lifetime in the class.

Advertencia

Es peligroso resolver un servicio con ámbito desde un singleton.It's dangerous to resolve a scoped service from a singleton. Puede dar lugar a que el servicio adopte un estado incorrecto al procesar solicitudes posteriores.It may cause the service to have incorrect state when processing subsequent requests.

Comportamiento de inserción de constructorConstructor injection behavior

Los servicios se pueden resolver mediante dos mecanismos:Services can be resolved by two mechanisms:

  • IServiceProvider
  • ActivatorUtilities – Permite la creación de objetos sin registrar el servicio en el contenedor de inserción de dependencias.ActivatorUtilities – Permits object creation without service registration in the dependency injection container. ActivatorUtilities se utiliza con abstracciones orientadas al usuario, como asistentes de etiquetas, controladores MVC y enlazadores de modelos.ActivatorUtilities is used with user-facing abstractions, such as Tag Helpers, MVC controllers, and model binders.

Los constructores pueden aceptar argumentos que no se proporcionan mediante la inserción de dependencias, pero los argumentos deben asignar valores predeterminados.Constructors can accept arguments that aren't provided by dependency injection, but the arguments must assign default values.

Cuando se resuelven los servicios mediante IServiceProvider o ActivatorUtilities, la inserción del constructor requiere un constructor público.When services are resolved by IServiceProvider or ActivatorUtilities, constructor injection requires a public constructor.

Cuando se resuelven los servicios mediante ActivatorUtilities, la inserción del constructor requiere que exista solo un constructor aplicable.When services are resolved by ActivatorUtilities, constructor injection requires that only one applicable constructor exists. Se admiten las sobrecargas de constructor, pero solo puede existir una sobrecarga cuyos argumentos pueda cumplir la inserción de dependencias.Constructor overloads are supported, but only one overload can exist whose arguments can all be fulfilled by dependency injection.

Contextos de Entity FrameworkEntity Framework contexts

Los contextos de Entity Framework normalmente se agregan al contenedor de servicios mediante la duración con ámbito porque las operaciones de base de datos de aplicación web se suelen limitar a la solicitud de cliente.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. La duración predeterminada se limita si no se especifica una duración mediante una sobrecarga de AddDbContext<TContext> al registrar el contexto de base de datos.The default lifetime is scoped if a lifetime isn't specified by an AddDbContext<TContext> overload when registering the database context. En los servicios de una duración determinada no se debe usar un contexto de base de datos con una duración más corta que el servicio.Services of a given lifetime shouldn't use a database context with a shorter lifetime than the service.

Opciones de registro y duraciónLifetime and registration options

Para mostrar la diferencia entre la duración y las opciones de registro, considere las siguientes interfaces que representan tareas como una operación con un identificador único, 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. Según cómo esté configurada la duración de un servicio de operaciones para las interfaces siguientes, el contenedor proporciona la misma instancia del servicio u otra distinta cuando así lo solicita la clase: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
{
}

Las interfaces se implementan en la clase Operation.The interfaces are implemented in the Operation class. El constructor Operation genera un GUID en caso de que no se proporcione uno: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; }
}

Se registra una instancia de OperationService que depende de cada uno de los demás tipos Operation.An OperationService is registered that depends on each of the other Operation types. Cuando OperationService se solicita con la inserción de dependencias, recibe una instancia nueva de cada servicio o una instancia existente en función de la duración de los servicios dependientes.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.

  • Si se crean servicios transitorios cuando se solicitan al contenedor, el elemento OperationId del servicio IOperationTransient es diferente del objeto OperationId de 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 recibe una instancia nueva de la clase IOperationTransient.OperationService receives a new instance of the IOperationTransient class. La nueva instancia produce un objeto OperationId diferente.The new instance yields a different OperationId.
  • Si se crean servicios con ámbito por solicitud de cliente, el objeto OperationId del servicio IOperationScoped es el mismo que para OperationService dentro de la solicitud de cliente.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. Entre las solicitudes de cliente, ambos servicios comparten un valor OperationId diferente.Across client requests, both services share a different OperationId value.
  • Si se crean servicios singleton y servicios de instancia singleton una vez y se usan en todas las solicitudes de cliente y servicios, el objeto OperationId es constante en todas las solicitudes de servicio.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; }
}

En Startup.ConfigureServices, cada tipo se agrega al contenedor según su duración con nombre: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>();
}

El servicio IOperationSingletonInstance usa una instancia específica con un identificador conocido de Guid.Empty.The IOperationSingletonInstance service is using a specific instance with a known ID of Guid.Empty. Resulta evidente identificar cuándo este tipo está en uso (su GUID es todo ceros).It's clear when this type is in use (its GUID is all zeroes).

La aplicación de ejemplo muestra las duraciones de los objetos dentro y entre las solicitudes individuales.The sample app demonstrates object lifetimes within and between individual requests. El objeto IndexModel de la aplicación de ejemplo solicita cada tipo de IOperation y OperationService.The sample app's IndexModel requests each kind of IOperation type and the OperationService. Después, la página muestra todos los valores OperationId del servicio y la clase del modelo de página mediante las asignaciones de propiedades: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.");
    }
}

Las dos salidas siguientes muestran el resultado de dos solicitudes:Two following output shows the results of two requests:

Primera solicitud:First request:

Operaciones del controlador: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

Operaciones 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

Segunda solicitud:Second request:

Operaciones del controlador: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

Operaciones OperationService:OperationService operations:

Transitorio: 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

Observe cuál de los valores OperationId varía dentro de una solicitud y entre solicitudes:Observe which of the OperationId values vary within a request and between requests:

  • Los objetos Transient siempre son diferentes.Transient objects are always different. El valor OperationId transitorio de la primera y la segunda solicitud de cliente varía tanto para las operaciones OperationService como entre solicitudes de cliente.The transient OperationId value for both the first and second client requests are different for both OperationService operations and across client requests. Se proporciona una nueva instancia para cada solicitud de servicio y solicitud de cliente.A new instance is provided to each service request and client request.
  • Los objetos con ámbito son iguales dentro de una solicitud de cliente, pero varían entre solicitudes de cliente.Scoped objects are the same within a client request but different across client requests.
  • Los objetos singleton son iguales para todos los objetos y solicitudes, independientemente de si se proporciona una instancia Operation en ConfigureServices.Singleton objects are the same for every object and every request regardless of whether an Operation instance is provided in ConfigureServices.

Llamada a servicios desde mainCall services from main

Cree un IServiceScope con IServiceScopeFactory.CreateScope para resolver un servicio con ámbito dentro del ámbito de la aplicación.Create an IServiceScope with IServiceScopeFactory.CreateScope to resolve a scoped service within the app's scope. Este método resulta útil para tener acceso a un servicio con ámbito durante el inicio para realizar tareas de inicialización.This approach is useful to access a scoped service at startup to run initialization tasks. En el siguiente ejemplo se indica cómo obtener un contexto para MyScopedService en Program.Main:The following example shows how to obtain a context for the MyScopedService in Program.Main:

public static void 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.");
        }
    }

    host.Run();
}

Validación del ámbitoScope validation

Cuando la aplicación se ejecuta en el entorno de desarrollo, el proveedor de servicios predeterminado realiza comprobaciones para confirmar lo siguiente:When the app is running in the Development environment, the default service provider performs checks to verify that:

  • Los servicios con ámbito no se resuelven directa o indirectamente desde el proveedor de servicios raíz.Scoped services aren't directly or indirectly resolved from the root service provider.
  • Los servicios con ámbito no se insertan directa o indirectamente en singletons.Scoped services aren't directly or indirectly injected into singletons.

El proveedor de servicios raíz se crea cuando se llama a BuildServiceProvider.The root service provider is created when BuildServiceProvider is called. La vigencia del proveedor de servicios raíz es la misma que la de la aplicación o el servidor cuando el proveedor se inicia con la aplicación, y se elimina cuando la aplicación se cierra.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.

De la eliminación de los servicios con ámbito se encarga el contenedor que los creó.Scoped services are disposed by the container that created them. Si un servicio con ámbito se crea en el contenedor raíz, su vigencia sube a la del singleton, ya que solo lo puede eliminar el contenedor raíz cuando la aplicación o el servidor se cierran.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. Al validar los ámbitos de servicio, este tipo de situaciones se detectan cuando se llama a BuildServiceProvider.Validating service scopes catches these situations when BuildServiceProvider is called.

Para obtener más información, vea Host web de ASP.NET Core.For more information, see Host web de ASP.NET Core.

Servicios de solicitudRequest Services

Los servicios disponibles en una solicitud de ASP.NET Core desde HttpContext se exponen mediante la colección HttpContext.RequestServices.The services available within an ASP.NET Core request from HttpContext are exposed through the HttpContext.RequestServices collection.

Los servicios de solicitud representan los servicios configurados y solicitados como parte de la aplicación.Request Services represent the services configured and requested as part of the app. Cuando los objetos especifican dependencias, estas se cumplen mediante los tipos que se encuentran en RequestServices, no en ApplicationServices.When the objects specify dependencies, these are satisfied by the types found in RequestServices, not ApplicationServices.

Por lo general, la aplicación no debe usar estas propiedades directamente.Generally, the app shouldn't use these properties directly. En su lugar, solicite los tipos que las clases requieren mediante el constructor de clases y permita que el marco de trabajo inserte las dependencias.Instead, request the types that classes require via class constructors and allow the framework inject the dependencies. Esto produce clases que son más fáciles de probar.This yields classes that are easier to test.

Nota

Se recomienda que solicite las dependencias como parámetros del constructor para obtener acceso a la colección RequestServices.Prefer requesting dependencies as constructor parameters to accessing the RequestServices collection.

Diseño de servicios para la inserción de dependenciasDesign services for dependency injection

Los procedimientos recomendados son:Best practices are to:

  • Diseñar servicios para usar la inserción de dependencias a fin de obtener sus dependencias.Design services to use dependency injection to obtain their dependencies.
  • Evite las llamadas de método estático y con estado.Avoid stateful, static method calls.
  • Evitar la creación directa de instancias de clases dependientes dentro de los servicios.Avoid direct instantiation of dependent classes within services. La creación directa de instancias se acopla al código de una implementación particular.Direct instantiation couples the code to a particular implementation.
  • Cree clases de aplicación pequeñas, bien factorizadas y probadas con facilidad.Make app classes small, well-factored, and easily tested.

Si una clase parece tener demasiadas dependencias insertadas, suele indicar que la clase tiene demasiadas responsabilidades y que esto infringe el principio de responsabilidad única (SRP).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). Trate de mover algunas de las responsabilidades de la clase a una nueva para intentar refactorizarla.Attempt to refactor the class by moving some of its responsibilities into a new class. Tenga en cuenta que las clases del modelo de página de Razor Pages y las clases de controlador MVC deben centrarse en aspectos de la interfaz de usuario.Keep in mind that Razor Pages page model classes and MVC controller classes should focus on UI concerns. Los detalles de implementación de las reglas de negocio y del acceso a datos se deben mantener en las clases pertinentes para cada uno de estos aspectos.Business rules and data access implementation details should be kept in classes appropriate to these separate concerns.

Eliminación de serviciosDisposal of services

El contenedor llama a Dispose para los tipos IDisposable que crea.The container calls Dispose for the IDisposable types it creates. Si se agrega una instancia al contenedor por código de usuario, no se elimina automáticamente.If an instance is added to the container by user code, it isn't disposed automatically.

// Services that implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}

public interface ISomeService {}
public class SomeServiceImplementation : ISomeService, IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    // The container creates the following instances and disposes them automatically:
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());

    // The container doesn't create the following instances, so it doesn't dispose of
    // the instances automatically:
    services.AddSingleton<Service3>(new Service3());
    services.AddSingleton(new Service3());
}

Reemplazo del contenedor de servicios predeterminadoDefault service container replacement

El contenedor de servicios integrado está pensado para atender las necesidades del marco de trabajo y de la mayoría de las aplicaciones de consumidor.The built-in service container is meant to serve the needs of the framework and most consumer apps. Se recomienda usar el contenedor integrado a menos que se necesite una característica específica no admitida por el contenedor.We recommend using the built-in container unless you need a specific feature that it doesn't support. Algunas de las características admitidas en contenedores de terceros no se incluyen en el contenedor integrado:Some of the features supported in 3rd party containers not found in the built-in container:

  • Inserción de propiedadesProperty injection
  • Inserción basada en nombresInjection based on name
  • Contenedores secundariosChild containers
  • Administración personalizada del ciclo de vidaCustom lifetime management
  • Compatibilidad con Func<T> para la inicialización diferidaFunc<T> support for lazy initialization

Vea el archivo readme.md de inserción de dependencias para obtener una lista de algunos de los contenedores que admiten adaptadores.See the Dependency Injection readme.md file for a list of some of the containers that support adapters.

En el ejemplo siguiente se reemplaza el contenedor integrado por Autofac:The following sample replaces the built-in container with Autofac:

  • Instale los paquetes de contenedor adecuados:Install the appropriate container package(s):

  • Configure el contenedor en Startup.ConfigureServices y devuelva IServiceProvider:Configure the container in Startup.ConfigureServices and return an IServiceProvider:

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        // Add other framework services
    
        // Add Autofac
        var containerBuilder = new ContainerBuilder();
        containerBuilder.RegisterModule<DefaultModule>();
        containerBuilder.Populate(services);
        var container = containerBuilder.Build();
        return new AutofacServiceProvider(container);
    }
    

    Para usar un contenedor de terceros, Startup.ConfigureServices debe devolver IServiceProvider.To use a 3rd party container, Startup.ConfigureServices must return IServiceProvider.

  • Configure Autofac en DefaultModule:Configure Autofac in DefaultModule:

    public class DefaultModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<CharacterRepository>().As<ICharacterRepository>();
        }
    }
    

En tiempo de ejecución, se usa Autofac para resolver tipos e insertar dependencias.At runtime, Autofac is used to resolve types and inject dependencies. Para más información sobre el uso de Autofac con ASP.NET Core, vea la documentación sobre Autofac.To learn more about using Autofac with ASP.NET Core, see the Autofac documentation.

Seguridad para subprocesosThread safety

Cree servicios de singleton seguros para subprocesos.Create thread-safe singleton services. Si un servicio de singleton tiene una dependencia en un servicio transitorio, es posible que este también deba ser seguro para subprocesos, según cómo lo use el singleton.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.

El patrón de diseño Factory Method de un servicio único, como el segundo argumento para AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), no necesita ser seguro para subprocesos.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. Al igual que un constructor de tipos (static), se garantiza que se le llame una vez mediante un único subproceso.Like a type (static) constructor, it's guaranteed to be called once by a single thread.

RecomendacionesRecommendations

  • No se admite la resolución de servicio basada en async/await y Task.async/await and Task based service resolution is not supported. C# no admite los constructores asincrónicos, por lo que el patrón recomendado es usar métodos asincrónicos después de resolver el servicio de manera sincrónica.C# does not support asynchronous constructors; therefore, the recommended pattern is to use asynchronous methods after synchronously resolving the service.

  • Evite almacenar datos y configuraciones directamente en el contenedor de servicios.Avoid storing data and configuration directly in the service container. Por ejemplo, el carro de la compra de un usuario no debería agregarse al contenedor de servicios.For example, a user's shopping cart shouldn't typically be added to the service container. La configuración debe usar el patrón de opciones.Configuration should use the options pattern. Del mismo modo, evite los objetos de tipo "contenedor de datos" que solo existen para permitir el acceso a otro objeto.Similarly, avoid "data holder" objects that only exist to allow access to some other object. Es mejor solicitar el elemento real que se necesita mediante la inserción de dependencias.It's better to request the actual item via DI.

  • Evite el acceso estático a servicios (por ejemplo, escribiendo de forma estática IApplicationBuilder.ApplicationServices para usarlo en otro lugar).Avoid static access to services (for example, statically-typing IApplicationBuilder.ApplicationServices for use elsewhere).

  • Evite el uso del patrón del localizador de servicios.Avoid using the service locator pattern. Por ejemplo, no invoque a GetService para obtener una instancia de servicio si puede usar la inserción de dependencias en su lugar:For example, don't invoke GetService to obtain a service instance when you can use DI instead:

    Incorrecto:Incorrect:

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

    Correcto:Correct:

    private readonly MyOptions _options;
    
    public MyClass(IOptionsMonitor<MyOptions> options)
    {
        _options = options.CurrentValue;
    }
    
    public void MyMethod()
    {
        var option = _options.Option;
    
        ...
    }
    
  • Otra variación del localizador de servicios que se debe evitar es insertar una fábrica que resuelva dependencias en tiempo de ejecución.Another service locator variation to avoid is injecting a factory that resolves dependencies at runtime. Estas dos prácticas combinan estrategias de Inversión de control.Both of these practices mix Inversion of Control strategies.

  • Evite el acceso estático a HttpContext (por ejemplo, IHttpContextAccessor.HttpContext).Avoid static access to HttpContext (for example, IHttpContextAccessor.HttpContext).

Al igual que sucede con todas las recomendaciones, podría verse en una situación que le obligue a ignorar alguna de ellas.Like all sets of recommendations, you may encounter situations where ignoring a recommendation is required. Las excepciones son poco frecuentes; principalmente en casos especiales dentro del propio marco de trabajo.Exceptions are rare—mostly special cases within the framework itself.

La inserción de dependencias es una alternativa a los patrones de acceso a objetos estáticos o globales.DI is an alternative to static/global object access patterns. No podrá aprovechar las ventajas de la inserción de dependencias si la combina con el acceso a objetos estáticos.You may not be able to realize the benefits of DI if you mix it with static object access.

Recursos adicionalesAdditional resources