ASP.NET CORE

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:

Considere el siguiente servicio IUserManager y su implementación UserManager. El mismo que se va a usar en los 2 ejemplos posteriores.

IUserManager.cs y UserManager.cs
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

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:

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.

AggregateException
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:\YoFrE\Proyectos\SitioWeb\Code\Source\aspdotnetcore\di\03\TiposIntegradosDiAspNetCore\TiposIntegradosDiAspNetCore\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:

Program.cs
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:

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.

Información
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:

Program.cs
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:

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.

SignInManager.cs
using System;

public class SignInManager
{
    public SignInManager(string host,
                         int port)
    { }

    public void SignIn(string username)
    {
        Console.WriteLine($"El usuario '{username}' inicio sesión.");
    }
}
Register.razor
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

Recursos Adicionales