asp.net-core
Inyección de dependencia
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 deMicrosoft.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
appsettings.json
directamente desde laappsettings.json
"mysettings" de appsettings.jsonservices.Configure<MySettings>(Configuration.GetSection("mysettings"));
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 quevalue2
es una propiedad del objetomysettings
, accedemos a él a través demysettings: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