La generación de código en tiempo de compilación es una técnica fundamental en la ingeniería de software moderna para aumentar la productividad del desarrollo y la calidad del código. Los generadores de código fuente construidos sobre la plataforma de compilación Roslyn de C# ofrecen características técnicas clave:
- Generación en Tiempo de Compilación: El código objetivo se produce durante la fase de compilación, eliminando cualquier sobrecarga de rendimiento en tiempo de ejecución.
- Compatibilidad con el Sistema de Tipos Fuerte: El código generado se adhiere completamente al sistema de tipos de C#, garantizando la seguridad de tipos.
- Automatización del Código Repetitivo: Los mecanismos de plantillas eliminan las tareas de codificación repetitivas.
- Coherencia del Código: Los generadores aseguran la uniformidad del estilo y la estructura del código.
Arquitectura del Generador de Código .NET
El generador de código que analizamos aquí se basa en la plataforma de compilación Microsoft Roslyn. Adopta una arquitectura modular que consta de dos componentes principales:
- GeneradorDeEntidades: Encargado de la generación de código para modelos de datos.
- GeneradorDeServiciosHttp: Este es el componente central, responsable de generar automáticamente implementaciones completas de clientes HTTP basadas en definiciones de interfaz.
Funcionalidades Clave de GeneradorDeServiciosHttp
| Categoría de Funcionalidad | Capacidad Principal | Contenido Generado | Características Técnicas | Valor de Aplicación |
|---|---|---|---|---|
| Estructuras Base | Clases base abstractas, implementaciones concretas | Implementaciones de partial class |
Soporte para herencia, métodos parciales | Extensión arquitectónica, personalización |
| Soporte para Métodos HTTP | GET, POST, PUT, DELETE, etc. | Implementación completa de métodos HTTP | Programación asíncrona, tokens de cancelación | Soporte RESTful exhaustivo |
| Manejo de Parámetros | Rutas, consultas, cuerpo, encabezados | Lógica de análisis y conversión de parámetros | Seguridad de tipos, codificación automática | Simplificación de llamadas API complejas |
| Gestión de Tokens | Diversos métodos de transmisión de tokens | Lógica de obtención e inyección de tokens | Actualización automática, manejo seguro | Gestión de autenticación unificada |
| Administración de Configuración | Inyección de dependencias, prioridad de configuración | Constructores y métodos de configuración | Patrón Options, adaptación a entornos | Configuración flexible |
| Manejo de Errores | Códigos de estado HTTP, gestión de excepciones | Flujo completo de manejo de errores | Registro estructurado, métodos parciales | Recuperación robusta ante errores |
Detalles de la Implementación del Generador
1. Análisis de Interfaces y Árbol de Sintaxis
| Tipo de Análisis | Contenido Procesado | Implementación Técnica | Resultado de la Generación |
|---|---|---|---|
| Atributos de Interfaz | Parámetros del atributo [HttpApiClient] |
Recorrido del árbol de sintaxis de Roslyn | Constructor, métodos de configuración |
| Atributos de Método | Atributos HTTP como [HttpGet], [HttpPost] |
Análisis del modelo semántico | Código de implementación de método HTTP |
| Atributos de Parámetro | Atributos de parámetro como [Path], [Body], [Query] |
Inferencia de tipos de parámetro | Lógica de procesamiento de parámetros |
| Relaciones de Herencia | Relaciones InheritsFrom |
Grafo de herencia de interfaz | Estructura de jerarquía de herencia |
2. Motor de Plantillas de Código
| Tipo de Plantilla | Contenido de la Plantilla | Sustitución de Variables | Resultado |
|---|---|---|---|
| Plantilla de Clase | Definición de clase, constructor | Nombre de interfaz, nombre de clase base | Estructura completa de la clase |
| Plantilla de Método | Implementación de método HTTP | Método HTTP, patrón de URL | Implementación específica del método |
| Plantilla de Configuración | Lógica de obtención de configuración | Elemento de configuración, prioridad | Métodos de gestión de configuración |
| Plantilla de Manejo de Errores | Lógica de manejo de excepciones | Tipo de error, nivel de registro | Código de manejo de errores |
3. Análisis Automático de Inyección de Dependencias (DI)
| Tipo de Dependencia | Mecanismo de Identificación | Lógica de Inyección | Código Generado |
|---|---|---|---|
| HttpClient | Requerido por todas las interfaces API | Primer parámetro del constructor | HttpClient _clienteHttp |
| ILogger | Necesario para cada clase generada | Segundo parámetro del constructor | ILogger<T> _registrador |
| JsonSerializerOptions | Configuración de serialización JSON | Inyección mediante patrón Options | IOptions<JsonSerializerOptions> |
| Opciones Específicas | Clase de configuración definida por la interfaz | Análisis de atributos de configuración | IOptions<TOpciones> |
| Gestor de Tokens | Atributo TokenManager |
Análisis de parámetros de atributo | ITokenManagementService |
4. Estrategias de Generación de Métodos HTTP
| Método HTTP | Lógica de Generación | Manejo del Contenido | Manejo de la Respuesta |
|---|---|---|---|
| GET | Construcción de URL con parámetros de consulta | Sin cuerpo de solicitud | Deserialización de flujo |
| POST | Construcción de contenido de formulario/JSON | Serialización del cuerpo | Deserialización de flujo |
| PUT | Procesamiento de solicitud de actualización | Serialización del cuerpo | Deserialización de flujo |
| DELETE | Procesamiento de solicitud de eliminación | Cuerpo opcional | Deserialización de flujo |
| PATCH | Manejo de actualización parcial | Serialización del cuerpo | Deserialización de flujo |
Flujo de Trabajo del Generador
graph TD
A[Activación en Compilación] --> B[Escaneo de Definiciones de Interfaz]
B --> C[Análisis del Árbol de Sintaxis]
C --> D[Análisis de Atributos]
D --> E[Análisis de Dependencias]
E --> F[Selección de Plantilla de Código]
F --> G[Sustitución de Variables]
G --> H[Generación de Código]
H --> I[Validación de Sintaxis]
I --> J[Adición a la Compilación]
Definición de Objetos Generados
El desglose técnico de los módulos centrales del generador de código en tiempo de compilación de .NET abarca los siguientes componentes clave:
| Módulo Técnico | Funcionalidad Principal | Escenarios de Aplicación Típicos | Métrica Técnica |
|---|---|---|---|
| Diseño de Arquitectura de Clases Abstractas y Herencia | Generación de clases base abstractas, herencia de interfaz, herencia multinivel | Construcción de arquitectura base, patrones de reutilización de código | Complejidad de la arquitectura reducida 30-50% |
| Sistema de Gestión de Tokens y Encabezados | Gestor de tokens, múltiples métodos de entrega, mapeo de alias | Mecanismos de autenticación e control de seguridad | Código de autenticación reducido en más del 80% |
| Inyección de Configuración y Gestión de Dependencias | Inyección automática de dependencias, prioridad de configuración, gestión de opciones | Gestión de configuración, mecanismos de adaptación a entornos | Tasa de error de configuración reducida en más del 70% |
| Automatización del Registro de Servicios | Registro por grupo, registro predeterminado, generación de métodos de extensión | Arquitectura de microservicios, desarrollo modular | Código de registro reducido en más del 90% |
| Procesamiento Avanzado de Parámetros | Parámetros de ruta, parámetros de consulta, parámetros de cuerpo, manejo de archivos | Llamadas API complejas, transferencia de datos | Complejidad del manejo de parámetros reducida en más del 60% |
Diseño de Arquitectura de Clases Abstractas y Herencia en Sistemas Empresariales
Principios Técnicos de la Arquitectura de Herencia
El diseño de clases abstractas y la arquitectura de herencia es una característica técnica fundamental del generador de código, que automatiza la creación de varios patrones de herencia mediante análisis sintáctico y un motor de plantillas:
| Tipo de Herencia | Atributo de Marcado | Tipo de Clase Generada | Escenario de Uso | Ejemplo de Código |
|---|---|---|---|---|
| Clase Base Abstracta | IsAbstract = true |
abstract partial class |
Definir comportamientos comunes, funcionalidades básicas | ClienteApiBaseConTokens |
| Implementación de Herencia | InheritsFrom = "ClaseBase" |
partial class : ClaseBase |
Extender funcionalidades, implementación concreta | ClienteApiConTokens : ClienteApiBaseConTokens |
| Implementación Independiente | Sin marcado especial | partial class |
Funcionalidad independiente, sin herencia | SimpleClienteApi |
| Herencia Multinivel | Uso combinado | Estructura de herencia multinivel | Arquitecturas complejas, diseño por capas | ApiEspecifica : ApiBase : IApiBase |
Basándonos en un ejemplo de proyecto real, podemos generar una clase base abstracta utilizando el parámetro IsAbstract para definir comportamientos comunes del cliente API:
// Interfaz para el gestor de tokens básico
public interface ITokenManagementService
{
Task<string> RetrieveTokenAsync();
}
// Interfaces extendidas para gestores de tokens
public interface IUserTokenManagementService : ITokenManagementService { }
public interface ITenantTokenManagementService : ITokenManagementService { }
// Definición de interfaz de cliente API base abstracta
[HttpApiClient(TokenManagementService = nameof(ITokenManagementService), IsAbstract = true)]
public interface IClienteApiBaseConTokens
{
/// <summary>
/// Obtiene información de usuario desde la interfaz base.
/// </summary>
[HttpGet("api/usuarios/{idUsuario}")]
Task<UserProfile> GetUserProfileBaseAsync([Path] string idUsuario, CancellationToken tokenCancelacion = default);
/// <summary>
/// Crea un usuario - utiliza un encabezado personalizado para el token.
/// </summary>
[HttpPost("/api/v1/user")]
Task<UserCreationResponse> CreateUserAccountAsync(
[Token][Header("x-api-token")] string apiToken,
[Body] UserCreationRequest userData,
CancellationToken tokenCancelacion = default);
}
Análisis de la Definición de Interfaz
Descripción de Atributos de Marcado
| Atributo de Marcado | Parámetro | Descripción de Función | Comportamiento de Generación | Ejemplo de Uso |
|---|---|---|---|---|
[HttpApiClient] |
TokenManagementService |
Especifica el tipo de gestor de tokens | Inyecta automáticamente el gestor de tokens correspondiente | TokenManagementService = nameof(ITokenManagementService) |
IsAbstract |
Marca como clase base abstracta | Genera una clase abstracta en lugar de una implementación concreta | IsAbstract = true |
|
InheritsFrom |
Especifica la clase base de la que heredar | Genera una clase de implementación con relaciones de herencia | InheritsFrom = "ClaseBase" |
|
RegistryGroupName |
Especifica el nombre del grupo de registro | Genera métodos de registro por grupo | RegistryGroupName = "Pagos" |
|
| Atributos de Método HTTP | [HttpGet] |
Marca una solicitud GET | Genera código para solicitudes GET | [HttpGet("api/datos")] |
[HttpPost] |
Marca una solicitud POST | Genera código para solicitudes POST | [HttpPost("api/datos")] |
|
[HttpPut] |
Marca una solicitud PUT | Genera código para solicitudes PUT | [HttpPut("api/datos/{id}")] |
|
| Atributos de Parámetro | [Path] |
Parámetro de ruta | Reemplaza el marcador de posición de la URL | [Path] string idUsuario |
[Body] |
Parámetro del cuerpo de la solicitud | Serialización JSON | [Body] UserPayload data |
|
[Query] |
Parámetro de consulta | Cadena de consulta URL | [Query] string nombre |
|
[Header] |
Parámetro de encabezado de solicitud | Añade encabezado HTTP | [Header("Auth")] string token |
|
[Token] |
Parámetro de Token | Marca como token de autenticación | [Token][Header("X-Auth-Token")] string token |
|
| Atributos Especiales | [ArrayQuery] |
Parámetro de consulta de arreglo | Serialización de parámetros de arreglo | [ArrayQuery(",")] string[] etiquetas |
[FilePath] |
Parámetro de ruta de archivo | Soporte para descarga de archivos | [FilePath] string rutaGuardado |
Flujo de Lógica de Generación
graph TD
A[Análisis de Definición de Interfaz] --> B[Análisis de Atributos]
B --> C[Identificación de Tipo de Parámetro]
C --> D[Generación de Estructura de Clase]
D --> E[Generación de Constructor]
E --> F[Generación de Implementación de Método]
F --> G[Generación de Métodos Auxiliares]
G --> H[Salida del Código Generado]
Flujo de Trabajo del Generador:
- Construcción del Árbol de Sintaxis: Utiliza el analizador Roslyn para construir el Árbol de Sintaxis Abstracto de la definición de la interfaz.
- Análisis del Modelo Semántico: Realiza un análisis semántico de los atributos marcados y los tipos de parámetros.
- Coincidencia de Plantillas de Código: Selecciona la plantilla de código apropiada basada en los resultados del análisis.
- Análisis de Relaciones de Dependencia: Identifica las dependencias requeridas por la interfaz y determina el orden de inyección.
- Generación de Código Objetivo: Genera código C# conforme a las especificaciones utilizando el motor de plantillas.
Implementación de la Clase Base Abstracta Generada:
/// <summary>
/// <inheritdoc cref="IClienteApiBaseConTokens"/>
/// </summary>
internal abstract partial class ClienteApiBaseConTokens : IClienteApiBaseConTokens
{
protected readonly HttpClient _clienteHttp;
protected readonly ILogger _registrador;
protected readonly JsonSerializerOptions _opcionesJsonSerializer;
protected readonly AppApiOptions _configuracionApi;
protected readonly ITokenManagementService _servicioGestionTokens;
public ClienteApiBaseConTokens(HttpClient httpClient, ILogger logger,
IOptions<JsonSerializerOptions> opcionesSerializador,
IOptions<AppApiOptions> configuracionApi,
ITokenManagementService servicioGestionTokens)
{
_clienteHttp = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_registrador = logger ?? throw new ArgumentNullException(nameof(logger));
_opcionesJsonSerializer = opcionesSerializador.Value;
_configuracionApi = configuracionApi?.Value ?? throw new ArgumentNullException(nameof(configuracionApi));
_servicioGestionTokens = servicioGestionTokens ?? throw new ArgumentNullException(nameof(servicioGestionTokens));
}
// Implementación de métodos concretos generados...
public async Task<UserProfile> GetUserProfileBaseAsync(string idUsuario, CancellationToken tokenCancelacion)
{
var rutaUrl = $"api/usuarios/{idUsuario}";
using var solicitud = new HttpRequestMessage(HttpMethod.Get, rutaUrl);
// Lógica de solicitud HTTP...
return await SendRequestAndParseResponse<UserProfile>(solicitud, rutaUrl, tokenCancelacion);
}
protected async Task<T> SendRequestAndParseResponse<T>(HttpRequestMessage request, string url, CancellationToken cancellationToken)
{
// ... lógica interna para enviar solicitud y deserializar respuesta
// (Se omitirá el detalle completo por concisión, pero incluye manejo de errores, logs, etc.)
using var response = await _clienteHttp.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode(); // Una simplificación para el ejemplo
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
return await JsonSerializer.DeserializeAsync<T>(stream, _opcionesJsonSerializer, cancellationToken);
}
}
Análisis Técnico del Código Generado
Mecanismo de Generación de Estructura de Clase
classDiagram
class ClienteApiBaseConTokens {
-HttpClient _clienteHttp
-ILogger _registrador
-JsonSerializerOptions _opcionesJsonSerializer
-AppApiOptions _configuracionApi
-ITokenManagementService _servicioGestionTokens
+ClienteApiBaseConTokens(httpClient, logger, opcionesSerializador, configuracionApi, servicioGestionTokens)
+GetUserProfileBaseAsync(idUsuario, tokenCancelacion) Task~UserProfile~
+CreateUserAccountAsync(apiToken, userData, tokenCancelacion) Task~UserCreationResponse~
#GetMediaType(contentType) string
#OnGetUserProfileBaseBefore(request, url) void
#OnGetUserProfileBaseAfter(response, url) void
#OnGetUserProfileBaseFail(response, url) void
#OnGetUserProfileBaseError(error, url) void
}
class IClienteApiBaseConTokens {
<<interface>>
+GetUserProfileBaseAsync(idUsuario, tokenCancelacion) Task~UserProfile~
+CreateUserAccountAsync(apiToken, userData, tokenCancelacion) Task~UserCreationResponse~
}
ClienteApiBaseConTokens --|> IClienteApiBaseConTokens : implementa
Resolución Automática de Inyección de Dependencias
Proceso de Identificación de Dependencias:
- Identificación de Gestor de Tokens: El atributo
TokenManagementServiceidentificaITokenManagementService. - Identificación de Opciones de Configuración: Se identifican
AppApiOptionsyJsonSerializerOptionsa partir de los archivos de configuración del proyecto. - Identificación del Componente de Registro: Cada clase generada inyecta automáticamente el
ILogger<T>correspondiente. - Identificación de HttpClient: Todos los clientes API requieren un
HttpClient.
Reglas de Generación del Constructor:
// Regla de generación: todas las dependencias requeridas se ordenan de la siguiente manera:
public ClassName(
HttpClient httpClient, // 1. HttpClient siempre es el primer parámetro
ILogger<ClassName> logger, // 2. El registrador siempre es el segundo parámetro
IOptions<JsonSerializerOptions> jsonSerializerOptions, // 3. Opciones de serialización JSON
IOptions<SpecificOptions> specificOptions, // 4. Opciones de configuración específicas
ITokenManagementService tokenManager) // 5. Gestor de Tokens (si se especifica)
Lógica de Generación de Campos
Análisis de Campos Generados Automáticamente:
| Nombre del Campo | Tipo | Origen | Propósito |
|---|---|---|---|
_clienteHttp |
HttpClient |
Inyección automática | Componente central para ejecutar solicitudes HTTP |
_registrador |
ILogger<ClassName> |
Inyección automática | Registra información de log y depuración |
_opcionesJsonSerializer |
JsonSerializerOptions |
IOptions<JsonSerializerOptions> |
Configuración de serialización JSON |
_configuracionApi |
AppApiOptions |
IOptions<AppApiOptions> |
Opciones de configuración específicas |
_servicioGestionTokens |
ITokenManagementService |
Atributo TokenManagementService |
Obtención y gestión de tokens |
Detalle de la Generación del Constructor
Patrón de Validación de Parámetros:
// Se genera una comprobación de nulidad para cada parámetro
_clienteHttp = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_registrador = logger ?? throw new ArgumentNullException(nameof(logger));
_opcionesJsonSerializer = opcionesSerializador.Value; // Options no necesita comprobación de nulidad, DI lo garantiza
_configuracionApi = configuracionApi?.Value ?? throw new ArgumentNullException(nameof(configuracionApi));
_servicioGestionTokens = servicioGestionTokens ?? throw new ArgumentNullException(nameof(servicioGestionTokens));
Juicio Inteligente del Generador:
- Parámetros
IOptions<T>: Automáticamente toman.Value, no requieren comprobación de nulidad (garantizado por DI). - Parámetros de Dependencia Directa: Necesitan comprobación de nulidad, como
HttpClient,ILogger. - Dependencias Opcionales: Usan el operador
?.y manejan valores predeterminados.
Detalle de la Generación de Métodos
Lógica Completa de Generación del Método GetUserProfileBaseAsync:
public async Task<UserProfile> GetUserProfileBaseAsync(string idUsuario, CancellationToken tokenCancelacion)
{
// Paso 1: Construcción de URL - sustitución de parámetros de ruta
var urlBase = $"api/usuarios/{idUsuario}";
// Paso 2: Verificación de BaseAddress
if (_clienteHttp.BaseAddress == null)
{
throw new InvalidOperationException("La configuración de BaseAddress es obligatoria. Las URL relativas requieren un BaseAddress válido " +
"establecido en el atributo HttpApiClient o en AppApiOptions.BaseUrl.");
}
// Paso 3: Registro de la solicitud previa
if (_configuracionApi.EnableLogging)
{
_registrador.LogDebug("Iniciando solicitud HTTP GET: {Url}", urlBase);
}
// Paso 4: Creación de la solicitud HTTP
using var solicitud = new HttpRequestMessage(HttpMethod.Get, urlBase);
// Paso 5: Llamada a Métodos Parciales - procesamiento previo a la solicitud
OnRequestBaseBefore(solicitud, urlBase);
OnGetUserProfileBaseBefore(solicitud, urlBase);
// Paso 6: Ejecución de la solicitud HTTP
try
{
using var respuesta = await _clienteHttp.SendAsync(solicitud, tokenCancelacion);
// Paso 7: Registro posterior a la respuesta
if (_configuracionApi.EnableLogging)
{
_registrador.LogDebug("Solicitud HTTP completada: {StatusCode}", (int)respuesta.StatusCode);
}
// Paso 8: Comprobación del estado de la respuesta
if (!respuesta.IsSuccessStatusCode)
{
// Paso 8.1: Llamada a los hooks de manejo de fallos
OnRequestBaseFail(respuesta, urlBase);
OnGetUserProfileBaseFail(respuesta, urlBase);
// Paso 8.2: Lectura del contenido de error
var contenidoError = await respuesta.Content.ReadAsStringAsync(tokenCancelacion);
// Paso 8.3: Registro de errores
if (_configuracionApi.EnableLogging)
{
_registrador.LogError("Solicitud HTTP fallida: {StatusCode}, Respuesta: {Response}",
(int)respuesta.StatusCode, contenidoError);
}
// Paso 8.4: Lanzar excepción estandarizada
throw new HttpRequestException($"La solicitud HTTP falló con el código: {(int)respuesta.StatusCode}");
}
// Paso 9: Procesamiento exitoso
OnRequestBaseAfter(respuesta, urlBase);
OnGetUserProfileBaseAfter(respuesta, urlBase);
// Paso 10: Deserialización de la respuesta
using var flujoRespuesta = await respuesta.Content.ReadAsStreamAsync(tokenCancelacion);
if (flujoRespuesta.Length == 0)
{
return default; // Manejo de respuesta vacía
}
var resultado = await JsonSerializer.DeserializeAsync<UserProfile>(flujoRespuesta, _opcionesJsonSerializer, tokenCancelacion);
return resultado;
}
catch (System.Exception ex)
{
// Paso 11: Manejo de excepciones
if (_configuracionApi.EnableLogging)
{
_registrador.LogError(ex, "Excepción en solicitud HTTP: {Url}", urlBase);
}
OnRequestBaseError(ex, urlBase);
OnGetUserProfileBaseError(ex, urlBase);
throw;
}
}
Diagrama de Flujo de Lógica de Generación
flowchart TD
A[Inicio del Método] --> B[Construcción de URL]
B --> C[Validación de BaseAddress]
C --> D[Registro Pre-Solicitud]
D --> E[Creación de HttpRequestMessage]
E --> F[Invocación de Métodos Parciales Before]
F --> G[Ejecución de Solicitud HTTP]
G --> H{Solicitud Exitosa?}
H -->|Sí| I[Registro de Éxito]
H -->|No| J[Invocación de Métodos Parciales Fail]
I --> K[Invocación de Métodos Parciales After]
J --> L[Lectura de Contenido de Error]
L --> M[Registro de Errores]
M --> N[Lanzamiento de HttpRequestException]
K --> O[Lectura de Flujo de Respuesta]
O --> P{Flujo Vacío?}
P -->|Sí| Q[Retorno Default]
P -->|No| R[Deserialización JSON]
R --> S[Retorno de Resultado]
G --> T[Captura de Excepción]
T --> U[Registro de Excepción]
U --> V[Invocación de Métodos Parciales Error]
V --> W[Relanzamiento de Excepción]
Generación de Métodos Auxiliares
Métodos Auxiliares Generados Automáticamente:
/// <summary>
/// Extrae la parte del tipo de medio de una cadena Content-Type, eliminando la información del conjunto de caracteres.
/// </summary>
/// <param name="contentType">La cadena Content-Type completa.</param>
/// <returns>La parte del tipo de medio.</returns>
protected string ExtractMediaType(string contentType)
{
if (string.IsNullOrEmpty(contentType))
return "application/json";
// Content-Type puede incluir información del conjunto de caracteres, como "application/json; charset=utf-8"
// Se necesita la parte del tipo de medio antes del punto y coma.
var indicePuntoYComa = contentType.IndexOf(';');
if (indicePuntoYComa >= 0)
{
return contentType.Substring(0, indicePuntoYComa).Trim();
}
return contentType.Trim();
}
Reglas de Generación de Métodos Parciales:
| Tipo de Método | Regla de Nomenclatura | Momento de Activación |
|---|---|---|
| Nivel de clase antes de solicitud | On{ClassName}RequestBefore |
Antes de cada solicitud de método |
| Nivel de clase después de solicitud | On{ClassName}RequestAfter |
Después de cada solicitud de método exitosa |
| Nivel de clase fallo de solicitud | On{ClassName}RequestFail |
Cuando un método falla |
| Nivel de clase excepción de solicitud | On{ClassName}RequestError |
Cuando un método lanza una excepción |
| Nivel de método antes de solicitud | On{MethodName}Before |
Antes de la solicitud de un método específico |
| Nivel de método después de solicitud | On{MethodName}After |
Después del éxito de un método específico |
| Nivel de método fallo de solicitud | On{MethodName}Fail |
Cuando un método específico falla |
| Nivel de método excepción de solicitud | On{MethodName}Error |
Cuando un método específico lanza una excepción |
Estrategias de Manejo de Errores
Lógica de Generación de Manejo de Errores:
- Comprobación de Código de Estado: Verifica
IsSuccessStatusCode. - Lectura del Contenido de Error: Lee asíncronamente el contenido de la respuesta.
- Registro de Errores: Registra información detallada del error.
- Invocación de Método Parcial: Llama a los hooks de manejo de fallos.
- Excepción Estandarizada: Lanza un
HttpRequestExceptioncon el código de estado.
Patrón de Manejo de Excepciones:
catch (System.Exception ex)
{
// Patrón unificado de manejo de excepciones
if (_configuracionApi.EnableLogging)
{
_registrador.LogError(ex, "Excepción en solicitud HTTP: {Url}", url);
}
// Llama a los hooks de manejo de excepciones
OnClassRequestError(ex, url);
OnMethodError(ex, url);
// Relanza la excepción, manteniendo el stack original
throw;
}
Características de Optimización de Rendimiento
Optimización de Rendimiento en el Código Generado:
- Sentencias
using: Aseguran queHttpRequestMessageyHttpResponseMessagese liberen correctamente. - Procesamiento en Flujo: Deserializa directamente desde el flujo de respuesta, evitando copias de memoria innecesarias.
- Comprobación de Flujo Vacío: Verifica la longitud del flujo de respuesta para evitar la deserialización de contenido vacío.
- Reutilización de Configuración: Reutiliza la instancia de
JsonSerializerOptions. - Registro Condicional: Decide si registrar o no basándose en la configuración, evitando la formatación de cadenas innecesaria.
Estas optimizaciones garantizan que el código generado sea funcionalmente completo y de alto rendimiento, adecuado para entornos de producción.
Cómo Implementar Relaciones de Herencia de Interfaz
Mediante el parámetro InheritsFrom, podemos hacer que una interfaz API concreta herede la funcionalidad de una clase base abstracta:
/// <summary>
/// Interfaz API para probar la funcionalidad de tokens - gestión de tokens a nivel de usuario.
/// </summary>
[HttpApiClient(TokenManagementService = nameof(IUserTokenManagementService), InheritsFrom = "ClienteApiBaseConTokens")]
[Header("Autorización", AliasAs = "X-Token")]
[Header("cliente-id", "app-id-123")]
[Header("version-api", "v1.0")]
public interface IClienteApiConTokens : IClienteApiBaseConTokens
{
/// <summary>
/// Obtiene información de usuario.
/// </summary>
[HttpGet("api/usuarios/{idUsuario}")]
Task<UserProfile> GetUserAsync([Path] string idUsuario, CancellationToken tokenCancelacion = default);
/// <summary>
/// Obtiene una lista de usuarios.
/// </summary>
[HttpGet("api/usuarios")]
Task<List<UserProfile>> GetUsersListAsync(CancellationToken tokenCancelacion = default);
}
Implementación de Herencia Generada:
/// <summary>
/// <inheritdoc cref="IClienteApiConTokens"/>
/// </summary>
internal partial class ClienteApiConTokens : ClienteApiBaseConTokens, IClienteApiConTokens
{
public ClienteApiConTokens(HttpClient httpClient, ILogger<ClienteApiConTokens> logger,
IOptions<JsonSerializerOptions> opcionesSerializador,
IOptions<AppApiOptions> configuracionApi,
ITenantTokenManagementService servicioGestionTokens)
: base(httpClient, logger, opcionesSerializador, configuracionApi, servicioGestionTokens)
{
// Configura BaseAddress y el tiempo de espera
var direccionBaseFinal = GetFinalBaseAddress();
if (!string.IsNullOrEmpty(direccionBaseFinal))
{
_clienteHttp.BaseAddress = new Uri(direccionBaseFinal);
}
}
public async Task<UserProfile> GetUserAsync(string idUsuario, CancellationToken tokenCancelacion)
{
// Obtiene automáticamente el token del gestor de tokens
var tokenAcceso = await _servicioGestionTokens.RetrieveTokenAsync();
if (string.IsNullOrEmpty(tokenAcceso))
{
throw new InvalidOperationException("No se pudo obtener el token de acceso.");
}
var rutaUrl = $"api/usuarios/{idUsuario}";
using var solicitud = new HttpRequestMessage(HttpMethod.Get, rutaUrl);
// Añade automáticamente el encabezado de Autorización
solicitud.Headers.Add("Autorización", tokenAcceso);
// Añade encabezados fijos definidos por la interfaz
solicitud.Headers.Add("cliente-id", "app-id-123");
solicitud.Headers.Add("version-api", "v1.0");
// Lógica de solicitud HTTP...
return await SendRequestAndParseResponse<UserProfile>(solicitud, rutaUrl, tokenCancelacion);
}
}
Cómo Implementar una Arquitectura de Herencia Multinivel
En proyectos reales, podemos construir estructuras de herencia más complejas:
// Primera capa: Cliente API base (abstracto)
[HttpApiClient(TokenManagementService = nameof(ITokenManagementService), IsAbstract = true)]
public interface IBaseServiceClient
{
[HttpGet("salud")]
Task<bool> CheckServiceHealthAsync();
}
// Segunda capa: Clase base de módulo de negocio (abstracta)
[HttpApiClient(TokenManagementService = nameof(IUserTokenManagementService), IsAbstract = true)]
public interface IPaymentBaseClient : IBaseServiceClient
{
[HttpGet("pagos/cuentas")]
Task<List<AccountInfo>> GetPaymentAccountsAsync();
}
// Tercera capa: Implementación API concreta
[HttpApiClient(InheritsFrom = "PaymentBaseClient")]
[Header("X-Modulo", "Pagos")]
public interface IPaymentApi : IPaymentBaseClient
{
[HttpPost("pagos/transferir")]
Task<TransferResult> InitiateTransferAsync([Body] TransferRequest transferDetails);
}
Sistema de Gestión de Tokens y Encabezados
Diseño de Arquitectura del Gestor de Tokens
El generador de código emplea una arquitectura de gestor de tokens por capas que soporta múltiples modos de autenticación y mecanismos de entrega:
// Gestor de tokens básico
public interface ITokenManagementService
{
/// <summary>
/// Obtiene el token de acceso.
/// </summary>
/// <returns>La cadena del token de acceso.</returns>
Task<string> RetrieveTokenAsync();
}
// Gestor de tokens a nivel de usuario
public interface IUserTokenManagementService : ITokenManagementService
{
// Hereda la funcionalidad base, puede añadir métodos específicos de usuario
}
// Gestor de tokens a nivel de inquilino
public interface ITenantTokenManagementService : ITokenManagementService
{
// Hereda la funcionalidad base, puede añadir métodos específicos de inquilino
}
// Ejemplo de implementación de gestor de tokens
public class InMemoryTokenManager : ITokenManagementService
{
public Task<string> RetrieveTokenAsync()
{
return Task.FromResult("Bearer test-access-token-123");
}
}
Diversos Métodos de Entrega de Tokens
| Método de Entrega | Atributo de Marcado | Escenario Aplicable | Seguridad | Lógica de Generación | Ventaja |
|---|---|---|---|---|---|
| Entrega por Encabezado | [Header("Autorización", AliasAs = "X-Auth-Token")] |
Llamadas API estándar, servicios RESTful | Alta | Añadir al Encabezado HTTP | Conforme al estándar HTTP, amigable para el servidor |
| Entrega por Parámetro de Consulta | [Query("Autorización", AliasAs = "api_key")] |
APIs simples, pruebas de depuración | Media | Añadir a la cadena de consulta de la URL | Fácil depuración, URL visible |
| Token a Nivel de Método | [Token][Header("custom-token")] string token |
Tokens temporales, multi-inquilino | Alta | Uso de parámetro de método | Control flexible, entrega precisa |
| Token en el Cuerpo | Incluido en el JSON del cuerpo de la solicitud | Protocolos de autenticación complejos | Media | Serialización JSON incluida | Soporte para protocolos complejos |
Entrega por Encabezado (Soporte para Mapeo de Alias)
// Uso de encabezado para entregar el Token, con soporte para mapeo de alias
[HttpApiClient(TokenManagementService = nameof(ITokenManagementService))]
[Header("Autorización", AliasAs = "X-Api-Auth-Token")]
public interface IClientWithHeaderToken
{
[HttpGet("api/recursos")]
Task<ResourceData> GetResourceDataAsync();
}
Análisis de Atributos de Interfaz:
| Atributo | Parámetros | Función | Comportamiento de Generación |
|---|---|---|---|
HttpApiClient |
TokenManagementService = nameof(ITokenManagementService) |
Especifica el gestor de tokens | Inyección de ITokenManagementService en el constructor |
Header |
"Autorización", AliasAs = "X-Api-Auth-Token" |
Establece un alias para el encabezado | El código generado utiliza el encabezado X-Api-Auth-Token |
Detalle de la Lógica de Generación para el Manejo de Tokens:
public async Task<ResourceData> GetResourceDataAsync()
{
// Paso 1: Obtener el token de acceso
var tokenAcceso = await _servicioGestionTokens.RetrieveTokenAsync();
if (string.IsNullOrEmpty(tokenAcceso))
{
throw new InvalidOperationException("No se pudo obtener el token de acceso.");
}
// Paso 2: Construir la solicitud
var rutaUrl = "api/recursos";
using var solicitud = new HttpRequestMessage(HttpMethod.Get, rutaUrl);
// Paso 3: Añadir el encabezado de Token (usando el alias mapeado)
// Nota: El nombre interno del encabezado es "Autorización", pero el nombre de encabezado real es "X-Api-Auth-Token"
solicitud.Headers.Add("X-Api-Auth-Token", tokenAcceso);
// Paso 4: Ejecutar la solicitud...
return await SendRequestAndParseResponse<ResourceData>(solicitud, rutaUrl, default);
}
Diagrama de Flujo del Procesamiento de Tokens del Generador:
flowchart TD
A[Inicio del Método] --> B[Comprobación de Atributo TokenManagementService]
B --> C{Existe TokenManagementService?}
C -->|Sí| D[Inyección de ITokenManagementService]
C -->|No| E[Saltar Procesamiento de Token]
D --> F[Obtener Token: await _servicioGestionTokens.RetrieveTokenAsync]
F --> G{Token Válido?}
G -->|Sí| H[Añadir a Encabezado Específico]
G -->|No| I[Lanzar InvalidOperationException]
H --> J[Continuar Solicitud HTTP]
I --> K[Fin del Método]
E --> J
J --> L[Fin del Método]
Mecanismo de Implementación de Mapeo de Alias:
- Definición de Interfaz:
[Header("Autorización", AliasAs = "X-Api-Auth-Token")] - Código Generado:
solicitud.Headers.Add("X-Api-Auth-Token", tokenAcceso) - Efecto Real: Se añade
X-Api-Auth-Token: {token}a los encabezados de la solicitud.
El generador maneja automáticamente las relaciones de mapeo de nombres de encabezados para asegurar que el token se transmita en el encabezado correcto.
Entrega por Parámetro de Consulta
// Usando parámetro de consulta para entregar el Token
[HttpApiClient(TokenManagementService = nameof(ITokenManagementService))]
[Query("Autorización", AliasAs = "token_de_acceso")]
public interface ITestQueryTokenClient
{
/// <summary>
/// Obtiene información de usuario (usando parámetro de consulta para el Token).
/// </summary>
[HttpGet("api/users/{idUsuario}")]
Task<UserProfile> GetUserDetailsAsync([Path] string idUsuario, CancellationToken tokenCancelacion = default);
}
Análisis de Atributos de Consulta:
| Atributo | Parámetros | Función | Comportamiento de Generación |
|---|---|---|---|
Query |
"Autorización", AliasAs = "token_de_acceso" |
Establece un alias para el parámetro de consulta | Añade ?token_de_acceso={token} a la URL |
Detalle de la Lógica de Generación para Token de Consulta:
public async Task<UserProfile> GetUserDetailsAsync(string idUsuario, CancellationToken tokenCancelacion)
{
// Paso 1: Obtener el token de acceso
var tokenAcceso = await _servicioGestionTokens.RetrieveTokenAsync();
if (string.IsNullOrEmpty(tokenAcceso))
{
throw new InvalidOperationException("No se pudo obtener el token de acceso.");
}
// Paso 2: Construcción de URL - ruta compuesta y parámetros de consulta
var constructorUrl = new StringBuilder();
// Paso 2.1: Construir la URL base (sustitución de parámetros de ruta)
constructorUrl.Append("api/users/").Append(HttpUtility.UrlEncode(idUsuario));
// Paso 2.2: Añadir el parámetro de consulta del Token (usando alias)
constructorUrl.Append("?token_de_acceso=").Append(HttpUtility.UrlEncode(tokenAcceso));
var urlCompleta = constructorUrl.ToString();
using var solicitud = new HttpRequestMessage(HttpMethod.Get, urlCompleta);
// Paso 3: Continuar el procesamiento de la solicitud HTTP...
return await SendRequestAndParseResponse<UserProfile>(solicitud, urlCompleta, tokenCancelacion);
}
Diagrama de Flujo del Procesamiento de Token de Consulta:
flowchart TD
A[Inicio del Método] --> B[Obtener Token de Acceso]
B --> C[Validación de Token]
C --> D{Token Válido?}
D -->|Sí| E[Inicializar Constructor de URL]
D -->|No| F[Lanzar Excepción]
E --> G[Procesar Parámetros de Ruta]
G --> H[Codificación URL de Parámetros de Ruta]
H --> I[Añadir Separador de Parámetros de Consulta?]
I --> J[Añadir Parámetro de Consulta de Token]
J --> K[Codificación URL de Token]
K --> L[Construir URL Completa]
L --> M[Crear HttpRequestMessage]
M --> N[Ejecutar Solicitud HTTP]
F --> O[Fin del Método]
N --> O
Reglas de Construcción de URL Complejas:
Cuando una interfaz incluye parámetros de ruta, parámetros de consulta y un token simultáneamente, el generador construye la URL según la siguiente prioridad:
// Interfaz de ejemplo compleja
[HttpGet("api/usuarios/{idUsuario}/publicaciones/{idPublicacion}")]
[Query("Autorización", AliasAs = "token_sesion")]
public interface IComplexQueryClient
{
Task<CustomData> GetDataWithComplexQueryAsync(
[Path] string idUsuario,
[Path] string idPublicacion,
[Query] string? categoria,
[Query] int pagina = 1);
}
Lógica de Construcción de URL Generada:
public async Task<CustomData> GetDataWithComplexQueryAsync(string idUsuario, string idPublicacion, string? categoria, int pagina = 1)
{
// 1. Obtener Token
var tokenAcceso = await _servicioGestionTokens.RetrieveTokenAsync();
// 2. Inicializar constructor de URL
var constructorUrl = new StringBuilder();
constructorUrl.Append("api/usuarios/").Append(HttpUtility.UrlEncode(idUsuario));
constructorUrl.Append("/publicaciones/").Append(HttpUtility.UrlEncode(idPublicacion));
// 3. Añadir parámetros de consulta
var tieneConsulta = false;
// 3.1: Añadir parámetro de consulta del Token (máxima prioridad)
constructorUrl.Append("?token_sesion=").Append(HttpUtility.UrlEncode(tokenAcceso));
tieneConsulta = true;
// 3.2: Añadir parámetros de consulta de negocio
if (categoria != null)
{
constructorUrl.Append("&categoria=").Append(HttpUtility.UrlEncode(categoria));
}
if (pagina > 1) // Evitar añadir el valor predeterminado
{
constructorUrl.Append("&pagina=").Append(pagina);
}
// 4. Construir la URL final
var urlFinal = constructorUrl.ToString();
// 5. Crear solicitud HTTP...
return await SendRequestAndParseResponse<CustomData>(new HttpRequestMessage(HttpMethod.Get, urlFinal), urlFinal, default);
}
Procesamiento de Codificación de URL:
El generador maneja automáticamente la codificación URL de varios parámetros:
| Tipo de Parámetro | Método de Codificación | Ejemplo |
|---|---|---|
| Parámetros de ruta | HttpUtility.UrlEncode |
nombre usuario → nombre+usuario |
| Valores de parámetros de consulta | HttpUtility.UrlEncode |
hola mundo → hola+mundo |
| Cadenas de token | HttpUtility.UrlEncode |
Bearer abc → Bearer+abc |
| Caracteres especiales | Codificación URL completa | a/b?c=d → a%2Fb%3Fc%3D |
Mejores Prácticas en la Construcción de URL:
- Prioridad de Token: Los parámetros de consulta de token siempre se añaden primero (usando
?). - Comprobación de Valores Nulos: Evitar añadir parámetros de consulta nulos o con valores predeterminados.
- Codificación Segura: Todos los parámetros se procesan con codificación URL.
- Gestión de Separadores: Uso correcto de los separadores
?y&.
Las reglas de construcción de URL anteriores garantizan que el código generado cumple con el estándar RFC 3986, asegurando la compatibilidad y fiabilidad de las solicitudes HTTP.
Parámetros de Token a Nivel de Método
// Uso directo de parámetros de Token en la clase base
[HttpApiClient(TokenManagementService = nameof(ITokenManagementService), IsAbstract = true)]
public interface IClienteApiBaseConTokens
{
[HttpPost("/api/v1/user")]
Task<UserCreationResponse> CreateUserAccountAsync(
[Token][Header("x-api-token")] string tokenEspecifico, // Parámetro de Token a nivel de método
[Body] UserCreationRequest newUserData, // Parámetro de cuerpo
CancellationToken tokenCancelacion = default); // Token de cancelación
}
Análisis de Atributos de Token a Nivel de Método:
| Combinación de Atributos | Parámetros | Función | Comportamiento de Generación |
|---|---|---|---|
[Token][Header("x-api-token")] |
string tokenEspecifico |
Marca como parámetro de Token y especifica el Encabezado | El parámetro del método se pasa directamente, no se invoca al TokenManagementService. |
Diferencias entre Token de Interfaz y Token Automático:
| Tipo de Token | Método de Obtención | Escenario de Uso | Lógica de Generación |
|---|---|---|---|
| Token a Nivel de Interfaz | await _servicioGestionTokens.RetrieveTokenAsync() |
Gestión unificada de tokens | Obtención y adición automática |
| Token a Nivel de Método | Paso de parámetro del método | Entrega flexible de tokens | Uso directo del valor del parámetro |
Detalle de la Lógica de Generación para Token a Nivel de Método:
public async Task<UserCreationResponse> CreateUserAccountAsync(string tokenEspecifico, UserCreationRequest newUserData, CancellationToken tokenCancelacion)
{
// Paso 1: Construir URL (ruta absoluta no requiere verificación de BaseAddress)
var rutaUrl = "/api/v1/user";
using var solicitud = new HttpRequestMessage(HttpMethod.Post, rutaUrl);
// Paso 2: Procesar parámetro de Token a nivel de método
// Nota: Aquí no se llama a TokenManagementService, se usa directamente el parámetro del método
if (!string.IsNullOrEmpty(tokenEspecifico))
solicitud.Headers.Add("x-api-token", tokenEspecifico);
// Paso 3: Procesar parámetro de cuerpo
if (newUserData != null)
{
// Paso 3.1: Serialización JSON
var contenidoJson = JsonSerializer.Serialize(newUserData, _opcionesJsonSerializer);
// Paso 3.2: Creación de StringContent
solicitud.Content = new StringContent(
contenidoJson,
Encoding.UTF8,
ExtractMediaType("application/json")); // Asumiendo un tipo de contenido predeterminado
}
// Paso 4: Continuar el procesamiento de la solicitud HTTP...
return await SendRequestAndParseResponse<UserCreationResponse>(solicitud, rutaUrl, tokenCancelacion);
}
Diagrama de Flujo del Procesamiento de Token a Nivel de Método:
flowchart TD
A[Llamada a Método] --> B[Comprobar Parámetro Token]
B --> C{Token Vacío?}
C -->|No| D[Añadir a Encabezado Específico]
C -->|Sí| E[Saltar Adición de Encabezado]
D --> F[Procesar Otros Parámetros]
E --> F
F --> G[Crear Solicitud HTTP]
G --> H[Ejecutar Solicitud]
H --> I[Devolver Resultado]
Prioridad de Procesamiento de Parámetros Compuestos:
Cuando un método contiene varios tipos de parámetros, el generador los procesa en el siguiente orden de prioridad:
[HttpPost("/api/v1/user")]
Task<UserCreationResponse> CreateUserAccountWithComplexParamsAsync(
[Token][Header("x-api-token")] string auth_token, // Prioridad 1: Parámetro de Token
[Body] UserCreationRequest request_body, // Prioridad 2: Parámetro de Cuerpo
[Header("X-Client-ID")] string client_id, // Prioridad 3: Parámetro de Encabezado
[Query] int api_version = 1, // Prioridad 4: Parámetro de Consulta
CancellationToken cancellationToken = default); // Prioridad 5: Token de Cancelación
Orden de Procesamiento de Parámetros en el Código Generado:
public async Task<UserCreationResponse> CreateUserAccountWithComplexParamsAsync(
string auth_token, UserCreationRequest request_body, string client_id, int api_version, CancellationToken cancellationToken)
{
var rutaUrl = "/api/v1/user";
using var solicitud = new HttpRequestMessage(HttpMethod.Post, rutaUrl);
// Prioridad 1: Procesamiento de parámetro de Token
if (!string.IsNullOrEmpty(auth_token))
solicitud.Headers.Add("x-api-token", auth_token);
// Prioridad 3: Procesamiento de parámetro de Encabezado
if (!string.IsNullOrEmpty(client_id))
solicitud.Headers.Add("X-Client-ID", client_id);
// Prioridad 4: Procesamiento de parámetro de Consulta (si hay parámetros de ruta)
if (api_version > 1) // Evitar añadir el valor predeterminado
{
rutaUrl += "?api_version=" + HttpUtility.UrlEncode(api_version.ToString());
solicitud.RequestUri = new Uri(rutaUrl, UriKind.RelativeOrAbsolute);
}
// Prioridad 2: Procesamiento de parámetro de Cuerpo (después de construir la URL)
if (request_body != null)
{
var contenidoJson = JsonSerializer.Serialize(request_body, _opcionesJsonSerializer);
solicitud.Content = new StringContent(contenidoJson, Encoding.UTF8, "application/json");
}
// Continuar el procesamiento de la solicitud...
return await SendRequestAndParseResponse<UserCreationResponse>(solicitud, rutaUrl, cancellationToken);
}
Escenarios de Uso para Tokens a Nivel de Método:
- Entrega de Tokens Temporales: Cuando se necesita usar un token específico de una sola vez.
- Escenarios Multi-Inquilino: Diferentes inquilinos usan diferentes tokens.
- Entornos de Prueba: Especificar manualmente tokens de prueba.
- Actualización de Tokens: Pasar un token recién actualizado.
Consideraciones:
- El token a nivel de método no invoca
TokenManagementService.RetrieveTokenAsync(). - No se pueden usar tokens a nivel de método y de interfaz simultáneamente.
- El invocador es responsable de la validez y actualización del token a nivel de método.
Gestión Combinada de Múltiples Encabezados
// Soporte para múltiples encabezados fijos y gestión de tokens
[HttpApiClient(TokenManagementService = nameof(IUserTokenManagementService), InheritsFrom = "ClienteApiBaseConTokens")]
[Header("Autorización", AliasAs = "X-User-Token")] // Encabezado de Token dinámico
[Header("app-version", "1.0.0")] // Encabezado fijo
[Header("correlation-id", "UUID_GENERATED")] // Encabezado fijo
public interface IClientApiWithMultipleHeaders : IClienteApiBaseConTokens
{
[HttpGet("api/users")]
Task<List<UserProfile>> GetUsersListWithHeadersAsync(CancellationToken tokenCancelacion = default);
}
Procesamiento de Múltiples Encabezados en el Código Generado:
public async Task<List<UserProfile>> GetUsersListWithHeadersAsync(CancellationToken tokenCancelacion)
{
var tokenAcceso = await _servicioGestionTokens.RetrieveTokenAsync();
// ...
using var solicitud = new HttpRequestMessage(HttpMethod.Get, "api/users");
// Añadir automáticamente el encabezado de Token dinámico
solicitud.Headers.Add("X-User-Token", tokenAcceso);
// Añadir automáticamente los encabezados fijos
solicitud.Headers.Add("app-version", "1.0.0");
solicitud.Headers.Add("correlation-id", "UUID_GENERATED");
// ...
return await SendRequestAndParseResponse<List<UserProfile>>(solicitud, "api/users", tokenCancelacion);
}
Configuración de Clases de Inyección Personalizadas y Gestión de Dependencias
Inyección Automática de Dependencias
El generador de código inyecta automáticamente las dependencias requeridas para las clases generadas:
// Clase de opciones de configuración
public class AppApiOptions
{
public string BaseUrl { get; set; } = "";
public string TimeoutSeconds { get; set; } = "60";
public bool EnableLogging { get; set; } = true;
}
Constructor Generado Automáticamente:
internal abstract partial class ClienteApiBaseConTokens : IClienteApiBaseConTokens
{
protected readonly HttpClient _clienteHttp;
protected readonly ILogger _registrador;
protected readonly JsonSerializerOptions _opcionesJsonSerializer;
protected readonly AppApiOptions _configuracionApi;
protected readonly ITokenManagementService _servicioGestionTokens;
// Constructor generado automáticamente que incluye todas las dependencias
public ClienteApiBaseConTokens(HttpClient httpClient,
ILogger logger,
IOptions<JsonSerializerOptions> opcionesSerializador,
IOptions<AppApiOptions> configuracionApi,
ITokenManagementService servicioGestionTokens)
{
_clienteHttp = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_registrador = logger ?? throw new ArgumentNullException(nameof(logger));
_opcionesJsonSerializer = opcionesSerializador.Value;
_configuracionApi = configuracionApi?.Value ?? throw new ArgumentNullException(nameof(configuracionApi));
_servicioGestionTokens = servicioGestionTokens ?? throw new ArgumentNullException(nameof(servicioGestionTokens));
}
}
Gestión de Prioridades de Configuración
El código generado soporta múltiples niveles de prioridad de configuración, asegurando la flexibilidad y la capacidad de sobrescritura de la configuración:
| Elemento de Configuración | Prioridad (de mayor a menor) | Fuente de Configuración | Valor Predeterminado | Lógica de Validación |
|---|---|---|---|---|
| BaseAddress | 1. Atributo HttpApiClient 2. Opciones de Configuración 3. Valor Predeterminado | [HttpApiClient("https://api.com")] opciones.BaseUrl |
null |
Validación de formato URI |
| Timeout | 1. Atributo HttpApiClient 2. Opciones de Configuración 3. Valor Predeterminado | [HttpApiClient(Timeout=120)] opciones.TimeoutSeconds |
60 segundos |
Validación de rango numérico |
| ContentType | 1. Atributo Body de Método 2. Atributo HttpApiClient 3. Valor Predeterminado | [Body(ContentType="xml")] [HttpApiClient(ContentType="json")] |
application/json |
Validación de tipo MIME |
| Headers | 1. Encabezado a nivel de Método 2. Encabezado a nivel de Interfaz 3. Token Dinámico | [Header("X-API-Key")] Obtención del Gestor de Tokens |
Ninguno | Validación de nombre de encabezado |
/// <summary>
/// Obtiene el tiempo de espera final, priorizando la configuración del atributo HttpApiClient,
/// de lo contrario, usa AppApiOptions.TimeoutSeconds.
/// </summary>
private int GetEffectiveTimeout()
{
// Prioridad 1: Configuración de tiempo de espera en el atributo HttpApiClient (simulado por 100 aquí)
var atributoTimeout = 100; // Esto sería un valor real extraído del atributo en tiempo de generación
if (atributoTimeout > 0)
return atributoTimeout;
// Prioridad 2: Configuración de tiempo de espera en las opciones
var opcionesTimeout = _configuracionApi.TimeoutSeconds;
return !string.IsNullOrEmpty(opcionesTimeout) && int.TryParse(opcionesTimeout, out var parsedTimeout)
? parsedTimeout
: 60; // Prioridad 3: 60 segundos por defecto
}
/// <summary>
/// Obtiene el BaseAddress final, priorizando la configuración del atributo HttpApiClient,
/// de lo contrario, usa AppApiOptions.BaseUrl.
/// </summary>
private string? GetEffectiveBaseAddress()
{
// Prioridad 1: BaseAddress del atributo HttpApiClient (simulado por cadena vacía aquí)
var atributoAddress = ""; // Esto sería un valor real extraído del atributo en tiempo de generación
return !string.IsNullOrEmpty(atributoAddress)
? atributoAddress
: _configuracionApi.BaseUrl; // Prioridad 2: Opciones de configuración
}
Atributos de Configuración Avanzada
// Interfaz usando múltiples atributos de configuración
[HttpApiClient(
"https://api.gateway.com/pagos", // BaseAddress
Timeout = 120, // Tiempo de espera
ContentType = "application/json", // Content-Type predeterminado
RegistryGroupName = "GatewayPagos")] // Nombre del grupo de registro
[Header("X-Tenant-ID", "global-tenant")]
[Header("X-Request-Version", "1.1")]
public interface IGatewayPaymentApi
{
[HttpPost("transactions")]
Task<TransactionResult> ProcessPaymentAsync([Body] PaymentRequest request);
[HttpGet("transactions/{id}")]
Task<TransactionDetails> GetTransactionDetailsAsync(string id);
}
Generar Funciones de Registro de Servicios
Funcionalidad de Registro por Grupo
El generador de código crea automáticamente métodos de extensión para el registro por grupo, permitiendo una gestión modular de servicios:
| Tipo de Registro | Atributo de Marcado | Nombre del Método Generado | Alcance del Registro | Escenario Aplicable |
|---|---|---|---|---|
| Registro por Grupo | RegistryGroupName = "NombreGrupo" |
Add{NombreGrupo}ApiClientServices |
Interfaces del mismo grupo | Módulos de microservicios, agrupación de negocios |
| Registro Predeterminado | Sin RegistryGroupName |
AddDefaultApiClientServices |
Todas las interfaces sin agrupar | Aplicaciones simples, gestión unificada |
| Registro Mixto | Nombre de grupo + sin nombre de grupo | Genera múltiples métodos | Agrupado + No agrupado | Aplicaciones complejas, configuración flexible |
Ejemplo de Registro Agrupado
// Definir interfaces para diferentes grupos
[HttpApiClient("https://api.ms-teams.com", RegistryGroupName = "Teams")]
public interface ITeamsApi
{
[HttpGet("users/profile")]
Task<UserProfile> GetTeamsUserProfileAsync();
}
[HttpApiClient("https://api.slack.com", RegistryGroupName = "Slack")]
public interface ISlackApi
{
[HttpGet("users.info")]
Task<UserProfile> GetSlackUserProfileAsync();
}
[HttpApiClient("https://api.zoom.us", RegistryGroupName = "Zoom")]
public interface IZoomApi
{
[HttpGet("users/{id}")]
Task<UserProfile> GetZoomUserProfileAsync([Path] string id);
}
Análisis de Atributos de Agrupación de Interfaz:
| Parámetro de Atributo | Valor | Función | Comportamiento de Generación |
|---|---|---|---|
BaseAddress |
"https://api.ms-teams.com" |
Establece la dirección base de la API | Configuración de HttpClient.BaseAddress |
RegistryGroupName |
"Teams" |
Especifica el nombre del grupo de registro | Genera el método de registro correspondiente |
Lógica de Generación de Registro de Servicios:
graph TD
A[Escanear todas las Interfaces] --> B[Agrupar por RegistryGroupName]
B --> C{Tiene Nombre de Grupo?}
C -->|Sí| D[Generar Método de Registro de Grupo]
C -->|No| E[Unirse a Método de Registro Predeterminado]
D --> F["Add{GroupName}ApiClientServices"]
E --> G[AddDefaultApiClientServices]
F --> H[Cada grupo genera un método independiente]
G --> I[Todas las interfaces sin grupo se registran en un método]
Ventajas del Registro Agrupado:
- Gestión Modular: Registro agrupado de APIs de diferentes módulos de negocio.
- Carga Bajo Demanda: Registrar solo las APIs del grupo específico cuando sea necesario.
- Aislamiento de Configuración: Diferentes grupos pueden tener configuraciones independientes.
- Control de Dependencias: Evita la inyección de dependencias innecesarias.
Reglas de Nomenclatura de Métodos de Registro del Generador:
// Plantilla de Nomenclatura: Add{GroupName}ApiClientServices
AddTeamsApiClientServices(IServiceCollection) // GroupName = "Teams"
AddSlackApiClientServices(IServiceCollection) // GroupName = "Slack"
AddZoomApiClientServices(IServiceCollection) // GroupName = "Zoom"
AddPaymentApiClientServices(IServiceCollection) // GroupName = "Payment"
AddUserApiClientServices(IServiceCollection) // GroupName = "User"
Métodos de Extensión de Registro Generados Automáticamente:
/// <summary>
/// Registra todas las interfaces marcadas con [HttpApiClient] y RegistryGroupName = "Teams",
/// junto con su implementación HttpClient.
/// </summary>
public static IServiceCollection AddTeamsApiClientServices(this IServiceCollection services)
{
// Registra la implementación HttpClient de ITeamsUserService
services.AddHttpClient<ITeamsUserService, TeamsUserService>(client =>
{
client.BaseAddress = new Uri("https://api.ms-teams.com");
client.Timeout = TimeSpan.FromSeconds(60);
});
// Registra la implementación HttpClient de ITeamsChannelService
services.AddHttpClient<ITeamsChannelService, TeamsChannelService>(client =>
{
client.BaseAddress = new Uri("https://api.ms-teams.com");
client.Timeout = TimeSpan.FromSeconds(60);
});
return services;
}
/// <summary>
/// Registra todas las interfaces marcadas con [HttpApiClient] y RegistryGroupName = "Zoom",
/// junto con su implementación HttpClient.
/// </summary>
public static IServiceCollection AddZoomApiClientServices(this IServiceCollection services)
{
services.AddHttpClient<IZoomAuthService, ZoomAuthService>(client =>
{
client.BaseAddress = new Uri("https://api.zoom.us");
client.Timeout = TimeSpan.FromSeconds(60);
});
return services;
}
Detalle de la Generación del Método de Registro
Manejo de Prioridades de Configuración
El generador procesa la configuración al configurar HttpClient según la siguiente prioridad:
flowchart TD
A[Iniciar Configuración de HttpClient] --> B{Interfaz tiene BaseAddress?}
B -->|Sí| C[Usar BaseAddress de Interfaz]
B -->|No| D{Atributo tiene BaseAddress?}
D -->|Sí| E[Usar BaseAddress de Atributo]
D -->|No| F[Usar Vacío o Predeterminado]
C --> G{Interfaz tiene Timeout?}
E --> G
F --> G
G -->|Sí| H[Usar Timeout de Interfaz]
G -->|No| I{Atributo tiene Timeout?}
I -->|Sí| J[Usar Timeout de Atributo]
I -->|No| K[Usar 60 segundos por defecto]
H --> L[Configuración Completa]
J --> L
K --> L
Plantilla de Código de Configuración Generada
// Plantilla de configuración utilizada por el generador
services.AddHttpClient<IInterface, Implementation>(client =>
{
// Configuración de BaseAddress
var direccionBaseFinal = GetEffectiveBaseAddress();
if (!string.IsNullOrEmpty(direccionBaseFinal))
{
client.BaseAddress = new Uri(direccionBaseFinal);
}
// Configuración de Timeout
var tiempoEsperaFinal = GetEffectiveTimeout();
if (tiempoEsperaFinal > 0)
{
client.Timeout = TimeSpan.FromSeconds(tiempoEsperaFinal);
}
// Configuración de DefaultRequestHeaders (si existe)
// El generador añade automáticamente según los atributos de encabezado de la interfaz
});
Lógica de Obtención de Configuración
El generador crea métodos de obtención de configuración para cada clase de implementación:
/// <summary>
/// Obtiene el BaseAddress final. Prioridad: Atributo de Interfaz > Atributo HttpClientApi > Opciones de Configuración.
/// </summary>
private static string GetEffectiveBaseAddress()
{
// Prioridad 1: BaseAddress del atributo HttpClientApi (simulado para "https://api.ms-teams.com")
var atributoAddress = "https://api.ms-teams.com";
if (!string.IsNullOrEmpty(atributoAddress))
return atributoAddress;
// Prioridad 2: BaseAddress de las opciones de configuración
// var opcionesAddress = _configuration["ApiSettings:BaseAddress"];
// return !string.IsNullOrEmpty(opcionesAddress) ? opcionesAddress : null;
return null; // Valor predeterminado
}
/// <summary>
/// Obtiene el Timeout final. Prioridad: Atributo de Interfaz > Atributo HttpClientApi > Valor predeterminado.
/// </summary>
private static int GetEffectiveTimeout()
{
// Prioridad 1: Timeout del atributo HttpClientApi (simulado para 60)
var atributoTimeout = 60;
if (atributoTimeout > 0)
return atributoTimeout;
// Prioridad 2: Timeout de las opciones de configuración
// var opcionesTimeout = _configuration["ApiSettings:Timeout"];
// return int.TryParse(opcionesTimeout, out var timeout) ? timeout : 60;
return 60; // Valor predeterminado
}
Ejemplo de Registro de Múltiples Grupos
// Registrar servicios en Program.cs o Startup.cs según sea necesario
var builder = WebApplication.CreateBuilder(args);
// Opción 1: Registrar todas las APIs
builder.Services.AddDefaultApiClientServices();
// Opción 2: Registrar por grupo
builder.Services.AddTeamsApiClientServices(); // Solo registra las APIs de Teams
builder.Services.AddSlackApiClientServices(); // Solo registra las APIs de Slack
builder.Services.AddZoomApiClientServices(); // Solo registra las APIs de Zoom
// Opción 3: Registro mixto
builder.Services.AddTeamsApiClientServices(); // Usar Teams en producción
// builder.Services.AddSlackApiClientServices(); // Comentar Slack en producción
var app = builder.Build();
Configuración de Registro Avanzada
Para escenarios de registro complejos, el generador soporta un control más fino:
// Definir una interfaz con configuración avanzada
[HttpApiClient(
"https://api.billing.com",
Timeout = 180,
RegistryGroupName = "Billing")]
[Header("X-Billing-Client", "BillingApp")]
[Header("X-Api-Version", "v2.0")]
public interface IBillingApi
{
[HttpPost("invoices")]
Task<InvoiceResult> CreateInvoiceAsync([Body] InvoiceRequest request);
}
// El método de registro generado incluirá más configuraciones
public static IServiceCollection AddBillingApiClientServices(this IServiceCollection services)
{
services.AddHttpClient<IBillingApi, BillingApi>(client =>
{
// Configuración básica
client.BaseAddress = new Uri("https://api.billing.com");
client.Timeout = TimeSpan.FromSeconds(180);
// Configuración de encabezados de solicitud predeterminados
client.DefaultRequestHeaders.Add("X-Billing-Client", "BillingApp");
client.DefaultRequestHeaders.Add("X-Api-Version", "v2.0");
});
return services;
}
Este enfoque de generación automática asegura la coherencia y mantenibilidad de la configuración, al tiempo que ofrece opciones de registro flexibles.
Método de Registro Predeterminado
/// <summary>
/// Registra todas las interfaces sin agrupar, marcadas con [HttpApiClient], junto con su implementación HttpClient.
/// </summary>
public static IServiceCollection AddDefaultApiClientServices(this IServiceCollection services)
{
// Registra la implementación HttpClient de varias interfaces API
services.AddHttpClient<IClientApiConTokens, ClientApiConTokens>(client =>
{
client.Timeout = TimeSpan.FromSeconds(100);
});
services.AddHttpClient<ITestQueryTokenClient, TestQueryTokenClient>(client =>
{
client.Timeout = TimeSpan.FromSeconds(100);
});
services.AddHttpClient<IClientWithoutToken, ClientWithoutToken>(client =>
{
client.Timeout = TimeSpan.FromSeconds(100);
});
return services;
}
Ejemplo de Uso
// Registrar servicios en Startup o Program
var builder = WebApplication.CreateBuilder(args);
// Registro agrupado
builder.Services.AddTeamsApiClientServices();
builder.Services.AddZoomApiClientServices();
// O registrar todas las APIs sin agrupar
builder.Services.AddDefaultApiClientServices();
var app = builder.Build();
// Usar en un controlador o servicio
public class UserManagementController : ControllerBase
{
private readonly IClientApiConTokens _userTokenClient;
private readonly ITestQueryTokenClient _queryTokenClient;
public UserManagementController(IClientApiConTokens userTokenClient, ITestQueryTokenClient queryTokenClient)
{
_userTokenClient = userTokenClient;
_queryTokenClient = queryTokenClient;
}
[HttpGet("api/users/{id}")]
public async Task<IActionResult> GetUserDetails(string id)
{
var user = await _userTokenClient.GetUserAsync(id);
return Ok(user);
}
}
Procesamiento Avanzado de Parámetros
Parámetros de Ruta y Token de Cancelación
El procesamiento avanzado de parámetros soporta múltiples tipos de parámetros y métodos de procesamiento para satisfacer las complejas demandas de las llamadas a API:
| Tipo de Parámetro | Atributo de Marcado | Lógica de Generación | Ejemplo de URL | Escenario de Uso |
|---|---|---|---|---|
| Parámetro de Ruta | [Path] |
Sustitución de marcador de posición de URL | api/users/123 |
Identificación de recursos, consulta de datos |
| Parámetro de Consulta | [Query] |
Cadena de consulta de URL | api/data?name=test |
Condiciones de filtro, parámetros de paginación |
| Parámetro de Cuerpo de Solicitud | [Body] |
Serialización JSON | Cuerpo de solicitud POST | Creación de datos, operaciones por lotes |
| Parámetro de Encabezado | [Header] |
Adición de encabezado HTTP | X-Token: abc123 |
Información de autenticación, identificador de cliente |
| Parámetro de Token | [Token] |
Manejo de Token de autenticación | Varias formas | Autenticación de identidad, control de seguridad |
| Parámetro de Ruta de Archivo | [FilePath] |
Ruta de guardado de archivo | Ruta de guardado de descarga | Descarga de archivos, exportación de datos |
| Parámetro de Consulta de Arreglo | [ArrayQuery] |
Serialización de arreglo | tags=a,b,c |
Condiciones de selección múltiple, consulta por lotes |
| Token de Cancelación | CancellationToken |
Soporte de cancelación asíncrona | Ninguno | Cancelación de solicitud, control de tiempo de espera |
// Uso básico de parámetros de ruta
[HttpApiClient(TokenManagementService = nameof(ITokenManagementService), IsAbstract = true)]
public interface IClienteApiBaseConTokens
{
/// <summary>
/// Obtiene información de usuario en la interfaz base - usando parámetro Path y CancellationToken.
/// </summary>
[HttpGet("api/usuarios/{id}")]
Task<UserProfile> GetUserProfileAsync([Path] string id, CancellationToken cancellationToken = default);
}
Procesamiento de Parámetros de Ruta en el Código Generado:
public async Task<UserProfile> GetUserProfileAsync(string id, CancellationToken cancellationToken)
{
var url = $"api/usuarios/{id}";
// Verifica si BaseAddress está configurado (las URL relativas requieren BaseAddress)
if (_clienteHttp.BaseAddress == null)
{
throw new InvalidOperationException("La configuración de BaseAddress es obligatoria. Las URL relativas requieren un BaseAddress válido " +
"establecido en el atributo HttpApiClient o en AppApiOptions.BaseUrl.");
}
using var request = new HttpRequestMessage(HttpMethod.Get, url);
// Usa el CancellationToken proporcionado
using var response = await _clienteHttp.SendAsync(request, cancellationToken);
// ...
return await SendRequestAndParseResponse<UserProfile>(request, url, cancellationToken);
}
Combinación de Parámetros de Token y Cuerpo
// Usar directamente parámetros de Token y Cuerpo en la interfaz
public interface IClienteApiBaseConTokens
{
/// <summary>
/// Crea un usuario - usa encabezado personalizado para el Token, tipos de parámetros compuestos.
/// </summary>
[HttpPost("/api/v1/user")]
Task<UserCreationResponse> CreateUserWithCustomTokenAsync(
[Token][Header("x-custom-token")] string customToken, // Parámetro de Token
[Body] UserCreationRequest userData, // Parámetro de Cuerpo
CancellationToken cancellationToken = default); // Token de cancelación
}
Procesamiento de Parámetros Compuestos en el Código Generado:
public async Task<UserCreationResponse> CreateUserWithCustomTokenAsync(string customToken, UserCreationRequest userData, CancellationToken cancellationToken)
{
var url = "/api/v1/user";
using var request = new HttpRequestMessage(HttpMethod.Post, url);
// Procesar parámetro de Token - añadir a encabezado específico
if (!string.IsNullOrEmpty(customToken))
request.Headers.Add("x-custom-token", customToken);
// Procesar parámetro de Cuerpo - serializar a JSON
if (userData != null)
{
var jsonContent = JsonSerializer.Serialize(userData, _opcionesJsonSerializer);
request.Content = new StringContent(jsonContent, Encoding.UTF8, ExtractMediaType("application/json"));
}
// Procesamiento de solicitud HTTP...
return await SendRequestAndParseResponse<UserCreationResponse>(request, url, cancellationToken);
}
Parámetros de Consulta Complejos y Tipos de Retorno
// Procesamiento de parámetros complejos en una implementación específica
[HttpApiClient(TokenManagementService = nameof(IUserTokenManagementService), InheritsFrom = "ClienteApiBaseConTokens")]
public interface IClienteApiConTokens : IClienteApiBaseConTokens
{
/// <summary>
/// Obtiene una lista de usuarios - soporta tipos de retorno complejos.
/// </summary>
[HttpGet("api/usuarios")]
Task<List<UserProfile>> GetUsersListAsync(CancellationToken cancellationToken = default);
/// &summary>
/// Ejemplo de parámetros de consulta complejos.
/// </summary>
[HttpGet("api/usuarios/buscar")]
Task<List<UserProfile>> SearchUsersAdvancedAsync(
[Query] string? nombre,
[Query] int? edad,
[Query] DateTime? creadoDespues,
[Query] bool? estaActivo = true,
CancellationToken cancellationToken = default);
}
Procesamiento de Parámetros Complejos en el Código Generado:
public async Task<List<UserProfile>> GetUsersListAsync(CancellationToken cancellationToken)
{
var tokenAcceso = await _servicioGestionTokens.RetrieveTokenAsync();
// Procesamiento de Token y Encabezado...
var url = "api/usuarios";
using var request = new HttpRequestMessage(HttpMethod.Get, url);
// Añadir encabezado...
using var response = await _clienteHttp.SendAsync(request, cancellationToken);
// Procesar tipo de retorno complejo - List<UserProfile>
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
var result = await JsonSerializer.DeserializeAsync<List<UserProfile>>(stream, _opcionesJsonSerializer, cancellationToken);
return result;
}
public async Task<List<UserProfile>> SearchUsersAdvancedAsync(string? nombre, int? edad, DateTime? creadoDespues, bool? estaActivo, CancellationToken cancellationToken)
{
var tokenAcceso = await _servicioGestionTokens.RetrieveTokenAsync();
// Procesamiento de Token y Encabezado...
var urlBuilder = new StringBuilder("api/usuarios/buscar?");
var hasQuery = false;
// Construir parámetros de consulta
if (nombre != null)
{
if (hasQuery) urlBuilder.Append("&");
urlBuilder.Append("nombre=").Append(HttpUtility.UrlEncode(nombre));
hasQuery = true;
}
if (edad.HasValue)
{
if (hasQuery) urlBuilder.Append("&");
urlBuilder.Append("edad=").Append(edad.Value);
hasQuery = true;
}
if (creadoDespues.HasValue)
{
if (hasQuery) urlBuilder.Append("&");
urlBuilder.Append("creadoDespues=").Append(HttpUtility.UrlEncode(creadoDespues.Value.ToString("yyyy-MM-ddTHH:mm:ss")));
hasQuery = true;
}
// Si estaActivo es null o false, no se añade a la query, asumiendo true es el default
if (estaActivo.HasValue && estaActivo.Value != true)
{
if (hasQuery) urlBuilder.Append("&");
urlBuilder.Append("estaActivo=").Append(estaActivo.Value.ToString().ToLower());
}
else if (!estaActivo.HasValue) // Si no se especificó, y el default es true, no añadir
{
// No hacer nada si es null y el default es true. Si se quiere enviar false, se debe especificar explícitamente.
}
var url = urlBuilder.ToString();
// Procesamiento de solicitud HTTP...
return await SendRequestAndParseResponse<List<UserProfile>>(new HttpRequestMessage(HttpMethod.Get, url), url, cancellationToken);
}
Ejemplo Integral de Múltiples Tipos de Parámetros
El siguiente es un ejemplo completo de una interfaz API compleja, que muestra el uso combinado de varios tipos de parámetros:
public interface IComprehensiveApiClient
{
// Subida de archivos - usando MultipartFormDataContent
[HttpPost("upload-file")]
Task<FileUploadResult> UploadFileToServerAsync([Body] MultipartFormDataContent fileContent);
// Parámetros de consulta de arreglo - soporta condiciones de selección múltiple
[HttpGet("filter-search")]
Task<List<SearchResult>> SearchWithFiltersAsync(
[ArrayQuery(",")] string[] tags,
[ArrayQuery("filter-criteria")] string[] criteria,
[Query] int pageNumber = 1,
[Query] int pageSize = 20);
// Descarga de archivos - retorna arreglo de bytes
[HttpGet("download-bytes/{documentId}")]
Task<byte[]> DownloadDocumentAsBytesAsync(string documentId);
// Descarga de archivos - guarda en ruta específica
[HttpGet("download-to-path/{documentId}")]
Task DownloadDocumentToFileAsync(string documentId, [FilePath] string savePath);
// Content-Type personalizado - datos XML
[HttpPost("submit-xml")]
Task<SubmissionResult> SubmitXmlDataAsync([Body(ContentType = "application/xml")] XmlDataPayload xmlData);
// Content-Type personalizado - datos de texto plano
[HttpPost("submit-text")]
Task<SubmissionResult> SubmitTextDataAsync([Body(UseStringContent = true, ContentType = "text/plain")] string rawText);
// Parámetros de ruta complejos - anidamiento multinivel
[HttpGet("usuarios/{userId}/proyectos/{projectId}/tareas/{taskId}")]
Task<TaskItem> GetTaskDetailsAsync(string userId, string projectId, string taskId);
// Múltiples parámetros de Encabezado - autenticación y metadatos
[HttpPost("process-request")]
Task<ProcessingResult> ProcessRequestWithHeadersAsync(
[Body] RequestPayload requestData,
[Header("Authorization")] string authToken,
[Header("X-Correlation-ID")] string correlationId,
[Header("X-App-Version")] string appVersion = "1.0");
}
Tabla Comparativa de Procesamiento Avanzado de Parámetros
| Característica | Combinación de Parámetros | URL/Solicitud Generada | Complejidad de Procesamiento | Aplicación Típica |
|---|---|---|---|---|
| Subida de Archivos | [Body] MultipartFormDataContent |
multipart/form-data |
Alta | Subida de imágenes, documentos |
| Consulta Multicriterio | [ArrayQuery] + [Query] |
?tags=a,b&page=1 |
Media | Funcionalidad de búsqueda, filtrado de listas |
| Descarga de Archivos | [Path] + [FilePath] |
Descarga y guarda en archivo | Media | Descarga de informes, exportación de datos |
| Múltiples Formatos de Datos | [Body(ContentType)] |
Content-Type personalizado | Media | Mezcla de XML/JSON/Texto |
| Ruta Compleja | Múltiples parámetros [Path] |
/users/123/projects/456/tasks/789 |
Baja | Acceso a recursos anidados |
| Autenticación con Múltiples Encabezados | Múltiples parámetros [Header] |
Varios encabezados de solicitud | Baja | Protocolos de autenticación complejos |
| Consulta Combinada | [Query] + [Path] + [Body] |
Solicitud compuesta | Alta | API de negocio complejas |
Manejo de Errores y Sistema de Registro
Código de Mecanismo de Manejo de Errores Generado
El código generado incluye una lógica completa de manejo de errores:
public async Task<T> ExecuteApiRequestAsync<T>(HttpRequestMessage request, string url, CancellationToken cancellationToken)
{
try
{
using var response = await _clienteHttp.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
// Registrar log de error
if (_configuracionApi.EnableLogging)
{
_registrador.LogError("Solicitud HTTP fallida: {StatusCode}, Respuesta: {Response}",
(int)response.StatusCode, errorContent);
}
// Invocar Método Parcial de manejo de errores
OnRequestFailed(response, url);
throw new HttpRequestException($"La solicitud HTTP falló con el código: {(int)response.StatusCode}");
}
// Procesamiento exitoso
OnRequestCompleted(response, url);
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
if (stream.Length == 0)
{
return default;
}
return await JsonSerializer.DeserializeAsync<T>(stream, _opcionesJsonSerializer, cancellationToken);
}
catch (Exception ex)
{
// Registro de excepción
if (_configuracionApi.EnableLogging)
{
_registrador.LogError(ex, "Excepción en solicitud HTTP: {Url}", url);
}
// Invocar Método Parcial de manejo de errores
OnRequestException(ex, url);
throw;
}
}
Soporte de Extensión para Métodos Parciales
El código generado incluye Métodos Parciales, permitiendo a los usuarios personalizar el comportamiento:
// Definición de Métodos Parciales
partial void OnRequestBeforeSend(HttpRequestMessage request, string url);
partial void OnRequestCompleted(HttpResponseMessage response, string url);
partial void OnRequestFailed(HttpResponseMessage response, string url);
partial void OnRequestException(Exception error, string url);
// Los usuarios pueden implementar lógica personalizada a través de Clases Parciales
public partial class ClientApiConTokens
{
partial void OnRequestBeforeSend(HttpRequestMessage request, string url)
{
// Procesamiento personalizado antes de la solicitud
request.Headers.Add("X-Custom-Header", "MyCustomValue");
}
partial void OnRequestException(Exception error, string url)
{
// Procesamiento personalizado durante el error
if (error is HttpRequestException httpEx)
{
// Manejo especial de excepciones HTTP
}
}
}
Integración de Registro Estructurado
// El código generado incluye un registro detallado
if (_configuracionApi.EnableLogging)
{
_registrador.LogDebug("Iniciando solicitud HTTP GET: {Url}", url);
}
// Después de completar la solicitud
if (_configuracionApi.EnableLogging)
{
_registrador.LogDebug("Solicitud HTTP completada: {StatusCode}", (int)response.StatusCode);
}
// En caso de error
if (_configuracionApi.EnableLogging)
{
_registrador.LogError("Solicitud HTTP fallida: {StatusCode}, Respuesta: {Response}",
(int)response.StatusCode, errorContent);
}
// En caso de excepción
if (_configuracionApi.EnableLogging)
{
_registrador.LogError(ex, "Excepción en solicitud HTTP: {Url}", url);
}
Optimización de Rendimiento y Mejores Prácticas
Optimización de la Configuración de HttpClient
// El código generado configura HttpClient automáticamente
public ClienteApiConTokens(HttpClient httpClient, ILogger<ClienteApiConTokens> logger,
IOptions<JsonSerializerOptions> opcionesSerializador,
IOptions<AppApiOptions> configuracionApi,
ITenantTokenManagementService servicioGestionTokens)
: base(httpClient, logger, opcionesSerializador, configuracionApi, servicioGestionTokens)
{
// Establecer HttpClient BaseAddress (para solicitudes de ruta relativa)
var direccionBaseFinal = GetEffectiveBaseAddress();
if (!string.IsNullOrEmpty(direccionBaseFinal))
{
_clienteHttp.BaseAddress = new Uri(direccionBaseFinal);
}
// Configurar tiempo de espera de HttpClient
var tiempoEsperaFinal = GetEffectiveTimeout();
_clienteHttp.Timeout = TimeSpan.FromSeconds(tiempoEsperaFinal);
}
Estrategias de Optimización de Memoria
El código generado incluye varias medidas de optimización de memoria:
// Uso de sentencias using para asegurar la liberación de recursos
using var request = new HttpRequestMessage(HttpMethod.Get, url);
using var response = await _clienteHttp.SendAsync(request, cancellationToken);
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
// Evitar la creación de objetos innecesarios
if (stream.Length == 0)
{
return default; // Retornar directamente, evitar deserializar un flujo vacío
}
// Reutilización de JsonSerializerOptions
var result = await JsonSerializer.DeserializeAsync<T>(stream, _opcionesJsonSerializer, cancellationToken);
Recomendaciones de Uso
Principios de Diseño de Interfaz
// ✅ Buenas prácticas - Métodos HTTP y rutas claras
[HttpGet("api/usuarios/{id}")]
Task<UserProfile> GetUserDetailsByIdAsync([Path] string id, CancellationToken cancellationToken = default);
// ✅ Buenas prácticas - Nomenclatura de métodos apropiada
[HttpPost("api/usuarios")]
Task<UserProfile> CreateNewUserAsync([Body] CreateUserRequest request, CancellationToken cancellationToken = default);
// ❌ Práctica a evitar - Nomenclatura ambigua
[HttpGet("obtaindata")]
Task<dynamic> RetrieveData(object input);
Estándares de Manejo de Errores
// ✅ Habilitar registro
public class ClientApiOptions
{
public bool EnableLogging { get; set; } = true;
}
// ✅ Usar Método Parcial para manejo de errores personalizado
public partial class MyApiClient
{
partial void OnRequestFailed(HttpResponseMessage response, string url)
{
// Lógica de manejo de errores personalizada
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Manejar fallo de autenticación
}
}
}
Consideraciones de Seguridad
// ✅ Usar HTTPS
[HttpApiClient("https://api.secureapp.com")]
// ✅ Implementar actualización de Token en el gestor de Tokens
public class SecureTokenManager : ITokenManagementService
{
private string _cachedToken;
private DateTime _tokenExpiry;
public async Task<string> RetrieveTokenAsync()
{
if (string.IsNullOrEmpty(_cachedToken) || DateTime.UtcNow >= _tokenExpiry)
{
await RefreshTokenAsync();
}
return _cachedToken;
}
private async Task RefreshTokenAsync()
{
// Implementar lógica de actualización de Token
// Ejemplo: llamar a un endpoint de autenticación para obtener un nuevo token
_cachedToken = "Bearer fresh_token_xyz";
_tokenExpiry = DateTime.UtcNow.AddMinutes(55); // Token válido por 55 minutos
}
}
Casos de Uso en la Práctica
Aplicación en Arquitecturas de Microservicios
// Definir interfaces API entre microservicios
[HttpApiClient("https://user-service-api", RegistryGroupName = "UserService")]
[Header("X-Caller-Service", "OrderService")]
public interface IUserServiceClient
{
[HttpGet("api/users/{id}")]
Task<UserProfile> GetUserInfoAsync([Path] string id);
[HttpPost("api/users/validate")]
Task<ValidationResult> ValidateUserCredentialsAsync([Body] UserValidationRequest request);
}
[HttpApiClient("https://order-service-api", RegistryGroupName = "OrderService")]
[Header("X-Caller-Service", "UserService")]
public interface IOrderServiceClient
{
[HttpGet("api/orders/user/{userId}")]
Task<List<OrderDetails>> GetUserOrdersHistoryAsync([Path] string userId);
[HttpPost("api/orders")]
Task<OrderDetails> CreateNewOrderAsync([Body] CreateOrderRequest request);
}
// Usar en microservicios
public class OrdersController : ControllerBase
{
private readonly IUserServiceClient _userService;
private readonly IOrderServiceClient _orderService;
public OrdersController(IUserServiceClient userService, IOrderServiceClient orderService)
{
_userService = userService;
_orderService = orderService;
}
[HttpPost("orders")]
public async Task<IActionResult> PlaceOrder([FromBody] CreateOrderRequest request)
{
// Validar usuario
var userValidation = await _userService.ValidateUserCredentialsAsync(
new UserValidationRequest { UserId = request.UserId });
if (!userValidation.IsValid)
{
return BadRequest("Fallo la validación del usuario.");
}
// Crear pedido
var order = await _orderService.CreateNewOrderAsync(request);
return Ok(order);
}
}
Caso de Integración con APIs de Terceros
Integración con API de Pagos Externos
[HttpApiClient("https://api.externalpaymentgateway.com", RegistryGroupName = "ExternalPayments")]
[Header("X-Gateway-Source", "MudFramework")]
public interface IExternalPaymentGatewayApi
{
[HttpPost("v1/process")]
Task<PaymentGatewayResponse> ExecuteTransactionAsync([Body] PaymentGatewayRequest request);
[HttpGet("v1/status")]
Task<PaymentGatewayQueryResponse> QueryTransactionStatusAsync([Query] Dictionary<string, string> parameters);
}
Integración con API de Mensajería Social
[HttpApiClient(TokenManagementService = nameof(ISocialTokenManager), RegistryGroupName = "SocialMessaging")]
[Header("Content-Type", "application/json")]
public interface ISocialMessagingApi
{
[HttpGet("oauth2/token")]
Task<SocialTokenResponse> GetAccessToken([Query] string grantType, [Query] string clientId, [Query] string clientSecret);
[HttpPost("messages/send")]
Task<SocialMessagingResponse> SendTemplateMessageAsync([Body] TemplateMessagePayload request);
}
Prácticas en Proyectos Empresariales
Patrón de Gateway API Unificado
// Interfaz API base
[HttpApiClient(TokenManagementService = nameof(IGatewayAuthTokenManager), IsAbstract = true)]
public interface IBaseUnifiedGatewayApi
{
[HttpGet("healthcheck")]
Task<GatewayHealthStatus> PerformHealthCheckAsync();
[HttpGet("api/version")]
Task<ServiceVersionInfo> GetServiceVersionAsync();
}
// API de negocio específica
[HttpApiClient(InheritsFrom = "BaseUnifiedGatewayApi")]
[Header("X-Gateway-Client", "UnifiedClientApp")]
[Header("X-API-Revision", "v1.0")]
public interface IBusinessLogicApi : IBaseUnifiedGatewayApi
{
[HttpGet("api/business/data")]
Task<BusinessDataPayload> FetchBusinessDataAsync([Query] string category);
[HttpPost("api/business/process")]
Task<ProcessResultDetails> ProcessBusinessDataAsync([Body] ProcessRequestPayload request);
}
Gestión de Configuración Multientorno
// Configuración para entorno de desarrollo
public class DevelopmentApiSettings
{
public string BaseUrl { get; set; } = "https://dev-api.mycompany.com";
public string RequestTimeout { get; set; } = "30";
public bool EnableDetailedLogging { get; set; } = true;
}
// Configuración para entorno de producción
public class ProductionApiSettings
{
public string BaseUrl { get; set; } = "https://api.mycompany.com";
public string RequestTimeout { get; set; } = "60";
public bool EnableDetailedLogging { get; set; } = false;
}
// Interfaces API específicas del entorno
[HttpApiClient("https://dev-api.mycompany.com", RegistryGroupName = "DevelopmentApis")]
public interface IDevelopmentApi
{
// APIs específicas para el entorno de desarrollo
}
[HttpApiClient("https://api.mycompany.com", RegistryGroupName = "ProductionApis")]
public interface IProductionApi
{
// APIs específicas para el entorno de producción
}
Evolución Futura
Plan de Extensión del Framework API
| Dirección Técnica | Funcionalidad Objetivo | Desafío Técnico | Beneficio Esperado |
|---|---|---|---|
| Generación de Clientes GraphQL | Análisis de Schema, generación de consultas | Mapeo de tipos, consultas dinámicas | Integración con el ecosistema GraphQL |
| Generación de Servidores gRPC | Análisis de Protobuf, implementación de servicios | Procesamiento de flujos, comunicación bidireccional | Optimización de comunicación de microservicios |
| Cliente WebSocket | Comunicación en tiempo real, manejo de mensajes | Gestión de conexiones, mecanismos de reconexión | Soporte para aplicaciones en tiempo real |
| Integración con OpenAPI | Importación de especificaciones, generación de interfaces | Mapeo de tipos complejos, soporte multiversión | Integración estandarizada de APIs |
Integración de Tecnologías Cloud-Native
// Ejemplo de integración de tecnologías cloud-native
[HttpApiClient]
[KubernetesServiceDiscovery("user-service")]
[ConsulServiceRegistration]
public interface ICloudNativeApi
{
[HttpGet("status")]
Task<ServiceHealthStatus> GetServiceStatusAsync();
}
Tecnologías de Generación de Código Inteligente
- Análisis Semántico Mejorado: Comprensión de descripciones de interfaz basadas en lenguaje natural.
- Optimización de Reconocimiento de Patrones: Identificación y optimización automática de patrones de código.
- Modelo de Predicción de Rendimiento: Sugerencias de optimización de rendimiento basadas en aprendizaje automático.
- Detección de Vulnerabilidades de Seguridad: Análisis estático de riesgos de seguridad del código.
Propuesta de Integración de Herramientas
| Tipo de Herramienta | Forma de Integración | Implementación Técnica | Valor para el Usuario |
|---|---|---|---|
| Plugins de IDE | Extensión de Visual Studio | Generación de código en tiempo real, resaltado de sintaxis | Optimización de la experiencia de desarrollo |
| Herramientas CLI | Interfaz de línea de comandos | Procesamiento por lotes, construcción automatizada | Integración con CI/CD |
| Interfaz Web | Aplicación de navegador | Configuración visual, vista previa en tiempo real | Simplificación de la gestión de configuración |
| Pruebas de API | Marco de pruebas integrado | Pruebas automatizadas, análisis de rendimiento | Garantía de calidad |
Conclusiones
Resumen de Logros Técnicos
El generador de código para .NET, a través de un diseño arquitectónico sistemático, ofrece una solución de automatización eficiente para la ingeniería de software moderna. Desde la arquitectura de herencia de clases abstractas hasta el sistema de gestión de tokens, desde el registro automático de servicios hasta el procesamiento avanzado de parámetros, cada módulo técnico ha sido meticulosamente diseñado para abordar los problemas centrales de eficiencia en el desarrollo de software.
Verificación de Métricas Técnicas
Basado en datos de aplicaciones de proyectos reales, este generador de código ha demostrado un rendimiento excelente en las siguientes métricas técnicas:
| Dimensión de Evaluación | Grado de Mejora | Principio Técnico | Método de Verificación |
|---|---|---|---|
| Eficiencia de Desarrollo | Aumento del 40-70% | Generación de código en tiempo de compilación | Comparación de ciclos de entrega de proyectos |
| Calidad del Código | Reducción de la tasa de defectos en más del 60% | Garantía de seguridad de tipos | Análisis estático de código |
| Costo de Mantenimiento | Reducción en más del 50% | Generación basada en plantillas | Estadísticas de frecuencia de cambios de código |
| Coherencia Arquitectónica | Aumento de más del 80% | Estándares de plantilla unificados | Resultados de revisión de código |
Estado de Aplicación
Esta tecnología de generación de código en tiempo de compilación ha sido aplicada en proyectos de ingeniería. Utilizando las capacidades de análisis sintáctico y modelado semántico del servicio de compilación Roslyn, el generador automatiza la construcción de código de cliente HTTP mediante algoritmos de recorrido del árbol de sintaxis abstracta, mejorando significativamente la eficiencia del desarrollo y la calidad del código en aplicaciones de nivel empresarial.