Inyección de Dependencias, Configuración y Estructura
A medida que una API crece, es vital estructurar el código de manera que sea mantenible. Este módulo se centra en cómo las Minimal APIs manejan la Inyección de Dependencias (DI), la configuración de la aplicación y la agrupación de rutas.
3.1. Trabajando con Servicios e Inyección de Dependencias (DI)
La Inyección de Dependencias es un patrón fundamental en .NET. En Minimal APIs, la DI se gestiona a través del contenedor de servicios accesible mediante builder.Services.
Registro de servicios
Antes de usar un servicio, debe registrarse con un ciclo de vida específico:
| Ciclo de Vida | Método | Descripción |
|---|---|---|
| Singleton | builder.Services.AddSingleton<T>() |
Se crea una única instancia la primera vez que se solicita y se reutiliza por el resto de la vida de la aplicación. Ideal para servicios sin estado o cachés. |
| Scoped | builder.Services.AddScoped<T>() |
Se crea una instancia por cada solicitud (request) HTTP. Ideal para servicios que interactúan con una base de datos o el contexto de la solicitud. |
| Transient | builder.Services.AddTransient<T>() |
Se crea una nueva instancia cada vez que se solicita. Ideal para servicios ligeros o utilidades que se usan múltiples veces dentro de la misma solicitud. |
Acceso a servicios en el delegado del endpoint
Minimal APIs simplifica la inyección: si un argumento en el delegado del endpoint no proviene de la ruta o la consulta, el framework lo busca automáticamente en el contenedor de DI.
app.MapGet("/items-con-servicio", (IServicio servicio) => {
// 'servicio' es automáticamente inyectado y resuelto por el DI.
return Results.Ok(servicio.ObtenerDatos());
});
3.2. Configuración de la Aplicación
La configuración se gestiona a través de la interfaz IConfiguration, que lee datos de archivos como appsettings.json, variables de entorno y secretos.
Acceso a IConfiguration y archivos appsettings.json
El objeto IConfiguration está disponible mediante inyección.
Ejemplo de appsettings.json:
{
"Logging": { ... },
"Settings": {
"NombreAplicacion": "MiAPI Minimal",
"MaxItems": 100
}
}
Podemos acceder a estos valores directamente en el código de la API.
Inyección de opciones fuertemente tipadas (IOptions<T>)
Para un código más seguro y legible, es una práctica recomendada mapear secciones de la configuración a clases C#. Esto se hace usando IOptions<T>.
- Definir la clase de opciones (ej.
SettingsOptions). - Registrar las opciones en
Program.cs:builder.Services.Configure<SettingsOptions>( builder.Configuration.GetSection("Settings")); - Inyectar en el endpoint:
app.MapGet("/config", (IOptions<SettingsOptions> options) => { return $"Nombre: {options.Value.NombreAplicacion}"; });
3.3. Estructura del Código y Agrupación
Cuando se tienen docenas de endpoints, definirlos todos en Program.cs se vuelve inmanejable.
Uso de la extensión app.MapGroup() para organizar rutas
app.MapGroup() permite crear prefijos de ruta y aplicar configuraciones comunes (como seguridad o documentación) a un conjunto de endpoints.
// Crea un grupo de rutas con el prefijo "/api/v1/productos"
var productosApi = app.MapGroup("/api/v1/productos");
productosApi.MapGet("/", () => { /* ... */ });
productosApi.MapPost("/", () => { /* ... */ });
Buenas prácticas: Separación de la lógica de endpoints (Endpoint Handlers)
Para mantener limpio Program.cs, la lógica de mapeo y los delegados deben moverse a otras clases (a menudo llamadas Endpoint Handlers o Route Groups).
La idea es tener un método estático que reciba WebApplication y encapsule todo el mapeo:
// En Program.cs
app.MapGruposDeRutas();
// En la clase RouteGroupExtensions.cs
public static class RouteGroupExtensions
{
public static void MapGruposDeRutas(this WebApplication app)
{
// ... Lógica de MapGroup aquí.
}
}
Esto permite delegar la responsabilidad de la configuración de rutas fuera del archivo principal.
// Usings necesarios.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Http.HttpResults;
// ====================================================================
// MODELOS DE CONFIGURACIÓN Y SERVICIOS
// ====================================================================
// 3.2. Configuración Fuertemente Tipada (IOptions<T>)
public class SettingsOptions
{
public const string Settings = "Settings"; // Clave de la sección en appsettings.json
public string NombreAplicacion { get; set; } = "API por Defecto";
public int MaxItems { get; set; }
}
// Interfaz para Inyección de Dependencias
public interface IItemService
{
List<string> ObtenerDatos();
}
// Implementación del Servicio (Ciclo de Vida Singleton)
public class MockItemService : IItemService
{
private readonly List<string> _items = new List<string> { "Item A", "Item B", "Item C" };
public List<string> ObtenerDatos() => _items;
}
// ====================================================================
// APLICACIÓN PRINCIPAL (Program.cs)
// ====================================================================
var builder = WebApplication.CreateBuilder(args);
// --------------------------------------------------------------------
// MÓDULO 3: INYECCIÓN DE DEPENDENCIAS Y CONFIGURACIÓN
// --------------------------------------------------------------------
// 3.1. Registro de Servicios (Inyección de Dependencias)
// Registramos el servicio MockItemService con ciclo de vida Singleton.
builder.Services.AddSingleton<IItemService, MockItemService>();
// 3.2. Registro de Opciones Fuertemente Tipadas
// Mapeamos la sección "Settings" de appsettings.json a la clase SettingsOptions.
builder.Services.Configure<SettingsOptions>(
builder.Configuration.GetSection(SettingsOptions.Settings));
// 3.3. Estructura del Código: Añadir servicios para la agrupación
// (Opcional, pero útil para demostrar la inyección en el grupo)
builder.Services.AddTransient<OtroServicio>(); // Un servicio Scoped o Transient para el grupo
var app = builder.Build();
// Configuración de Middleware
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// --------------------------------------------------------------------
// ENDPOINTS DEL MÓDULO 3
// --------------------------------------------------------------------
// Endpoint 1: Inyección de un servicio registrado (IItemService)
app.MapGet("/api/v1/items-con-servicio", (IItemService itemService) =>
{
// El servicio se inyecta automáticamente.
var datos = itemService.ObtenerDatos();
return Results.Ok(new { Mensaje = "Datos obtenidos del servicio inyectado", Datos = datos });
})
.WithName("ItemsConServicio");
// Endpoint 2: Inyección de Configuración (IOptions<T>)
app.MapGet("/api/v1/configuracion", (IOptions<SettingsOptions> options) =>
{
var settings = options.Value;
return Results.Ok(new
{
settings.NombreAplicacion,
settings.MaxItems,
Mensaje = "Opciones de configuración inyectadas correctamente."
});
})
.WithName("Configuracion");
// --------------------------------------------------------------------
// 3.3. AGRUPACIÓN DE RUTAS CON app.MapGroup()
// --------------------------------------------------------------------
// Llamamos al método de extensión para mantener limpio Program.cs (Mejor Práctica)
app.MapDemoGroups();
// --------------------------------------------------------------------
// EJECUCIÓN
// --------------------------------------------------------------------
app.Run();
// ====================================================================
// EXTENSIÓN PARA AGRUPACIÓN Y MANEJO DE ENDPOINTS FUERA DE Program.cs
// ====================================================================
/// <summary>
/// Clase para un servicio simple usado en la extensión de rutas.
/// </summary>
public class OtroServicio
{
public string ObtenerInfo() => "Información de un servicio del grupo de rutas.";
}
/// <summary>
/// Método de extensión para agrupar y mapear rutas fuera del archivo principal.
/// </summary>
public static class RouteGroupExtensions
{
public static void MapDemoGroups(this WebApplication app)
{
// 1. Crear el grupo con el prefijo /api/v2/grupo
var grupoV2 = app.MapGroup("/api/v2/grupo")
// Podemos añadir Middlewares o filtros a todo el grupo (ej. .RequireAuthorization())
.WithTags("Grupo V2");
// Endpoint 3: Endpoint dentro del grupo
// Ruta: /api/v2/grupo/saludo
grupoV2.MapGet("/saludo", () =>
{
return Results.Ok("¡Hola desde el Grupo V2!");
})
.WithName("GrupoV2Saludo");
// Endpoint 4: Inyección dentro del grupo
// Ruta: /api/v2/grupo/info
grupoV2.MapGet("/info", (OtroServicio otroServicio) =>
{
// El servicio 'OtroServicio' es inyectado por el framework.
return Results.Ok(new { Mensaje = otroServicio.ObtenerInfo() });
})
.WithName("GrupoV2Info");
}
}
// appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
// Sección de configuración usada en el Módulo 3
"Settings": {
"NombreAplicacion": "MiAPI Tarea v3.0",
"MaxItems": 50
}
}