← maurobernal.com.ar

Etiqueta: httpClient

  • dotnet run script.cs y las nuevas herramientas de .NET 10: OpenAPI, HttpClient y más

    Uno de los cambios de .NET 10 que más me llamó la atención no es del compilador ni del runtime: es de las herramientas. dotnet run script.cs permite ejecutar un archivo C# sin crear un proyecto. Y el soporte OpenAPI nativo en Minimal APIs me hizo desinstalar Swashbuckle de varias soluciones. Repaso todo el tooling nuevo.

    dotnet run script.cs: C# sin proyecto

    Antes de .NET 10, ejecutar un archivo C# requería crear un proyecto, un .csproj, restaurar paquetes… Para un script rápido o una prueba de concepto, era excesivo. Ahora podés ejecutar un archivo directamente.

    // script.cs — archivo suelto, sin .csproj
    using System.Net.Http.Json;
    
    var http = new HttpClient();
    var usuarios = await http.GetFromJsonAsync<List<Usuario>>(
        "https://jsonplaceholder.typicode.com/users"
    );
    
    foreach (var u in usuarios ?? [])
        Console.WriteLine($"{u.Id}: {u.Name} ({u.Email})");
    
    record Usuario(int Id, string Name, string Email);
    # Ejecutar directamente
    dotnet run script.cs
    
    # Con referencias a paquetes NuGet (directivas en el archivo)
    # #:package Newtonsoft.Json@13.0.3
    
    # Con argumentos
    dotnet run script.cs -- arg1 arg2
    
    # Ver el IL generado sin ejecutar
    dotnet run --no-build script.cs

    Uso esto constantemente para probar APIs externas, procesar archivos CSV, o generar datos de prueba. Antes abría LINQPad; ahora uso la terminal directamente.

    OpenAPI built-in en Minimal APIs (.NET 9+)

    Desde .NET 9, la documentación OpenAPI está integrada en el framework. No hace falta Swashbuckle ni NSwag para tener un endpoint /openapi/v1.json funcional.

    // Program.cs — OpenAPI nativo en .NET 9/10
    var builder = WebApplication.CreateBuilder(args);
    
    // Solo agregar esto:
    builder.Services.AddOpenApi();
    
    var app = builder.Build();
    
    // Endpoint para servir el documento OpenAPI
    app.MapOpenApi();  // /openapi/v1.json por defecto
    
    // Opcional: UI de Swagger (sigue siendo un paquete externo)
    // app.UseSwaggerUI(options => options.SwaggerEndpoint("/openapi/v1.json", "Mi API"));
    
    // Minimal API con metadata para OpenAPI
    app.MapGet("/api/pedidos/{id}", async (int id, IRepositorio repo) =>
    {
        var pedido = await repo.ObtenerAsync(id);
        return pedido is null ? Results.NotFound() : Results.Ok(pedido);
    })
    .WithName("ObtenerPedido")
    .WithSummary("Obtiene un pedido por ID")
    .WithDescription("Retorna el pedido completo con sus líneas de detalle")
    .WithTags("Pedidos")
    .Produces<Pedido>(200)
    .Produces(404);
    
    app.Run();

    HttpClient: mejoras en .NET 10

    // .NET 10: HttpClient con Resilience nativo (Microsoft.Extensions.Http.Resilience)
    builder.Services.AddHttpClient<IMiApiClient, MiApiClient>(client =>
    {
        client.BaseAddress = new Uri("https://api.externa.com");
        client.Timeout = TimeSpan.FromSeconds(30);
    })
    .AddStandardResilienceHandler();  // retry + circuit breaker + timeout automáticos
    
    // Configuración personalizada del pipeline de resiliencia
    builder.Services.AddHttpClient<IMiApiClient, MiApiClient>()
        .AddResilienceHandler("mi-pipeline", builder =>
        {
            builder.AddRetry(new HttpRetryStrategyOptions
            {
                MaxRetryAttempts = 3,
                Delay = TimeSpan.FromMilliseconds(500),
                BackoffType = DelayBackoffType.Exponential
            });
            
            builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
            {
                FailureRatio = 0.5,
                SamplingDuration = TimeSpan.FromSeconds(10),
                MinimumThroughput = 5
            });
            
            builder.AddTimeout(TimeSpan.FromSeconds(10));
        });

    Mejoras en Minimal APIs

    // .NET 9/10: TypedResults para respuestas con tipos bien definidos
    app.MapPost("/api/usuarios", async (CrearUsuarioRequest req, IServicio svc) =>
    {
        if (string.IsNullOrEmpty(req.Email))
            return TypedResults.ValidationProblem(new Dictionary<string, string[]>
            {
                ["email"] = ["El email es requerido"]
            });
    
        var usuario = await svc.CrearAsync(req);
        return TypedResults.Created($"/api/usuarios/{usuario.Id}", usuario);
    });
    
    // Route groups para organizar endpoints
    var pedidosGroup = app.MapGroup("/api/pedidos")
        .WithTags("Pedidos")
        .RequireAuthorization();
    
    pedidosGroup.MapGet("/", ObtenerTodos);
    pedidosGroup.MapGet("/{id}", ObtenerPorId);
    pedidosGroup.MapPost("/", Crear);
    pedidosGroup.MapPut("/{id}", Actualizar);
    pedidosGroup.MapDelete("/{id}", Eliminar);

    Blazor en .NET 10: render modes y streaming

    // .NET 10: componentes Blazor con render modes explícitos
    @page "/dashboard"
    @rendermode InteractiveServer
    
    <h1>Dashboard</h1>
    
    @if (datos is null)
    {
        <p>Cargando...</p>
    }
    else
    {
        @foreach (var item in datos)
        {
            <div>@item.Nombre: @item.Valor</div>
        }
    }
    
    @code {
        private List<Dato>? datos;
    
        protected override async Task OnInitializedAsync()
        {
            // Streaming: el componente renderiza mientras carga
            await foreach (var dato in servicio.GetStreamAsync())
            {
                datos ??= [];
                datos.Add(dato);
                StateHasChanged();  // actualiza el DOM incremental
            }
        }
    }

    ← Rendimiento extremo en .NET 10: Stack allocation, Native AOT y el GC que trabaja menos | Serie .NET 8 → .NET 10 | Próximo: Guía práctica: cómo migré mis proyectos de .NET 8 a .NET 10 sin romper producción →

Tags

tsql (27)mssql (26)sql (20)devops (20)dotnet (18)docker (15)performance (14)contenedores (11)dotnet10 (10)linux (9)csharp (8)microservicios (7)angular (7)angular21 (7)sql server (6)issabel (6)docker-compose (6)typescript (6)mysql (5).NET (5)