Eternet.Client.Http.Generator
2.1.25
Prefix Reserved
See the version list below for details.
dotnet add package Eternet.Client.Http.Generator --version 2.1.25
NuGet\Install-Package Eternet.Client.Http.Generator -Version 2.1.25
<PackageReference Include="Eternet.Client.Http.Generator" Version="2.1.25"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Eternet.Client.Http.Generator" Version="2.1.25" />
<PackageReference Include="Eternet.Client.Http.Generator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Eternet.Client.Http.Generator --version 2.1.25
#r "nuget: Eternet.Client.Http.Generator, 2.1.25"
#:package Eternet.Client.Http.Generator@2.1.25
#addin nuget:?package=Eternet.Client.Http.Generator&version=2.1.25
#tool nuget:?package=Eternet.Client.Http.Generator&version=2.1.25
Eternet.Client.Http.Generator
Generador incremental para crear handlers HTTP a partir de contratos Eternet.Mediator.
Para la guía actual de autoría de contratos que alimentan este generator, ver
../docs/contracts-and-transport-authoring.md y ../docs/endpoint-manifest-ecosystem.md.
Soporta el caso clásico de un solo HttpClient por contrato y, además, el escenario de API Gateway con:
- múltiples transportes por cliente (
Internal,Gateway) - ruta proyectada por namespace
- override de verbo HTTP en gateway
- selección dinámica en runtime sin ensuciar
Mediator
Paquetes
En la app consumidora:
<ItemGroup>
<PackageReference Include="Eternet.Mediator" Version="x.y.z" />
<PackageReference Include="Eternet.Client.Http.Generator" Version="x.y.z" PrivateAssets="all" />
</ItemGroup>
Si el contrato publica metadata de gateway:
<ItemGroup>
<PackageReference Include="Eternet.Models" Version="x.y.z" />
</ItemGroup>
Alternativa local en workspace:
<ItemGroup>
<ProjectReference Include="..\..\..\..\Eternet.AspNetCore\src\Eternet.Mediator\Eternet.Client.Http.Generator\Eternet.Client.Http.Generator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
Estrategia de runtime
El package tiene dos modos de uso.
App cliente pura
Una app cliente que no expone controladores no debe referenciar Eternet.Mediator.Generator.
Con:
<ItemGroup>
<PackageReference Include="Eternet.Mediator" Version="x.y.z" />
<PackageReference Include="Eternet.Client.Http.Generator" Version="x.y.z" PrivateAssets="all" />
</ItemGroup>
el generator HTTP emite el runtime liviano necesario para:
services.AddMediator()Mediator/IMediator- descubrimiento y registro de
IRequestHandler<,>locales - ejecución de
IPipelineBehavior<,>locales - fallback al dispatch HTTP generado cuando no existe handler local
Este es el caso pensado para Blazor/WASM y clientes que sólo consumen contratos o ejecutan comandos internos livianos.
App mixta o server
Si el proyecto ya usa Eternet.Mediator.Generator para generar el runtime server, pipelines y controladores, el runtime liviano del cliente debe apagarse explícitamente:
[assembly: Mediator.MediatorClientOptionsAttribute(GenerateMediatorRuntime = false)]
Con esa opción:
Eternet.Client.Http.Generatorsigue generando handlers HTTP yservices.AddHttpClientHandlers()services.AddHttpClientHandlers()también registra losIRequestHandler<,>generados para que elIMediatorprincipal del host pueda despacharlosEternet.Mediator.Generatorqueda como único dueño deservices.AddMediator()y del runtime compartido- se evita cualquier duplicación de
Mediator,AddMediator(...)o tipos de options
GenerateMediatorRuntime no es una opción de DI tardía; es un switch de ownership leído antes del codegen:
true: la app cliente dueña del proceso también es dueña del runtime livianofalse: el package cliente queda transport-only y el host conservaAddMediator(...),IMediatory los request senders
Usar un atributo, y no una opción runtime de AddMediator(...), permite decidir ese ownership antes de generar código.
Si consumís el generator vía PackageReference y necesitás declarar ese opt-out en source, definí una vez
MediatorClientOptionsAttribute en el proyecto consumidor. El generator detecta ese tipo y no vuelve a emitirlo:
using System;
[assembly: Mediator.MediatorClientOptionsAttribute(GenerateMediatorRuntime = false)]
namespace Mediator;
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class MediatorClientOptionsAttribute : Attribute
{
public bool GenerateMediatorRuntime { get; set; } = true;
}
Si además querés customizar namespace, lifetime o ExtensionMethodNamespace, sumá esas properties al atributo local.
Descriptor HTTP dentro del host server
Cuando un server necesita consumir endpoints HTTP de otro servicio, ya no es obligatorio crear un ensamblado
*.Client sólo para aislar el descriptor. El host puede contener endpoints locales y descriptores outbound en el mismo
assembly si los contratos remotos publican endpoint manifest y los descriptores outbound están dentro de un contenedor
[GenerateHttpClient].
El contrato remoto sigue siendo el dueño de EndpointGroup, HTTP verb, ruta y bindings:
using Eternet.Mediator;
using Eternet.Mediator.Abstractions.Handlers;
using Eternet.Mediator.Abstractions.Responses;
using Eternet.Mediator.Attributes.Mvc;
using Mediator;
namespace Worker.Contracts;
[EndpointGroup("WorkerProducts")]
public abstract class GetWorkerProductContract
: DomainResultHandlerAsync<GetWorkerProductContract.Request, GetWorkerProductContract.Response>
{
public record Request : IRequest<Response>
{
[FromRoute] public required int Id { get; init; }
}
public record Response : DomainResult;
[HttpGet("{id}")]
public abstract override ValueTask<Response> Handle(Request request, CancellationToken cancellationToken);
}
El host mixto sólo agrega el descriptor de cliente HTTP:
using Eternet.Mediator;
using Worker.Contracts;
[assembly: Mediator.MediatorClientOptionsAttribute(GenerateMediatorRuntime = false)]
[GenerateHttpClient("Worker")]
internal abstract class WorkerHttpClient
{
internal abstract class GetWorkerProduct : GetWorkerProductContract;
}
Con [GenerateHttpClient] en el contenedor:
Eternet.Client.Http.Generatorresuelve la metadata desde el endpoint manifest del assembly de contratos referenciadoEternet.Client.Http.Generatorgenera el handler HTTP y los helpersAdd*HttpClient(...)Eternet.Mediator.Generatorno genera controller MVC para esos descriptores outboundEternet.Mediator.EndpointManifest.Generatory el fallback por reflection no los publican como endpoints servidos por este assemblyGenerateMediatorRuntime = falsesigue siendo necesario cuando el host ya usaEternet.Mediator.Generator
El ensamblado *.Client sigue siendo válido cuando querés una frontera NuGet reutilizable, pero ya no es el único modo
seguro para un host mixto.
Caso simple
El uso existente no cambia:
using Eternet.Mediator;
using Eternet.Netmap.Contracts.Nodes.Commands;
[GenerateHttpClient("Default")]
internal abstract class DefaultHttpClient
{
internal abstract class AddNodeHandler : AddNode;
internal abstract class UpdateNodeHandler : UpdateNode;
}
El generator sigue emitiendo:
services.AddDefaultHttpClient(...)services.AddHttpClientHandlers()
Registro:
services.AddHttpClientHandlers();
services.AddDefaultHttpClient(
"https://internal-service/",
configure: client =>
{
client.DefaultRequestHeaders.Add("X-App", "Netmap");
},
configureBuilder: builder =>
{
builder.AddHttpMessageHandler<MyAuthHandler>();
});
Consumo:
public sealed class NodesViewModel(IGetResponse<AddNode.Response> response)
{
public ValueTask<AddNode.Response> CreateAsync(AddNode.Request request, CancellationToken ct)
=> response.Get(request, ct);
}
IGetResponse<TResponse> sigue usando IMediator.
En el runtime liviano del cliente, IMediator intenta resolver primero un IRequestHandler<,> local y, si no existe, cae al handler HTTP generado para ese contrato.
Múltiples transportes
Cuando el mismo contrato puede consumirse por el servicio interno o por API Gateway, repetí GenerateHttpClient con distinto transporte:
using Eternet.Mediator;
[GenerateHttpClient("Default", Transport = HttpClientTransport.Internal)]
[GenerateHttpClient("Default", Transport = HttpClientTransport.Gateway)]
internal abstract class DefaultHttpClient
{
internal abstract class GetProductHandler : GetProduct;
}
El generator produce:
services.AddDefaultHttpClient(...)services.AddDefaultHttpClientInternal(...)services.AddDefaultHttpClientGateway(...)
Registros típicos:
services.AddHttpClientHandlers();
services.AddDefaultHttpClientInternal(
"https://internal-service/",
configure: client =>
{
client.DefaultRequestHeaders.Add("X-Transport", "internal");
},
configureBuilder: builder =>
{
builder.AddHttpMessageHandler<InternalAuthHandler>();
builder.AddPolicyHandler(InternalRetryPolicy.Create());
});
services.AddDefaultHttpClientGateway(
"https://api-gateway/",
configure: client =>
{
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "gateway-token");
},
configureBuilder: builder =>
{
builder.AddHttpMessageHandler<GatewayAuthHandler>();
});
La sobrecarga configureBuilder existe para que el helper generado no limite la configuración del IHttpClientBuilder.
Proyecciones de API Gateway
ApiGatewayEndpointAttribute vive en Eternet.Models.Attributes y modela la proyección publicada por gateway:
using Eternet.Mediator;
using Eternet.Mediator.Abstractions.Handlers;
using Eternet.Mediator.Attributes.Mvc;
using Eternet.Models;
using Eternet.Models.Attributes;
[EndpointGroup("Products")]
public abstract class GetProduct
: DomainResultHandlerAsync<GetProduct.Request, GetProduct.Response>
{
public record Request : CreateEntityCommand<Response>
{
[FromRoute] public required int Id { get; init; }
}
public record Response : CreateEntityDomainResult
{
public string Name { get; init; } = string.Empty;
}
[HttpGet("Internal/{id}")]
[ApiGatewayEndpoint(OperationNamespace.Customers, "Public/Products/{id}", HttpMethod = "POST")]
[ApiGatewayEndpoint(OperationNamespace.Billing, "BillingProducts/{id}")]
public abstract override ValueTask<Response> Handle(
Request request,
CancellationToken cancellationToken);
}
Soporte actual:
- transporte
InternaloGateway - namespace publicado en gateway
- route override
- HTTP method override
No soporta todavía transformaciones arbitrarias del body ni rename de parámetros.
Para uploads, los contratos deben usar Eternet.Mediator.Abstractions.Requests.IUploadedFile o UploadedFile en vez de
Microsoft.AspNetCore.Http.IFormFile. El cliente generado envia requests con archivos [FromForm] como
MultipartFormDataContent: los escalares se agregan como StringContent y los archivos portables como StreamContent.
Los formularios sin archivos siguen usando FormUrlEncodedContent. IFormFile sigue funcionando como compatibilidad legacy,
pero el analyzer EM015 recomienda migrar a IUploadedFile.
Los atributos de binding de Eternet.Mediator.Attributes.Mvc pueden declararse por propiedad o, para requests homogeneos,
en la clase Request: [FromRoute], [FromQuery], [FromHeader], [FromBody] y [FromForm]. Cuando una propiedad declara
su propio atributo, esa propiedad conserva su binding explicito.
Selección en runtime
Para el path por defecto seguí usando IGetResponse<TResponse>:
public sealed class InternalProductsViewModel(IGetResponse<GetProduct.Response> response)
{
public ValueTask<GetProduct.Response> LoadAsync(GetProduct.Request request, CancellationToken ct)
=> response.Get(request, ct);
}
Para elegir gateway en runtime usá IGetResponseFactory:
public sealed class GatewayProductsViewModel(
IGetResponseFactory responseFactory)
{
public ValueTask<GetProduct.Response> LoadCustomersAsync(
GetProduct.Request request,
CancellationToken ct)
{
return responseFactory
.Gateway<GetProduct.Response>(OperationNamespace.Customers)
.Get(request, ct);
}
public ValueTask<GetProduct.Response> LoadBillingAsync(
GetProduct.Request request,
CancellationToken ct)
{
return responseFactory
.Gateway<GetProduct.Response>(OperationNamespace.Billing)
.Get(request, ct);
}
public ValueTask<GetProduct.Response> LoadInternalAsync(
GetProduct.Request request,
CancellationToken ct)
{
return responseFactory
.Internal<GetProduct.Response>()
.Get(request, ct);
}
}
Con eso:
IGetResponse<TResponse>queda para el flujo defaultIGetResponseFactorypermite elegir el transporte y el namespace de gateway en runtime- el handler generado resuelve
HttpClient, ruta y verbo a partir del contexto y los atributos
Base address dinámico por request
Cuando un host necesita resolver el endpoint real en runtime, por ejemplo un control-plane que llama a workers movidos por Service Fabric, puede mantener el cliente generado y sólo reemplazar la base address para esa llamada:
public sealed class WorkerProxy(
IGetResponseFactory responseFactory,
IWorkerEndpointResolver endpointResolver)
{
public async ValueTask<GetWorkerStatus.Response> GetStatusAsync(
string workerName,
GetWorkerStatus.Request request,
CancellationToken ct)
{
var workerUrl = await endpointResolver.ResolveAsync(workerName, ct);
return await responseFactory
.Internal<GetWorkerStatus.Response>(new Uri(workerUrl, UriKind.Absolute))
.Get(request, ct);
}
}
El override debe ser un Uri absoluto. El handler generado sigue usando el
HttpClient nombrado, por lo que conserva headers default, auth handlers,
policies, logging y el primary handler configurado por AddHttpClient(...).
El override sólo cambia la base address de esa request; no cambia el route
template, verbo HTTP, transporte ni payload mapping generados.
Qué genera el package
Runtime liviano habilitado
Cuando GenerateMediatorRuntime queda en su valor por defecto (true), el package genera:
- runtime liviano de
Mediator services.AddMediator()- registro de handlers locales
IRequestHandler<,> - wrappers para ejecutar
IPipelineBehavior<,> - handlers HTTP e infraestructura de
IGetResponseFactory
La resolución default queda así:
- handler local
IRequestHandler<,> - handler HTTP generado
Runtime liviano deshabilitado
Cuando el proyecto define:
[assembly: Mediator.MediatorClientOptionsAttribute(GenerateMediatorRuntime = false)]
el package genera sólo la parte HTTP:
- handlers HTTP
services.AddHttpClientHandlers()- registros
IRequestHandler<,>para que el runtime principal del host pueda resolver esos contratos IGetResponseFactoryGeneratedContextualGetResponse<T>
En ese modo no genera:
services.AddMediator()Mediator- el runtime liviano que hace ownership de
IMediator
En otras palabras: con GenerateMediatorRuntime = false, el cliente no intenta “ganar” IMediator; sólo aporta transporte.
Si el host quiere inyectar IGetResponse<TResponse> en UI o servicios propios, debe registrar los request senders una sola vez
desde su runtime compartido.
Transporte HTTP generado
Para contratos con múltiples transportes o proyecciones de gateway, el handler generado:
- resuelve el
HttpClientreal a usar - proyecta la ruta final (
InternaloGateway/{namespace}/...) - proyecta el verbo final (
GET,POST, etc.) - mantiene un único
RequestClassHandlerWrapper<TRequest, TResponse>
Esto evita duplicar registros de Mediator para el mismo request.
Referencia rápida
GenerateHttpClientAttribute: nombre lógico del cliente y transporteHttpClientTransport:Internal,Gateway- los tipos contenidos por
GenerateHttpClientAttributeson descriptores outbound en el assembly actual - los descriptores outbound no repiten
GenerateEndpoint; esa metadata viene deEndpointGroupy del contrato publicado por el endpoint manifest ApiGatewayEndpointAttribute: namespace, route yHttpMethodMediatorClientOptionsAttribute.GenerateMediatorRuntime: switch de ownership del runtime (true= cliente puro,false= host/mixed app)IGetResponse<TResponse>: consumo default víaIMediatorIGetResponseFactory: selección explícita de transporte en runtime
Ver también
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.1.28 | 0 | 5/8/2026 |
| 2.1.27 | 0 | 5/7/2026 |
| 2.1.26 | 50 | 5/7/2026 |
| 2.1.25 | 51 | 5/6/2026 |
| 2.1.24 | 58 | 5/5/2026 |
| 2.1.23 | 62 | 5/4/2026 |
| 2.1.22 | 64 | 5/4/2026 |
| 2.1.21 | 67 | 5/2/2026 |
| 2.1.20 | 74 | 5/1/2026 |
| 2.1.19 | 60 | 5/1/2026 |
| 2.1.18 | 63 | 5/1/2026 |
| 2.1.17 | 63 | 5/1/2026 |
| 2.1.16 | 60 | 5/1/2026 |
| 2.1.15 | 74 | 4/30/2026 |
| 2.1.14 | 73 | 4/30/2026 |
| 2.1.13 | 89 | 4/30/2026 |
| 2.1.12 | 87 | 4/29/2026 |
| 2.1.11 | 109 | 4/29/2026 |
| 2.1.10 | 89 | 4/23/2026 |
| 2.1.9 | 91 | 4/13/2026 |