Generación Avanzada de Clientes HTTP en .NET con Roslyn: Automatización de Herencia y DI

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:

  1. GeneradorDeEntidades: Encargado de la generación de código para modelos de datos.
  2. 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:

  1. Construcción del Árbol de Sintaxis: Utiliza el analizador Roslyn para construir el Árbol de Sintaxis Abstracto de la definición de la interfaz.
  2. Análisis del Modelo Semántico: Realiza un análisis semántico de los atributos marcados y los tipos de parámetros.
  3. Coincidencia de Plantillas de Código: Selecciona la plantilla de código apropiada basada en los resultados del análisis.
  4. Análisis de Relaciones de Dependencia: Identifica las dependencias requeridas por la interfaz y determina el orden de inyección.
  5. 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:

  1. Identificación de Gestor de Tokens: El atributo TokenManagementService identifica ITokenManagementService.
  2. Identificación de Opciones de Configuración: Se identifican AppApiOptions y JsonSerializerOptions a partir de los archivos de configuración del proyecto.
  3. Identificación del Componente de Registro: Cada clase generada inyecta automáticamente el ILogger<T> correspondiente.
  4. 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:

  1. Comprobación de Código de Estado: Verifica IsSuccessStatusCode.
  2. Lectura del Contenido de Error: Lee asíncronamente el contenido de la respuesta.
  3. Registro de Errores: Registra información detallada del error.
  4. Invocación de Método Parcial: Llama a los hooks de manejo de fallos.
  5. Excepción Estandarizada: Lanza un HttpRequestException con 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:

  1. Sentencias using: Aseguran que HttpRequestMessage y HttpResponseMessage se liberen correctamente.
  2. Procesamiento en Flujo: Deserializa directamente desde el flujo de respuesta, evitando copias de memoria innecesarias.
  3. Comprobación de Flujo Vacío: Verifica la longitud del flujo de respuesta para evitar la deserialización de contenido vacío.
  4. Reutilización de Configuración: Reutiliza la instancia de JsonSerializerOptions.
  5. 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:

  1. Definición de Interfaz: [Header("Autorización", AliasAs = "X-Api-Auth-Token")]
  2. Código Generado: solicitud.Headers.Add("X-Api-Auth-Token", tokenAcceso)
  3. 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:

  1. Prioridad de Token: Los parámetros de consulta de token siempre se añaden primero (usando ?).
  2. Comprobación de Valores Nulos: Evitar añadir parámetros de consulta nulos o con valores predeterminados.
  3. Codificación Segura: Todos los parámetros se procesan con codificación URL.
  4. 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:

  1. Entrega de Tokens Temporales: Cuando se necesita usar un token específico de una sola vez.
  2. Escenarios Multi-Inquilino: Diferentes inquilinos usan diferentes tokens.
  3. Entornos de Prueba: Especificar manualmente tokens de prueba.
  4. 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:

  1. Gestión Modular: Registro agrupado de APIs de diferentes módulos de negocio.
  2. Carga Bajo Demanda: Registrar solo las APIs del grupo específico cuando sea necesario.
  3. Aislamiento de Configuración: Diferentes grupos pueden tener configuraciones independientes.
  4. 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.

Etiquetas: Roslyn Source Generators C# .NET HttpClient

Publicado el 10-24 19:25