# Repository > Astrol.Shared.Repository is the AstrolUI HTTP client for Blazor: Get, Post, Put, Patch, Delete and file uploads with a response that always tells you whether it failed, the HTTP status and the server message. Source page: https://doc.astrolui.com/repository **Astrol.Shared.Repository** wraps **HttpClient** to move data between your Blazor client and your API: it resolves URLs against the API address, sends and reads JSON, uploads files, adds the language and the token, and always returns an **HttpResponseWrapper**. No **try/catch** needed: HTTP errors, network failures and timeouts come back in the result itself. ## 1. Installation process Install the package and register it in **Program.cs** after the **HttpClient**. The address can be fixed or come from **ClientSetting** (production or development). Relative URLs (**"api/clients"** or **"/api/clients"**) are resolved against that address; if the address has a path, end it with **/** (**https://host/api/**). Absolute **http/https** URLs are accepted too. ```html dotnet add package Astrol.Shared.Repository ``` ```csharp // 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()!; 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) }); ``` ```html // wwwroot/appsettings.json { "ClientSetting": { "EnvironmentProduction": false, "Address": { "Development": "https://localhost:7001/", "Production": "https://api.my-app.com/" } } } ``` ## 2. Demo IRepository This documentation registers the repository with its own address. The buttons read a JSON file from the site, request a file that does not exist (404) and call a server that is down (network failure): none of them throws an exception. ```razor @using Astrol.Shared.Repository @inject IRepository Repository @if (_result is not null) {
IsSuccess: @_result.IsSuccess
StatusCode: @_result.StatusCode
IsNetworkError: @_result.IsNetworkError
ErrorMessage: @_result.ErrorMessage
} @code { ``` ```csharp private HttpResponseWrapper>? _result; private async Task Load(string url) { _result = await Repository.Get>(url); } public class ClientDto { public int Id { get; set; } public string? FullName { get; set; } public decimal Points { get; set; } } } ``` ## 3. Usage and error handling Inject **IRepository** and check **IsSuccess** (or **Error**). When it failed, **ErrorMessage** already has a message ready to show, **StatusCode** the HTTP status (**null** when there was no response) and **IsNetworkError** tells whether the server did not answer. ```csharp @inject IRepository Repository @inject IAstrolToast Toast // GET: list, object, text (Get) or file (Get) var clients = await Repository.Get>("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("api/clients", newClient); var updated = await Repository.Put($"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("api/clients/1", "en", _cts.Token); // Any other content or method var csv = await Repository.Send(HttpMethod.Post, "api/report", new StringContent("a;b")); ``` ## 4. Uploading files **Post(url, files)** and **Put(url, files)** send a list of **IBrowserFile** (for example the one from [AstrolUploadFile](/basic-component/upload-file)) as **multipart/form-data**. Use **fieldName** to change the field name and **fields** to add text fields. ```razor @code { ``` ```csharp private bool _statusUpload; private async Task SaveFiles(List files) { var result = await Repository.Post>("api/documents", files, fieldName: "files", // form field name (default "files") fields: new Dictionary { ["folder"] = "invoices" }); // extra text fields if (result.Error) throw new Exception(result.ErrorMessage); // AstrolUploadFile shows it as an error _statusUpload = false; } } ``` ```csharp // API (ASP.NET Core) [HttpPost("api/documents")] public async Task> Upload([FromForm] List files, [FromForm] string folder) { ... } ``` ## 5. Authentication token Register an implementation of **IRepositoryTokenProvider** and every request will carry **Authorization: Bearer {token}**. Do not modify **HttpClient.DefaultRequestHeaders**: the **HttpClient** is shared by the whole app. ```csharp 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 GetTokenAsync(CancellationToken cancellationToken = default) => await _js.InvokeAsync("localStorage.getItem", cancellationToken, "token"); } // Program.cs builder.Services.AddScoped(); builder.Services.AddAstrolRepository(clientSetting); ``` ## 6. Performance: source-generated JSON By default JSON is read and written with **reflection**: in WebAssembly each type has to be analyzed in the browser the first time it is used, which delays the first load of each screen, and reflection prevents **trimming** from shrinking the app. With **UseJsonContext**, the types you declare in a **JsonSerializerContext** use code generated at compile time: no reflection and faster from the first call. - No call and no JSON changes: names follow the policy of **RepositoryOptions.JsonSerializerOptions** (camelCase), not the context's **JsonSourceGenerationOptions**. - Types that are not in the context (e.g. anonymous types in **Patch**) keep working with reflection, unless the app turns it off (**JsonSerializerIsReflectionEnabledByDefault=false**). - Declare the types in **.cs** files, not inside a **.razor** (**@code**): the JSON generator cannot see classes declared in components. - This documentation uses it: the demo above reads **List** with generated code. ```csharp using System.Text.Json.Serialization; // One class per project: list every type you send or read with IRepository // (also the collections: List, ClientDto[] ...). [JsonSerializable(typeof(ClientDto))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(UserToken))] internal partial class AppJsonContext : JsonSerializerContext { } // Program.cs builder.Services.AddAstrolRepository(clientSetting, options => options.UseJsonContext(AppJsonContext.Default)); ``` ### Supported parameters **IRepository** (every method accepts optional **language** and **cancellationToken**): - **Get(url)**: reads JSON; with **T = string** it returns the text and with **T = byte[]** the bytes (downloads). - **Post(url, data)**, **Put**, **Patch**: send **data** as JSON without reading the response. - **Post(url, data)**, **Put**, **Patch**: send JSON and read the response. - **Delete(url)** and **Delete(url)**. - **Post(url, files, fieldName = "files", fields = null)** and **Put(...)**: files as **multipart/form-data**. - **Send(method, url, content)**: any method and any **HttpContent**. - **language** (string): **Accept-Language** header; **null** (default) uses the app language. - **cancellationToken**: cancels the request; cancellation is thrown as **OperationCanceledException**. **HttpResponseWrapper**: - **IsSuccess** / **Error** (bool): result of the request. - **Response** (T): content read; **default** when it failed or the response was empty (204). - **StatusCode** (HttpStatusCode?): HTTP status; **null** when there was no response. - **ErrorMessage** (string): **detail** or **title** of a ProblemDetails, the validation **errors** messages (one per line), **message**/**error** of a JSON, the body text or the HTTP status. - **IsNetworkError** (bool): the server could not be reached (offline, DNS, CORS or timeout). - **Exception** (Exception): captured exception (network, timeout or invalid JSON). - **HttpResponseMessage** and **GetBody()**: the original response and its body as text (can be read several times). On a network failure it is a 503 response (408 on a timeout). **RepositoryOptions** (**AddAstrolRepository(url, options => ...)**): - **JsonSerializerOptions**: **JsonSerializerDefaults.Web** by default (camelCase, case-insensitive). Set **TypeInfoResolver** to use source-generated JSON (faster and lighter in WebAssembly). - **UseJsonContext(context)**: uses source-generated JSON for the context types (see section 6). - **DefaultLanguage** (string): language when the call does not give one (default **null** = current culture). - **ThrowExceptions** (bool): **true** throws network failures, timeouts and invalid JSON as before (default **false**). - **AuthorizationScheme** (string): token scheme (default **Bearer**). - **ConfigureRequest** (Action): to add your own headers to every request.