DS.Foundation.WebApi
8.4.1
dotnet add package DS.Foundation.WebApi --version 8.4.1
NuGet\Install-Package DS.Foundation.WebApi -Version 8.4.1
<PackageReference Include="DS.Foundation.WebApi" Version="8.4.1" />
<PackageVersion Include="DS.Foundation.WebApi" Version="8.4.1" />
<PackageReference Include="DS.Foundation.WebApi" />
paket add DS.Foundation.WebApi --version 8.4.1
#r "nuget: DS.Foundation.WebApi, 8.4.1"
#:package DS.Foundation.WebApi@8.4.1
#addin nuget:?package=DS.Foundation.WebApi&version=8.4.1
#tool nuget:?package=DS.Foundation.WebApi&version=8.4.1
DS.Foundation.WebApi
Cliente HTTP para consumir APIs REST (en especial los microservicios DS), los contratos de request y response que esas APIs comparten, autorizadores (Bearer, OAuth2 por password, login simple), registro de requests en archivos o memoria, subida de archivos multipart y notificaciones a Slack.
- Target framework:
netstandard2.0(compatible con Blazor WebAssembly medianteIHttpClient). - Dependencias:
DS.Foundation,Microsoft.Extensions.Options,System.Text.Json.
Instalación
<PackageReference Include="DS.Foundation.WebApi" Version="8.4.1" />
Registro
Los clientes no se registran en DI: se crean con un contexto (WebApiConsumerContext o WebApiClientContext). Cree un contexto por API destino y reutilícelo, porque cada contexto crea su propio HttpClient.
SlackNotifier implementa IApplicationService, pero el paquete no expone un IServiceRepository, así que regístrelo a mano:
using DS.Foundation.WebApi.Notification;
builder.Services.Configure<SlackOptions>(builder.Configuration.GetSection("Slack"));
builder.Services.AddScoped<ISystemNotifier, SlackNotifier>(); // usa IOptionsSnapshot: debe ser scoped
Configuración
WebApiConsumerContext / WebApiClientContext
| Propiedad | Tipo | Valor por defecto | Obligatoria |
|---|---|---|---|
constructor timeout |
int (segundos) |
360 |
No |
BaseUrl |
string |
"" |
Sí (se concatena con el endpoint, sin agregar /) |
Authorizer |
IWebApiAuthorizer |
null |
Sí si usa Authentication |
Logger |
IWebApiLogger |
NopWebApiLogger |
No |
Parameters |
Dictionary<string,string> |
vacío | No (TENANT-CODE se usa en los logs) |
KeepSendRecords / KeepDataRecords |
bool |
false |
No |
Authentication (solo WebApiClientContext) |
WebApiAuthentication (Username, Password) |
null |
No |
Authorization (solo WebApiClientContext) |
WebApiAuthorization (Key, Type, AccessToken, ExpiresAt) |
null |
No |
TimeProvider |
TimeProvider |
TimeProvider.System |
No (reemplazar en pruebas para controlar el vencimiento de los tokens) |
Con Authorization cargada, cada request lleva ese encabezado: si Type es Bearer, Authorization: Bearer <AccessToken>; si no, <Key>: <AccessToken>. Si hay Authentication, el request llama a Authorizer.Authorize(usuario, contraseña) cuando todavía no hay token o cuando el token vence en menos de 30 segundos (ExpiresAt), y guarda el token nuevo. WebApiOAuth2Authorizer calcula ExpiresAt a partir de expires_in; un token sin ExpiresAt no vence. Si el login falla no se guarda nada y el request sale sin encabezado de autorización; el siguiente request vuelve a intentar el login.
WebApiClientContext.Clone() copia BagContainer en lugar de compartirlo: use un clon por hilo cuando necesite consumir la misma API en paralelo.
SlackOptions
| Propiedad | Tipo | Valor por defecto | Obligatoria |
|---|---|---|---|
WebhookUrl |
string |
"" |
Sí (sin valor, SendMessage devuelve false) |
MaxRetries |
int |
3 |
No |
RetryDelayMilliseconds |
int |
1000 |
No |
TimeoutSeconds |
int |
10 |
No (límite de cada intento) |
SendMessageAsync(mensaje, cancellationToken) no bloquea el hilo; SendMessage es la versión sincrónica. Ambos devuelven false si Slack no responde 200 tras MaxRetries intentos, y como máximo tardan MaxRetries × TimeoutSeconds más las pausas entre intentos.
Uso
Consumir un microservicio DS
using DS.Foundation.WebApi.Authorizer;
using DS.Foundation.WebApi.Client;
using DS.Foundation.WebApi.Security;
using DS.Foundation.WebApi.Surface.Contracts;
var context = new WebApiClientContext(timeout: 60)
{
BaseUrl = "https://ventas.ejemplo.com",
Authentication = new WebApiAuthentication { Username = usuario, Password = contraseña },
};
context.Authorizer = new WebApiBearerAuthorizer(context, "/api/identity/sign-in");
var client = new WebApiClient<ResponseContract<List<PedidoDto>>>(context);
ResponseContract<List<PedidoDto>> respuesta = client.SendAsJson("/api/pedidos", "GET");
if (respuesta.Success) { var pedidos = respuesta.Data; }
WebApiClient<T> es sincrónico y tiene puntos de extensión virtuales (DoBeforeExecute, DoAfterExecute, DoBeforeDeserialization, DoAfterDeserialization, FiltrarDatos, CurarDatos).
Llamada asincrónica
using DS.Foundation.WebApi.Consumer;
var consumer = new WebApiConsumer<ResponseContract<PedidoDto>>(context);
var pedido = await consumer.SendAsJsonAsync(new WebApiRequest("/api/pedidos/1", "GET"), cancellationToken);
WebApiConsumer<T> propaga las excepciones de red. La respuesta se deserializa con System.Text.Json sin opciones: los nombres de propiedad distinguen mayúsculas y minúsculas.
Contratos
BaseResponseContract:Success,Message,Messages(UserMessageContract).ResponseContract<T>agregaData.RequestContract<T>:Datamás datos de la máquina y la sesión (BaseRequestContract).SearchContract(Id,Code,Text,Include*,Filters),ExtractContract(BatchSize,BatchNumber) yErrorContract.response.FillWithAppEvent(appEvent)completaMessageyMessagesdesde unAppEventde DS.Foundation.
Registro de requests
context.Logger = new FileSystemWebApiLogger(@"C:\logs\webapi"); // un JSON por request: <ruta>/<tenant>/<yyyy_MM_dd>/<endpoint>/
MemoryWebApiLogger guarda los eventos en memoria (para depuración) y admite requests concurrentes.
Si el logger lanza una excepción, se ignora: un fallo de registro no hace fallar el request.
Errores frecuentes
| Síntoma | Causa | Solución |
|---|---|---|
ArgumentNullException en WebApiClient<T>.SendAsJson |
El request falló (red, timeout, DNS): WebApiClient registra el error en el logger y devuelve contenido null, que no se puede deserializar |
Revisar el logger del contexto; usar WebApiConsumer<T>.SendAsJsonAsync si necesita la excepción original |
JsonException al deserializar |
La respuesta no es JSON (por ejemplo, una página de error HTML) o no coincide con T |
Verificar el endpoint y el tipo de respuesta |
401 con Authentication cargada |
El login falló: el autorizador devuelve un token vacío sin lanzar excepción y el request sale sin autorización | Verificar las credenciales y el endpoint del autorizador |
InvalidOperationException: ... no Authorizer |
Hay Authentication pero Authorizer es null |
Configurar Authorizer |
| URL mal formada | BaseUrl y el endpoint se concatenan sin separador |
Terminar BaseUrl sin / y comenzar el endpoint con / |
Qué no hacer
- No usar
FileSystemWebApiLoggerniMemoryWebApiLoggeren producción con datos sensibles: guardan encabezados y cuerpos completos, incluido el encabezadoAuthorizationy la contraseña enviada al autorizador. - No reutilizar un
WebApiClientContextdurante mucho tiempo conWebApiClient: cada respuesta se agrega aBagContainery la memoria crece sin límite. - No crear un contexto por request: cada contexto crea un
HttpClientnuevo. - No llamar a los métodos sincrónicos (
SendAsJson,Send,Execute,HttpDataFileUploader.Send) desde código asincrónico de ASP.NET Core: bloquean el hilo. UseSendAsJsonAsync.
Cambios
Ver CHANGELOG.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- DS.Foundation (>= 8.4.1)
- Microsoft.Bcl.TimeProvider (>= 8.0.1)
- Microsoft.Extensions.Options (>= 8.0.2)
- System.Text.Json (>= 8.0.6)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on DS.Foundation.WebApi:
| Package | Downloads |
|---|---|
|
HQS.DataBridge.Foundation
Package Description |
|
|
HQS.DataBridge.ERP.DB.Wrapper.Interfaces
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 8.4.1 | 188 | 10/19/2026 |
| 8.4.0 | 100 | 10/19/2026 |
| 8.3.2 | 141 | 10/8/2026 |
| 8.3.1 | 349 | 5/31/2026 |
| 8.2.6 | 811 | 4/7/2026 |
| 8.2.5 | 324 | 12/23/2025 |
| 8.2.4 | 273 | 10/5/2025 |
| 8.2.3 | 237 | 10/3/2025 |
| 8.2.2 | 362 | 9/19/2025 |
| 8.2.1 | 492 | 8/22/2025 |
| 8.2.0 | 474 | 6/22/2025 |
| 8.1.32 | 264 | 7/15/2025 |
| 8.1.31 | 285 | 7/3/2025 |
| 8.1.30 | 403 | 6/8/2025 |
| 8.1.29 | 264 | 6/6/2025 |
| 8.1.28 | 215 | 6/6/2025 |
| 8.1.27 | 257 | 5/25/2025 |
| 8.1.26 | 323 | 4/15/2025 |
| 8.1.25 | 275 | 4/1/2025 |
| 8.1.24 | 358 | 3/6/2025 |