Resolver instancias con ASP.NET Core DI desde ConfigureServices
¿Cómo resuelvo manualmente un tipo utilizando el marco de inyección de dependencia integrado de ASP.NET Core MVC?
Configurar el contenedor es bastante fácil:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddTransient<ISomeService, SomeConcreteService>();
}
¿ Pero cómo puedo solucionarlo ISomeService
sin realizar la inyección? Por ejemplo, quiero hacer esto:
ISomeService service = services.Resolve<ISomeService>();
No existen tales métodos en IServiceCollection
.
La IServiceCollection
interfaz se utiliza para construir un contenedor de inyección de dependencias. Una vez que esté completamente compilado, se compone en una IServiceProvider
instancia que puede usar para resolver servicios. Puedes inyectar un IServiceProvider
en cualquier clase. Las clases IApplicationBuilder
y HttpContext
también pueden proporcionar al proveedor de servicios, a través de sus propiedades ApplicationServices
o RequestServices
respectivamente.
IServiceProvider
define un GetService(Type type)
método para resolver un servicio:
var service = (IFooService)serviceProvider.GetService(typeof(IFooService));
También hay varios métodos de extensión convenientes disponibles, como serviceProvider.GetService<IFooService>()
(agregar un using
for Microsoft.Extensions.DependencyInjection
).
Resolver servicios dentro de la clase de inicio.
Inyectando dependencias
El proveedor de servicios de alojamiento del tiempo de ejecución puede inyectar ciertos servicios en el constructor de la Startup
clase, como IConfiguration
,
IWebHostEnvironment
( IHostingEnvironment
en versiones anteriores a 3.0) ILoggerFactory
y IServiceProvider
. Tenga en cuenta que esta última es una instancia creada por la capa de alojamiento y contiene sólo los servicios esenciales para iniciar una aplicación .
El ConfigureServices()
método no permite inyectar servicios, solo acepta un IServiceCollection
argumento. Esto tiene sentido porque ConfigureServices()
es donde registras los servicios requeridos por tu aplicación. Sin embargo, puedes utilizar los servicios inyectados en el constructor de la startup aquí, por ejemplo:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Use Configuration here
}
Cualquier servicio registrado ConfigureServices()
se puede inyectar en el Configure()
método; puede agregar una cantidad arbitraria de servicios después del IApplicationBuilder
parámetro:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IFooService>();
}
public void Configure(IApplicationBuilder app, IFooService fooService)
{
fooService.Bar();
}
Resolver dependencias manualmente
Si necesita resolver servicios manualmente, preferiblemente debe utilizar lo ApplicationServices
proporcionado IApplicationBuilder
en el Configure()
método:
public void Configure(IApplicationBuilder app)
{
var serviceProvider = app.ApplicationServices;
var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}
Es posible pasar y usar directamente an IServiceProvider
en el constructor de su Startup
clase, pero como se indicó anteriormente, esto contendrá un subconjunto limitado de servicios y, por lo tanto, tiene una utilidad limitada:
public Startup(IServiceProvider serviceProvider)
{
var hostingEnv = serviceProvider.GetService<IWebHostEnvironment>();
}
Si debe resolver servicios en el ConfigureServices()
método, se requiere un enfoque diferente. Puede crear un intermediario IServiceProvider
a partir de la IServiceCollection
instancia que contiene los servicios que se han registrado hasta ese momento :
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFooService, FooService>();
// Build the intermediate service provider
var sp = services.BuildServiceProvider();
// This will succeed.
var fooService = sp.GetService<IFooService>();
// This will fail (return null), as IBarService hasn't been registered yet.
var barService = sp.GetService<IBarService>();
}
Tenga en cuenta:
generalmente debe evitar resolver servicios dentro del ConfigureServices()
método, ya que este es en realidad el lugar donde está configurando los servicios de la aplicación. A veces solo necesitas acceso a una IOptions<MyOptions>
instancia. Puede lograr esto vinculando los valores de la IConfiguration
instancia a una instancia de MyOptions
(que es esencialmente lo que hace el marco de opciones):
public void ConfigureServices(IServiceCollection services)
{
var myOptions = new MyOptions();
Configuration.GetSection("SomeSection").Bind(myOptions);
}
O use una sobrecarga para AddSingleton/AddScoped/AddTransient
:
// Works for AddScoped and AddTransient as well
services.AddSingleton<IBarService>(sp =>
{
var fooService = sp.GetRequiredService<IFooService>();
return new BarService(fooService);
}
La resolución manual de servicios (también conocida como Localizador de servicios) generalmente se considera un antipatrón . Si bien tiene sus casos de uso (para marcos y/o capas de infraestructura), debes evitarlo tanto como sea posible.
La resolución manual de instancias implica el uso de la IServiceProvider
interfaz:
Resolver la dependencia en Startup.ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IMyService>();
}
Resolver dependencias en Startup.Configure
public void Configure(
IApplicationBuilder application,
IServiceProvider serviceProvider)
{
// By type.
var service1 = (MyService)serviceProvider.GetService(typeof(MyService));
// Using extension method.
var service2 = serviceProvider.GetService<MyService>();
// ...
}
Resolver dependencias en Startup.Configure en ASP.NET Core 3
public void Configure(
IApplicationBuilder application,
IWebHostEnvironment webHostEnvironment)
{
application.ApplicationServices.GetService<MyService>();
}
Uso de servicios inyectados en tiempo de ejecución
Algunos tipos se pueden inyectar como parámetros del método:
public class Startup
{
public Startup(
IHostingEnvironment hostingEnvironment,
ILoggerFactory loggerFactory)
{
}
public void ConfigureServices(
IServiceCollection services)
{
}
public void Configure(
IApplicationBuilder application,
IHostingEnvironment hostingEnvironment,
IServiceProvider serviceProvider,
ILoggerFactory loggerfactory,
IApplicationLifetime applicationLifetime)
{
}
}
Resolución de dependencias en acciones del controlador
[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";
Si generas una aplicación con una plantilla tendrás algo como esto en la Startup
clase:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
}
Luego puedes agregar dependencias allí, por ejemplo:
services.AddTransient<ITestService, TestService>();
Si desea acceder ITestService
a su controlador, puede agregar IServiceProvider
el constructor y se inyectará:
public HomeController(IServiceProvider serviceProvider)
Luego podrás resolver el servicio que agregaste:
var service = serviceProvider.GetService<ITestService>();
Tenga en cuenta que para utilizar la versión genérica debe incluir el espacio de nombres con las extensiones:
using Microsoft.Extensions.DependencyInjection;
ITestService.cs
public interface ITestService
{
int GenerateRandom();
}
TestService.cs
public class TestService : ITestService
{
public int GenerateRandom()
{
return 4;
}
}
Startup.cs (Configurar servicios)
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddTransient<ITestService, TestService>();
}
InicioController.cs
using Microsoft.Extensions.DependencyInjection;
namespace Core.Controllers
{
public class HomeController : Controller
{
public HomeController(IServiceProvider serviceProvider)
{
var service = serviceProvider.GetService<ITestService>();
int rnd = service.GenerateRandom();
}