AstrolUI AstrolUI
Amarillo Naranja Rojo Verde Azul Morado Color propio
Modo claro
Repository

Astrol.Shared.Repository envuelve HttpClient para mover la información entre su cliente Blazor y su API: resuelve las URL contra la dirección de la API, envía y lee JSON, sube archivos, añade el idioma y el token, y devuelve siempre un HttpResponseWrapper<T>. No hace falta try/catch: los errores HTTP, los fallos de red y los timeouts llegan en el propio resultado.

1. Proceso de instalación

Instale el paquete y regístrelo en Program.cs después del HttpClient. La dirección puede ser fija o salir de ClientSetting (producción o desarrollo). Las URL relativas ("api/clients" o "/api/clients") se resuelven contra esa dirección; si la dirección tiene ruta, termínela en / (https://host/api/). También se aceptan URL absolutas http/https.

dotnet add package Astrol.Shared.Repository
// Program.cs
using Astrol.Shared.Repository;
 
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
 
// a) Fixed API URL
builder.Services.AddAstrolRepository("https://api.my-app.com/");
 
// b) From appsettings.json (wwwroot/appsettings.json in WebAssembly)
var clientSetting = builder.Configuration.GetSection("ClientSetting").Get<ClientSetting>()!;
builder.Services.AddAstrolRepository(clientSetting, options =>
{
 options.DefaultLanguage = null; // null = app language (CultureInfo.CurrentUICulture)
 options.ThrowExceptions = false; // network errors in the wrapper, no exceptions
 options.UseJsonContext(AppJsonContext.Default); // source-generated JSON (section 6)
});
// wwwroot/appsettings.json
{
 "ClientSetting": {
 "EnvironmentProduction": false,
 "Address": {
 "Development": "https://localhost:7001/",
 "Production": "https://api.my-app.com/"
 }
 }
}

2. Demo IRepository

Nota:
Esta documentación registra el repositorio con su propia dirección. Los botones leen un JSON del sitio, piden un archivo que no existe (404) y llaman a un servidor apagado (fallo de red): ninguno lanza una excepción.
@using Astrol.Shared.Repository
@inject IRepository Repository
 
<AstrolButton Text="Read JSON" OnClick='() => Load("sample-data/clients.json")' />
<AstrolButton Text="Error 404" OnClick='() => Load("sample-data/missing.json")' />
<AstrolButton Text="No connection" OnClick='() => Load("http://localhost:9/api/clients")' />
 
@if (_result is not null)
{
 <div>IsSuccess: @_result.IsSuccess</div>
 <div>StatusCode: @_result.StatusCode</div>
 <div>IsNetworkError: @_result.IsNetworkError</div>
 <div>ErrorMessage: @_result.ErrorMessage</div>
}
@code {
 private HttpResponseWrapper<List<ClientDto>>? _result;
 
 private async Task Load(string url) {
 _result = await Repository.Get<List<ClientDto>>(url);
 }
 
 public class ClientDto {
 public int Id { get; set; }
 public string? FullName { get; set; }
 public decimal Points { get; set; }
 }
}

3. Uso y manejo de errores

Inyecte IRepository y compruebe IsSuccess (o Error). Si falló, ErrorMessage ya trae el mensaje listo para mostrar, StatusCode el estado HTTP (null si no hubo respuesta) e IsNetworkError indica si el servidor no respondió.

@inject IRepository Repository
@inject IAstrolToast Toast
 
// GET: list, object, text (Get<string>) or file (Get<byte[]>)
var clients = await Repository.Get<List<ClientDto>>("api/clients?page=1");
if (clients.IsSuccess) _clients = clients.Response;
else Toast.Error(clients.ErrorMessage!);
 
// POST / PUT / PATCH without and with a response
var saved = await Repository.Post("api/clients", newClient);
var created = await Repository.Post<ClientDto, ClientDto>("api/clients", newClient);
var updated = await Repository.Put<ClientDto, ClientDto>($"api/clients/{id}", client);
var patched = await Repository.Patch($"api/clients/{id}", new { FullName = "Ana" });
 
// DELETE
var deleted = await Repository.Delete($"api/clients/{id}");
 
// Telling errors apart
if (deleted.IsNetworkError) Toast.Warning("No connection to the server.");
else if (deleted.StatusCode == HttpStatusCode.Unauthorized) Navigation.NavigateTo("/login");
else if (deleted.Error) Toast.Error(deleted.ErrorMessage!);
 
// Language and cancellation (e.g. when leaving the page)
var result = await Repository.Get<ClientDto>("api/clients/1", "en", _cts.Token);
 
// Any other content or method
var csv = await Repository.Send<string>(HttpMethod.Post, "api/report", new StringContent("a;b"));

4. Subir archivos

Post<TResponse>(url, files) y Put<TResponse>(url, files) envían una lista de IBrowserFile (por ejemplo la de AstrolUploadFile) como multipart/form-data. Con fieldName cambia el nombre del campo y con fields añade campos de texto.

<AstrolUploadFile Status="@_statusUpload" Save="SaveFiles" OnClose="s => _statusUpload = s" />
@code {
 private bool _statusUpload;
 
 private async Task SaveFiles(List<IBrowserFile> files) {
 var result = await Repository.Post<List<string>>("api/documents", files,
 fieldName: "files", // form field name (default "files")
 fields: new Dictionary<string, string> { ["folder"] = "invoices" }); // extra text fields
 
 if (result.Error) throw new Exception(result.ErrorMessage); // AstrolUploadFile shows it as an error
 _statusUpload = false;
 }
}
// API (ASP.NET Core)
[HttpPost("api/documents")]
public async Task<List<string>> Upload([FromForm] List<IFormFile> files, [FromForm] string folder) { ... }

5. Token de autenticación

Registre una implementación de IRepositoryTokenProvider y cada petición llevará Authorization: Bearer {token}. No modifique HttpClient.DefaultRequestHeaders: el HttpClient se comparte en toda la aplicación.

public class TokenProvider : IRepositoryTokenProvider
{
 private readonly IJSRuntime _js;
 public TokenProvider(IJSRuntime js) => _js = js;
 
 // null or empty = the request is sent without a token
 public async ValueTask<string?> GetTokenAsync(CancellationToken cancellationToken = default)
 => await _js.InvokeAsync<string?>("localStorage.getItem", cancellationToken, "token");
}
 
// Program.cs
builder.Services.AddScoped<IRepositoryTokenProvider, TokenProvider>();
builder.Services.AddAstrolRepository(clientSetting);

6. Rendimiento: JSON generado en compilación

Por defecto el JSON se lee y escribe con reflexión: en WebAssembly, la primera vez que se usa cada tipo hay que analizarlo en el navegador, lo que retrasa la primera carga de cada pantalla, y la reflexión impide que el trimming reduzca el tamaño de la aplicación. Con UseJsonContext los tipos que declare en un JsonSerializerContext usan código generado al compilar: sin reflexión y más rápido desde la primera llamada.

  • No cambia ninguna llamada ni el JSON: los nombres siguen la política de RepositoryOptions.JsonSerializerOptions (camelCase), no los JsonSourceGenerationOptions del contexto.
  • Los tipos que no estén en el contexto (p. ej. tipos anónimos en Patch) siguen funcionando con reflexión, salvo que la aplicación la desactive (JsonSerializerIsReflectionEnabledByDefault=false).
  • Declare los tipos en archivos .cs, no dentro de un .razor (@code): el generador de JSON no ve las clases declaradas en componentes.
  • Esta documentación lo usa: la demo de arriba lee List<ClientDto> con código generado.
using System.Text.Json.Serialization;
 
// One class per project: list every type you send or read with IRepository
// (also the collections: List<ClientDto>, ClientDto[] ...).
[JsonSerializable(typeof(ClientDto))]
[JsonSerializable(typeof(List<ClientDto>))]
[JsonSerializable(typeof(UserToken))]
internal partial class AppJsonContext : JsonSerializerContext
{
}
 
// Program.cs
builder.Services.AddAstrolRepository(clientSetting, options => options.UseJsonContext(AppJsonContext.Default));

Parámetros que admite

IRepository (todos los métodos aceptan language y cancellationToken opcionales):

  • Get<T>(url): lee JSON; con T = string devuelve el texto y con T = byte[] los bytes (descargas).
  • Post<T>(url, data), Put<T>, Patch<T>: envían data como JSON sin leer la respuesta.
  • Post<T, TResponse>(url, data), Put<T, TResponse>, Patch<T, TResponse>: envían JSON y leen la respuesta.
  • Delete(url) y Delete<T>(url).
  • Post<TResponse>(url, files, fieldName = "files", fields = null) y Put<TResponse>(...): archivos como multipart/form-data.
  • Send<TResponse>(method, url, content): cualquier método y cualquier HttpContent.
  • language (string): cabecera Accept-Language; null (por defecto) usa el idioma de la aplicación.
  • cancellationToken: cancela la petición; la cancelación sí se lanza como OperationCanceledException.

HttpResponseWrapper<T>:

  • IsSuccess / Error (bool): resultado de la petición.
  • Response (T): contenido leído; default si hubo error o la respuesta estaba vacía (204).
  • StatusCode (HttpStatusCode?): estado HTTP; null si no hubo respuesta.
  • ErrorMessage (string): detail o title de un ProblemDetails, los mensajes de errors de validación (uno por línea), message/error de un JSON, el texto del cuerpo o el estado HTTP.
  • IsNetworkError (bool): no se pudo contactar con el servidor (sin conexión, DNS, CORS o timeout).
  • Exception (Exception): excepción capturada (red, timeout o JSON inválido).
  • HttpResponseMessage y GetBody(): la respuesta original y su cuerpo como texto (se pueden leer varias veces). En un fallo de red es una respuesta 503 (408 en un timeout).

RepositoryOptions (AddAstrolRepository(url, options => ...)):

  • JsonSerializerOptions: por defecto JsonSerializerDefaults.Web (camelCase, sin distinguir mayúsculas). Asigne TypeInfoResolver para usar JSON generado en compilación (más rápido y ligero en WebAssembly).
  • UseJsonContext(contexto): usa JSON generado en compilación para los tipos del contexto (ver sección 6).
  • DefaultLanguage (string): idioma cuando la llamada no indica uno (por defecto null = cultura actual).
  • ThrowExceptions (bool): true lanza los fallos de red, timeouts y JSON inválido como antes (por defecto false).
  • AuthorizationScheme (string): esquema del token (por defecto Bearer).
  • ConfigureRequest (Action<HttpRequestMessage>): para añadir cabeceras propias a cada petición.
Markdown para IA © 2026 AstrolUI v10.0.13