Buscar..


Introducción

El núcleo de Aspnet se construye con la inyección de dependencia como uno de sus conceptos clave. Introduce una abstracción de contenedor conforme para que pueda reemplazar la incorporada con un contenedor de terceros de su elección.

Sintaxis

  • IServiceCollection.Add(ServiceDescriptor item);
  • IServiceCollection.AddScoped(Type serviceType);
  • IServiceCollection.AddScoped(Type serviceType, Type implementationType);
  • IServiceCollection.AddScoped(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddScoped<TService>()
  • IServiceCollection.AddScoped<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddScoped<TService, TImplementation>()
  • IServiceCollection.AddScoped<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceCollection.AddSingleton(Type serviceType);
  • IServiceCollection.AddSingleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddSingleton(Type serviceType, Type implementationType);
  • IServiceCollection.AddSingleton(Type serviceType, object implementationInstance);
  • IServiceCollection.AddSingleton<TService>()
  • IServiceCollection.AddSingleton<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddSingleton<TService>(TService implementationInstance)
  • IServiceCollection.AddSingleton<TService, TImplementation>()
  • IServiceCollection.AddSingleton<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceCollection.AddTransient(Type serviceType);
  • IServiceCollection.AddTransient(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddTransient(Type serviceType, Type implementationType);
  • IServiceCollection.AddTransient<TService>()
  • IServiceCollection.AddTransient<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddTransient<TService, TImplementation>()
  • IServiceCollection.AddTransient<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceProvider.GetService(Type serviceType)
  • IServiceProvider.GetService<T>()
  • IServiceProvider.GetServices(Type serviceType)
  • IServiceProvider.GetServices<T>()

Observaciones

Para usar variantes genéricas de los métodos IServiceProvider , debe incluir el siguiente espacio de nombres:

using Microsoft.Extensions.DependencyInjection;

Registrarse y resolver manualmente

La forma preferida de describir las dependencias es mediante el uso de la inyección de constructor que sigue el Principio de Dependencias Explícitas :

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}

Startup.cs (ConfigureServices)

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

    services.AddTransient<ITestService, TestService>();
}

HomeController.cs

using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(ITestService service)
        {
            int rnd = service.GenerateRandom();
        }
    }
}

Registrar dependencias

El contenedor incorporado viene con un conjunto de características incorporadas:

Control de por vida

public void ConfigureServices(IServiceCollection services)
    {
        // ...
    
        services.AddTransient<ITestService, TestService>();
        // or
        services.AddScoped<ITestService, TestService>();
        // or
        services.AddSingleton<ITestService, TestService>();
        // or
        services.AddSingleton<ITestService>(new TestService());
    }
  • AddTransient : Creado cada vez que se resuelve
  • AddScoped : creado una vez por solicitud
  • AddSingleton : Lazily creado una vez por aplicación
  • AddSingleton (instancia) : proporciona una instancia creada previamente por aplicación

Dependencias enumerables

También es posible registrar dependencias enumerables:

 services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl1>());
 services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl2>());

A continuación, puede consumirlos de la siguiente manera:

public class HomeController : Controller
{
    public HomeController(IEnumerable<ITestService> services)
    {
        // do something with services.
    }
}

Dependencias genéricas

También puedes registrar dependencias genéricas:

services.Add(ServiceDescriptor.Singleton(typeof(IKeyValueStore<>), typeof(KeyValueStore<>)));

Y luego consumirlo de la siguiente manera:

public class HomeController : Controller
{
    public HomeController(IKeyValueStore<UserSettings> userSettings)
    {
        // do something with services.
    }
}

Recuperar dependencias en un controlador

Una vez registrada, se puede recuperar una dependencia agregando parámetros en el constructor del controlador.

// ...
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(ITestService service)
        {
            int rnd = service.GenerateRandom();
        }
    }
}

Inyectar una dependencia en una acción del controlador

Una característica incorporada menos conocida es la inyección de acción del controlador que utiliza el FromServicesAttribute .

[HttpGet]
public async Task<IActionResult> GetAllAsync([FromServices]IProductService products)
{
     return Ok(await products.GetAllAsync());
}

Una nota importante es que el [FromServices] no se puede utilizar como mecanismo general de "Inyección de propiedades" o "Inyección de métodos". Solo se puede usar en los parámetros de método de una acción de controlador o constructor de controlador (aunque en el constructor está obsoleto, ya que el sistema ASP.NET Core DI ya usa inyección de constructor y no se requieren marcadores adicionales).

No se puede utilizar en ningún lugar fuera de un controlador, acción del controlador . También es muy específico para ASP.NET Core MVC y reside en el ensamblado Microsoft.AspNetCore.Mvc.Core .

Cita original del problema de ASP.NET Core MVC GitHub ( Limitar [FromServices] para aplicar solo a parámetros ) con respecto a este atributo:

@rynowak:

@Eilon:

El problema con las propiedades es que a muchas personas les parece que se puede aplicar a cualquier propiedad de cualquier objeto.

De acuerdo, hemos tenido una serie de problemas publicados por los usuarios con confusión acerca de cómo se debe utilizar esta función. Realmente ha habido una gran cantidad de comentarios de ambos tipos "[FromServices] es raro y no me gusta" y "[FromServices] me ha confundido". Se siente como una trampa, y algo que el equipo todavía estaría respondiendo a las preguntas en los próximos años.

Creemos que el escenario más valioso para [FromServices] es el parámetro del método para una acción para un servicio que solo necesita en ese lugar.

/ cc @ danroth27 - docs cambios

A cualquier persona enamorada del [FromServices] actual, le recomiendo encarecidamente que busque un sistema DI que pueda inyectar propiedades (por ejemplo, Autofac).

Notas:

  • Cualquier servicio registrado con .NET Core Dependency Injection se puede inyectar dentro de la acción de un controlador usando el atributo [FromServices] .

  • El caso más relevante es cuando necesita un servicio solo en un método de acción simple y no desea saturar el constructor de su controlador con otra dependencia, que solo se usará una vez.

  • No se puede utilizar fuera de ASP.NET Core MVC (es decir, aplicaciones puras de .NET Framework o .NET Core consola), ya que reside en Microsoft.AspNetCore.Mvc.Core ensamblado de Microsoft.AspNetCore.Mvc.Core .

  • Para la inyección de propiedad o método, debe utilizar uno de los contenedores de IoC de terceros disponibles (Autofac, Unity, etc.).

El patrón de opciones / opciones de inyección en servicios

Con ASP.NET Core, el equipo de Microsoft también introdujo el patrón de Opciones, que permite tener opciones tipificadas y una vez configurada la capacidad de inyectar las opciones en sus servicios.

Primero comenzamos con una clase fuerte, que mantendrá nuestra configuración.

public class MySettings 
{
    public string Value1 { get; set; }
    public string Value2 { get; set; }
}

Y una entrada en el appsettings.json .

{
  "mysettings" : {
    "value1": "Hello",
    "value2": "World"
  }
}

A continuación lo inicializamos en la clase de inicio. Hay dos maneras de hacer esto

  1. appsettings.json directamente desde la appsettings.json "mysettings" de appsettings.json

    services.Configure<MySettings>(Configuration.GetSection("mysettings"));
    
  2. Hacerlo manualmente

    services.Configure<MySettings>(new MySettings 
    {
        Value1 = "Hello",
        Value2 = Configuration["mysettings:value2"]
    });
    

    Cada nivel de jerarquía de appsettings.json está separado por : Dado que value2 es una propiedad del objeto mysettings , accedemos a él a través de mysettings:value2 .

Finalmente, podemos insertar las opciones en nuestros servicios, usando la IOptions<T>

public class MyService : IMyService
{
    private readonly MySettings settings;

    public MyService(IOptions<MySettings> mysettings) 
    {
        this.settings = mySettings.Value;
    }
}

Observaciones

Si las IOptions<T> no están configuradas durante el inicio, inyectar IOptions<T> inyectará la instancia predeterminada de la clase T

Uso de servicios de ámbito durante el inicio de la aplicación / Secuencias de base de datos

Resolver los servicios de ámbito durante el inicio de la aplicación puede ser difícil, ya que no hay solicitud y, por lo tanto, no hay servicio de ámbito.

Resolver un servicio con ámbito durante el inicio de la aplicación a través de app.ApplicationServices.GetService<AppDbContext>() puede causar problemas, ya que se creará en el ámbito del contenedor global, lo que lo convierte en un singleton con la vida útil de la aplicación, lo que puede llevar a excepciones como Cannot access a disposed object in ASP.NET Core when injecting DbContext .

El siguiente patrón resuelve el problema creando primero un nuevo alcance y luego resolviendo los servicios con ámbito desde él, luego, una vez que se realiza el trabajo, se desecha el contenedor con ámbito.

public Configure(IApplicationBuilder app)
{
    // serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
    using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        var db = serviceScope.ServiceProvider.GetService<AppDbContext>();

        if (await db.Database.EnsureCreatedAsync())
        {
            await SeedDatabase(db);
        }
    }
}

Esta es una forma semioficial del equipo central de Entity Framework para generar datos durante el inicio de la aplicación y se refleja en la aplicación de ejemplo MusicStore .

Resolver controladores, ViewComponents y TagHelpers mediante inyección de dependencia

Por defecto, los controladores, ViewComponents y TagHelpers no se registran y resuelven a través del contenedor de inyección de dependencias. Esto resulta en la incapacidad de hacer, por ejemplo, la inyección de propiedades cuando se utiliza un contenedor de Inversión de Control (IoC) de terceros como AutoFac.

Para que ASP.NET Core MVC resuelva estos tipos también a través de IoC, es necesario agregar los siguientes registros en Startup.cs (tomado de la muestra oficial ControllersFromService en GitHub)

public void ConfigureServices(IServiceCollection services)
{
    var builder = services
        .AddMvc()
        .ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear())
        .AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly)
        .ConfigureApplicationPartManager(manager =>
        {
            manager.ApplicationParts.Add(new TypesPart(
              typeof(AnotherController),
              typeof(ComponentFromServicesViewComponent),
              typeof(InServicesTagHelper)));

            manager.FeatureProviders.Add(new AssemblyMetadataReferenceFeatureProvider());
        })
        .AddControllersAsServices()
        .AddViewComponentsAsServices()
        .AddTagHelpersAsServices();

    services.AddTransient<QueryValueService>();
    services.AddTransient<ValueService>();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

Ejemplo de inyección de dependencia simple (sin Startup.cs)

Esto le muestra cómo usar el paquete de nuget Microsoft.Extensions.DependencyInjection sin el uso del WebHostBuilder de WebHostBuilder (por ejemplo, cuando desea construir algo más que una aplicación web):

internal class Program
{
    public static void Main(string[] args)
    {
        var services = new ServiceCollection(); //Creates the service registry
        services.AddTransient<IMyInterface, MyClass>(); //Add registration of IMyInterface (should create an new instance of MyClass every time)
        var serviceProvider = services.BuildServiceProvider(); //Build dependencies into an IOC container
        var implementation = serviceProvider.GetService<IMyInterface>(); //Gets a dependency

        //serviceProvider.GetService<ServiceDependingOnIMyInterface>(); //Would throw an error since ServiceDependingOnIMyInterface is not registered
        var manualyInstaniate = new ServiceDependingOnIMyInterface(implementation); 

        services.AddTransient<ServiceDependingOnIMyInterface>();
        var spWithService = services.BuildServiceProvider(); //Generaly its bad practise to rebuild the container because its heavey and promotes use of anti-pattern.
        spWithService.GetService<ServiceDependingOnIMyInterface>(); //only now i can resolve
    }
}

interface IMyInterface
{
}

class MyClass : IMyInterface
{
}

class ServiceDependingOnIMyInterface
{
    private readonly IMyInterface _dependency;

    public ServiceDependingOnIMyInterface(IMyInterface dependency)
    {
        _dependency = dependency;
    }
}

Funcionamiento interno de Microsoft.Extensions.DependencyInjection

IServiceCollection

Para comenzar a construir un contenedor IOC con el paquete de nuget DI de Microsoft, comience con la creación de una IServiceCollection . Puedes usar la colección ya provista: ServiceCollection :

var services = new ServiceCollection();

Este IServiceCollection no es más que una implementación de: IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable

Todos los métodos siguientes son solo métodos de extensión para agregar instancias de ServiceDescriptor a la lista:

services.AddTransient<Class>(); //add registration that is always recreated
services.AddSingleton<Class>(); // add registration that is only created once and then re-used
services.AddTransient<Abstract, Implementation>(); //specify implementation for interface
services.AddTransient<Interface>(serviceProvider=> new Class(serviceProvider.GetService<IDependency>())); //specify your own resolve function/ factory method.
services.AddMvc(); //extension method by the MVC nuget package, to add a whole bunch of registrations.
// etc..

//when not using an extension method:
services.Add(new ServiceDescriptor(typeof(Interface), typeof(Class)));

IServiceProvider

El proveedor de servicios es el que 'compila' todos los registros para que puedan usarse rápidamente, esto se puede hacer con services.BuildServiceProvider() que es básicamente una extensión de mehtod para:

var provider = new ServiceProvider( services, false); //false is if it should validate scopes

Tras bambalinas, cada ServiceDescriptor en IServiceCollection se compila a un método de fábrica Func<ServiceProvider, object> donde object es el tipo de retorno y es: la instancia creada del tipo de implementación, el Singleton o su propio método de fábrica definido.

Estos registros se agregan a ServiceTable que es básicamente un ConcurrentDictionary con la clave que es ServiceType y el valor del método de fábrica definido anteriormente.

Resultado

Ahora tenemos un ConcurrentDictionary<Type, Func<ServiceProvider, object>> que podemos usar al mismo tiempo para solicitar la creación de Servicios para nosotros. Para mostrar un ejemplo básico de cómo podría haberse visto esto.

  var serviceProvider = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
  var factoryMethod = serviceProvider[typeof(MyService)];
  var myServiceInstance = factoryMethod(serviceProvider)

¡Asi no es como funciona esto!

Este ConcurrentDictionary es una propiedad de ServiceTable que es una propiedad de ServiceProvider



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow