Tipos Integrados e Inyección de Dependencias en ASP.NET Core
ASP.NET Core no permite el registro de tipos integrados (int
, string
, bool
, etc.) en el contenedor de servicios; puesto que sería ilógico devolver siempre el mismo valor cuando se solicite un tipo int
, o un string
. Sin embargo, en caso de requerirlo puede implementar las siguientes alternativas:
- Definir el constructor del servicio con parámetros o argumentos opcionales, donde cada parámetro opcional tiene un valor predeterminado como parte de su definición.
- Crear manualmente la instancia del servicio al momento de registrarlo, y proveer así los valores de los parámetros definidos en el constructor (Patrón Factory).
Considere el siguiente servicio IUserManager
y su implementación UserManager
. El mismo que se va a usar en los 2 ejemplos posteriores.
public interface IUserManager
{
void Create(string username);
}
public class UserManager : IUserManager
{
public void Create(string username)
{
Console.WriteLine($"El usuario '{username}' fue creado.");
}
}
Constructor con Parámetros Opcionales
Los métodos de extensión AddTransient
, AddScoped
y AddSingleton
no solo permiten agregar al contenedor tipos de servicios y sus implementaciones (como IUserManager
y UserManager
) sino también solo esta última.
Como ejemplo, considere el siguiente servicio SignInManager.cs
using System;
public class SignInManager
{
public SignInManager(string host = "http://localhost",
int port = 7125)
{ }
public void SignIn(string username)
{
Console.WriteLine($"El usuario '{username}' inicio sesión.");
}
}
En el código C# anterior:
- Se define un constructor con argumentos opcionales, donde cada parámetro tiene un valor predeterminado.
- Puesto que cada parámetro tiene un valor predeterminado como parte de su definición, el contenedor registrará el servicio sin ningún problema.
Si los parámetros no se definen como opcionales, ocurrirán excepciones en tiempo de ejecución de tipo AggregateException
e InvalidOperationException
. Indicando así, que el servicio no pudo ser registrado en el contenedor.
System.AggregateException
HResult=0x80131500
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: TiposIntegradosDiAspNetCore.SignInManager Lifetime: Scoped ImplementationType: TiposIntegradosDiAspNetCore.SignInManager': Unable to resolve service for type 'System.TimeSpan' while attempting to activate 'TiposIntegradosDiAspNetCore.SignInManager'.)
Source=Microsoft.Extensions.DependencyInjection
StackTrace:
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.Hosting.HostApplicationBuilder.Build()
at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
at Program.<Main>$(String[] args) in C:\Program.cs:line 13
This exception was originally thrown at this call stack:
[External Code]
Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: TiposIntegradosDiAspNetCore.SignInManager Lifetime: Scoped ImplementationType: TiposIntegradosDiAspNetCore.SignInManager': Unable to resolve service for type 'System.TimeSpan' while attempting to activate 'TiposIntegradosDiAspNetCore.SignInManager'.
Inner Exception 2:
InvalidOperationException: Unable to resolve service for type 'System.TimeSpan' while attempting to activate 'TiposIntegradosDiAspNetCore.SignInManager'.
Registro de los servicios en el contenedor:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IUserManager, UserManager>();
builder.Services.AddSingleton<SignInManager>();
var app = builder.Build();
//Se omitió código por razón de brevedad.
app.Run();
En el código C# anterior:
- Se registra el servicio
IUserManager
y su implementaciónUserManager
, por lo que cada vez que se soliciteIUserManager
, el contenedor devolverá una instancia de su implementación:UserManager
. - Se registra el servicio
SignInManager
, por lo que cada vez que se soliciteSignInManager
, el contenedor devolverá una instancia del mismo objeto:SignInManager
. - La instancia de
SignInManager
que el contendor de servicios proveerá, usará los valores ya definidos en el constructor de los parámetros opcionaleshost
yport
.
Creación Manual de la Instancia
Si no se definen los valores predeterminados en el constructor del servicio, deberán proveerse manualmente cuando el contenedor reciba una solicitud. De esta forma el contenedor devolverá un objeto "preconstruido" siempre que se requiera una instancia del servicio.
Esta forma de definir como un objeto será creado o instanciado se llama Factory Pattern.
Factory es un patrón de diseño creacional que define la forma en que debe crearse un objeto. En lugar de usar la palabra clave new
para crear el objeto, se define un método que es el responsable de realizar esta acción.
ASP.NET Core ofrece una forma sencilla de registrar un servicio implementando el patrón Factory, y es mediante una función lambda, tal como se observa a continuación:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IUserManager, UserManager>();
builder.Services.AddScoped(
provider =>
new SignInManager
(
host: "http://localhost",
port: 7125
));
var app = builder.Build();
//Se omitió código por razón de brevedad.
app.Run();
En el código C# anterior:
-
Se utiliza la sobrecarga del método de extensión
AddScoped
, que recibe como parámetro una función que definirá como crear la instancia del servicio, y devolverá unIServiceCollection
, que tendrá una referencia a esta instancia después de que se haya completado la operación:AddSingletonpublic static IServiceCollection AddScoped<TService>( this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;
- Puesto que para crear el servicio se está utilizando una función, la vida útil del servicio no esta restringida a
Singleton
. - Cada vez que se solicite el servicio
SignInManager
, el contenedor invocara la función lambda que define como crearlo, implementando así el patrón Factory. - Hay que tener presente que la función Factory debe ser sincrónica, por lo que no se puede utilizar la palabra clave
async
.
De esta forma no es necesario definir los parámetros host
y port
como opcionales, ya que sus valores serán provistos por el contenedor al momento de solicitar una instancia del servicio.
using System;
public class SignInManager
{
public SignInManager(string host,
int port)
{ }
public void SignIn(string username)
{
Console.WriteLine($"El usuario '{username}' inicio sesión.");
}
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
public class RegisterModel : PageModel
{
private readonly IUserManager _userManager;
private readonly SignInManager _signInManager;
public RegisterModel(IUserManager userManager,
SignInManager signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
public ContentResult OnGet(string username)
{
_userManager.Create(username);
_signInManager.SignIn(username);
return Content($"¡El usuario '{username}' fue registrado e inicio sesión satisfactoriamente!");
}
}
Artículos Relacionados
- Inyección de Dependencias en ASP.NET Core.
- Duración de los Servicios de Inyección de Dependencias en ASP.NET Core.