Rystem.RepositoryFramework.Api.Client
10.0.7
dotnet add package Rystem.RepositoryFramework.Api.Client --version 10.0.7
NuGet\Install-Package Rystem.RepositoryFramework.Api.Client -Version 10.0.7
<PackageReference Include="Rystem.RepositoryFramework.Api.Client" Version="10.0.7" />
<PackageVersion Include="Rystem.RepositoryFramework.Api.Client" Version="10.0.7" />
<PackageReference Include="Rystem.RepositoryFramework.Api.Client" />
paket add Rystem.RepositoryFramework.Api.Client --version 10.0.7
#r "nuget: Rystem.RepositoryFramework.Api.Client, 10.0.7"
#:package Rystem.RepositoryFramework.Api.Client@10.0.7
#addin nuget:?package=Rystem.RepositoryFramework.Api.Client&version=10.0.7
#tool nuget:?package=Rystem.RepositoryFramework.Api.Client&version=10.0.7
What is Rystem?
Rystem.RepositoryFramework.Api.Client
.NET client adapter for APIs generated by Rystem.RepositoryFramework.Api.Server.
It lets you inject IRepository<T, TKey>, IQuery<T, TKey>, or ICommand<T, TKey> locally while the actual work happens over HTTP against a remote repository API.
Resources
- Complete Documentation: https://rystem.net
- MCP Server for AI: https://rystem.cloud/mcp
- Discord Community: https://discord.gg/tkWvy4WPjt
- Support the Project: https://www.buymeacoffee.com/keyserdsoze
Installation
dotnet add package Rystem.RepositoryFramework.Api.Client
The current package metadata in src/Repository/RepositoryFramework.Api.Client/RepositoryFramework.Api.Client.csproj is:
- package id:
Rystem.RepositoryFramework.Api.Client - version:
10.0.6 - target framework:
net10.0 - HTTP packages:
Microsoft.Extensions.Http,Microsoft.Extensions.Http.Polly,Polly
Package architecture
| Area | Purpose |
|---|---|
WithApiClient(...) |
Register repository/query/command consumers backed by HTTP |
RepositoryClient<T, TKey> |
Concrete client implementation that translates repository calls to HTTP requests |
IApiRepositoryBuilder<T, TKey> |
Configure base address, path, version, server name, and server factory name |
HttpClientRepositoryBuilder<T, TKey> |
Access IHttpClientBuilder and the built-in policy helper |
| Request interceptors | Enrich outgoing HttpClient instances |
| Response interceptors | Inspect responses and optionally retry requests |
| Bearer authorization helpers | Provide token-based request enrichment and optional 401 retry flow |
Mental model
This package is not a general REST client generator. It is a client for the wire format produced by RepositoryFramework.Api.Server.
That means it expects the server to expose routes and payload shapes like:
Get,Exist,Deleteendpoints following repository conventionsQuery/StreamandBatch/StreamendpointsSerializableFilter,BatchOperations<T, TKey>,State<T, TKey>, andEntity<T, TKey>payloads
If the server follows the matching Rystem API-server conventions, the client can make the remote repository feel local.
Quick start
builder.Services.AddRepository<Product, int>(repositoryBuilder =>
{
repositoryBuilder.WithApiClient(apiBuilder =>
{
apiBuilder
.WithHttpClient("https://api.example.com")
.ApiBuilder
.WithStartingPath("api")
.WithVersion("v1");
});
});
public sealed class ProductService
{
private readonly IRepository<Product, int> _repository;
public ProductService(IRepository<Product, int> repository)
=> _repository = repository;
public Task<Product?> GetAsync(int id)
=> _repository.GetAsync(id);
}
Registration patterns
WithApiClient(...) is available on repository, query, and command builders.
Full repository
builder.Services.AddRepository<Product, int>(repositoryBuilder =>
{
repositoryBuilder.WithApiClient(apiBuilder =>
{
apiBuilder.WithHttpClient("https://api.example.com");
});
});
Query only
builder.Services.AddQuery<Product, int>(queryBuilder =>
{
queryBuilder.WithApiClient(apiBuilder =>
{
apiBuilder.WithHttpClient("https://api.example.com");
});
});
Command only
builder.Services.AddCommand<Product, int>(commandBuilder =>
{
commandBuilder.WithApiClient(apiBuilder =>
{
apiBuilder.WithHttpClient("https://api.example.com");
});
});
WithApiClient(...) itself defaults to ServiceLifetime.Scoped.
IApiRepositoryBuilder<T, TKey>
| Method | Purpose |
|---|---|
WithHttpClient(string domain) |
Register a named HttpClient; adds https:// automatically when missing |
WithHttpClient(Action<HttpClient>) |
Configure the HttpClient directly |
WithStartingPath(string) |
Override the base path, default api |
WithVersion(string) |
Add a version segment |
WithName(string) |
Override the remote model segment |
WithServerFactoryName(string) |
Target a named repository registration on the server |
WithHttpClient(string domain)
apiBuilder.WithHttpClient("localhost:7058");
If the string does not start with http:// or https://, the builder prepends https://.
WithHttpClient(Action<HttpClient>)
Use this overload when you want full control.
apiBuilder.WithHttpClient(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.Timeout = TimeSpan.FromSeconds(30);
});
Route building
ApiClientSettings<T, TKey> builds the final paths from:
StartingPath, defaultapi- optional
Version - remote
Name, defaulttypeof(T).Name - optional remote
FactoryName
The effective base route is:
{startingPath}/{version?}/{name}/{serverFactoryName?}
Examples:
api/Product/Get?key=42api/v2/Product/Get?key=42SuperApi/v2/calamityuser/Get?key=abc@example.comapi/Product/premium/Get?key=42
Named segments and factory segments are different concepts
WithName(...)changes the remote model segmentWithServerFactoryName(...)adds the remote server factory segment- the
nameparameter onWithApiClient(..., name: "local")is only the local DI factory name for your client registration
So a local named client and a remote named server repository are separate configuration knobs.
Request shapes used by the client
The client mirrors the server conventions from RepositoryFramework.Api.Server.
Non-JSON keys
For keys that are not treated as JSONable by KeySettings<TKey>:
GetAsync,ExistAsync,DeleteAsyncuseGET ...?key=...InsertAsync,UpdateAsyncusePOST ...?key=...with bodyT
JSONable keys
For keys treated as JSONable:
GetAsync,ExistAsync,DeleteAsyncsendPOSTwith bodyTKeyInsertAsync,UpdateAsyncsendPOSTwith bodyEntity<T, TKey>
Query, batch, and operation
QueryAsync(...)posts a serialized filter to.../Query/StreamBatchAsync(...)postsBatchOperations<T, TKey>to.../Batch/StreamOperationAsync(...)posts a serialized filter to.../Operation?op=...&returnType=...
The client intentionally uses the streaming endpoints for query and batch operations, even when the caller later materializes the results into a list.
What RepositoryClient<T, TKey> actually does
The concrete client implementation maps local repository calls to HTTP like this:
GetAsync→ GET or POST depending on key serializationExistAsync→ GET or POST depending on key serializationInsertAsync→ POSTUpdateAsync→ POSTDeleteAsync→ GET or POST depending on key serializationQueryAsync→ POST toQuery/StreamandDeserializeAsyncEnumerable<Entity<T, TKey>>BatchAsync→ POST toBatch/StreamandDeserializeAsyncEnumerable<BatchResult<T, TKey>>OperationAsync→ POST toOperation
The client expects 200 OK for success. Any other status code becomes an HttpRequestException containing the raw response body.
That is one reason this package works best against the matching Rystem API server rather than arbitrary REST endpoints.
Built-in HTTP policy helper
WithHttpClient(...) returns HttpClientRepositoryBuilder<T, TKey>.
repositoryBuilder.WithApiClient(apiBuilder =>
{
apiBuilder
.WithHttpClient("https://api.example.com")
.WithDefaultRetryPolicy();
});
Important note about WithDefaultRetryPolicy()
Despite the method name, the built-in policy is not a retry policy.
It registers an advanced circuit breaker with these settings:
- failure threshold:
50% - sampling duration:
10seconds - minimum throughput:
10 - break duration:
15seconds
If you want retries, attach your own policy through ClientBuilder.
var retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.RetryAsync(3);
repositoryBuilder.WithApiClient(apiBuilder =>
{
apiBuilder
.WithHttpClient("https://api.example.com")
.ClientBuilder
.AddPolicyHandler(retryPolicy);
});
Request interceptors
Request interceptors let you mutate the HttpClient before the repository client starts using it.
Registration options:
services.AddApiClientInterceptor<MyGlobalInterceptor>();
services.AddApiClientSpecificInterceptor<Product, MyProductInterceptor>();
services.AddApiClientSpecificInterceptor<Product, int, MyProductKeyInterceptor>();
Interfaces:
public interface IRepositoryClientInterceptor
{
Task<HttpClient> EnrichAsync(HttpClient client, RepositoryMethods path);
}
public interface IRepositoryClientInterceptor<T> : IRepositoryClientInterceptor { }
public interface IRepositoryClientInterceptor<T, TKey> : IRepositoryClientInterceptor { }
Important lifecycle nuance
In the current implementation, request interceptors do not run on every repository call.
They run the first time a given RepositoryClient<T, TKey> instance enriches its HttpClient, then that client is reused for the rest of the repository-client lifetime.
So request interceptors are a good fit for:
- default auth headers
- sticky headers
- long-lived client setup
They are not a good fit for truly per-request values such as a fresh correlation ID on every repository method call.
The execution order is:
- global interceptors
- model-specific interceptors
- model-plus-key-specific interceptors
Response interceptors
Response interceptors run after the HTTP call returns and before the client enforces the final 200 OK success check.
Registration options:
services.AddApiClientResponseInterceptor<MyGlobalResponseInterceptor>();
services.AddApiClientResponseInterceptor<Product, MyProductResponseInterceptor>();
services.AddApiClientResponseInterceptor<Product, int, MyProductKeyResponseInterceptor>();
Interfaces:
public interface IRepositoryResponseClientInterceptor
{
Task<HttpResponseMessage> CheckResponseAsync(
HttpClient client,
HttpResponseMessage response,
Func<HttpClient, Task<HttpResponseMessage>> request);
}
public interface IRepositoryClientResponseInterceptor<T> : IRepositoryResponseClientInterceptor { }
public interface IRepositoryResponseClientInterceptor<T, TKey> : IRepositoryResponseClientInterceptor
where TKey : notnull { }
Typical use cases:
- retry after refreshing a token
- logging failed responses
- translating selected server failures before the client throws
The same ordering applies here: global, then model-specific, then model-plus-key-specific.
Bearer token helpers
The package includes a default bearer authenticator based on ITokenManager.
Implement ITokenManager
public sealed class MyTokenManager : ITokenManager
{
public Task<string?> GetTokenAsync()
{
throw new NotImplementedException();
}
public Task<string?> RefreshTokenAsync()
{
throw new NotImplementedException();
}
public async Task EnrichWithAuthorizationAsync(HttpClient client)
{
var token = await GetTokenAsync();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
}
}
Register globally
services.AddDefaultAuthorizationInterceptorForApiHttpClient<MyTokenManager>();
Register for one model
services.AddDefaultAuthorizationInterceptorForApiHttpClient<Product, MyTokenManager>();
Register for one model and key
services.AddDefaultAuthorizationInterceptorForApiHttpClient<Product, int, MyTokenManager>();
Authenticator settings
services.AddDefaultAuthorizationInterceptorForApiHttpClient<MyTokenManager>(settings =>
{
settings.Scopes = ["api.read", "api.write"];
settings.ExceptionHandler = async (exception, serviceProvider) =>
{
await Task.CompletedTask;
};
});
Important behavior notes
- global bearer registration adds both a request interceptor and a response interceptor
- model-specific bearer registration also adds both request and response interceptors
- model-plus-key-specific bearer registration currently adds only the request interceptor, not the response interceptor
So automatic 401 Unauthorized refresh-and-retry is available in the global and model-specific registrations, but not fully wired in the model-plus-key-specific registration path.
Also note that AuthenticatorSettings.Scopes exists in the core client package but is not consumed by the core bearer interceptor itself. It becomes meaningful in the companion authentication packages that provide concrete token managers.
Bootstrap note
RepositoryClient<T, TKey> exposes BootstrapAsync() because the repository abstraction includes it.
However, the current client implementation simply performs:
GET {basePath}/Bootstrap
without sending a key.
The stock API-server bootstrap endpoint is currently wired through the key-binding path, so bootstrap interoperability is less polished than the rest of the client surface. In practice, prefer server-side WarmUpAsync() for startup bootstrap flows.
Practical examples from the repo
Point the client at a custom server path and version
The API tests register clients like this:
settings.WithApiClient(apiBuilder =>
{
apiBuilder
.WithVersion("v2")
.WithStartingPath("SuperApi");
});
Match a renamed server route segment
The tests also use the client-side name override to match a server-side WithName<T>(...) route customization:
settings.WithApiClient(apiBuilder =>
{
apiBuilder
.WithName("calamityuser")
.WithVersion("v2")
.WithStartingPath("SuperApi");
});
Attach a custom Polly policy
The web client sample configures a retry policy through ClientBuilder:
var retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.Or<TimeoutRejectedException>()
.RetryAsync(3);
settings.WithApiClient(apiBuilder =>
{
apiBuilder
.WithHttpClient("localhost:7058")
.ClientBuilder
.AddPolicyHandler(retryPolicy);
});
Global bearer integration
The web client sample also registers the built-in authorization interceptor globally:
builder.Services.AddDefaultAuthorizationInterceptorForApiHttpClient(settings =>
{
settings.Scopes = builder.Configuration["AzureAd:Scopes"]!.Split(' ');
});
Related packages
| Package | Purpose |
|---|---|
Rystem.RepositoryFramework.Abstractions |
Core repository contracts and DI registration |
Rystem.RepositoryFramework.Api.Server |
The server package this client is designed to call |
Rystem.RepositoryFramework.Api.Client.Authentication.BlazorServer |
Token-manager helpers for Blazor Server |
Rystem.RepositoryFramework.Api.Client.Authentication.BlazorWasm |
Token-manager helpers for Blazor WebAssembly |
If you are continuing through the repository area, this is the package to read right after src/Repository/RepositoryFramework.Api.Server/README.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Microsoft.Extensions.Http (>= 10.0.5)
- Microsoft.Extensions.Http.Polly (>= 10.0.5)
- Polly (>= 8.6.6)
- Rystem.RepositoryFramework.Abstractions (>= 10.0.7)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Rystem.RepositoryFramework.Api.Client:
| Package | Downloads |
|---|---|
|
Rystem.RepositoryFramework.Api.Client.Authentication.BlazorServer
Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test. |
|
|
Rystem.RepositoryFramework.Api.Client.Authentication.BlazorWasm
Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.7 | 84 | 3/26/2026 |
| 10.0.6 | 167,553 | 3/3/2026 |
| 10.0.5 | 187 | 2/22/2026 |
| 10.0.4 | 205 | 2/9/2026 |
| 10.0.3 | 147,968 | 1/28/2026 |
| 10.0.1 | 209,150 | 11/12/2025 |
| 9.1.3 | 376 | 9/2/2025 |
| 9.1.2 | 764,638 | 5/29/2025 |
| 9.1.1 | 97,903 | 5/2/2025 |
| 9.0.32 | 186,694 | 4/15/2025 |
| 9.0.31 | 5,928 | 4/2/2025 |
| 9.0.30 | 88,899 | 3/26/2025 |
| 9.0.29 | 9,101 | 3/18/2025 |
| 9.0.28 | 289 | 3/17/2025 |
| 9.0.27 | 285 | 3/16/2025 |
| 9.0.26 | 321 | 3/13/2025 |
| 9.0.25 | 52,171 | 3/9/2025 |
| 9.0.21 | 435 | 3/6/2025 |
| 9.0.20 | 19,665 | 3/6/2025 |
| 9.0.19 | 354 | 3/6/2025 |