Skip to content

ASP.NET Core Integration

This page shows how to wire ConvergeDB into an ASP.NET Core application using dependency injection.

Register ConvergenceClient as a singleton, then register each KindHandle<T> as a singleton via factory method:

Program.cs
builder.Services.AddSingleton<ConvergenceClient>(_ =>
ConvergenceClient.ConnectAsync(new ConvergenceOptions
{
Host = builder.Configuration["Convergence:Host"]!,
Port = int.Parse(builder.Configuration["Convergence:Port"]!),
SourceId = byte.Parse(builder.Configuration["Convergence:SourceId"]!),
}).GetAwaiter().GetResult());
// Register each KindHandle as a singleton
builder.Services.AddSingleton<KindHandle<Player>>(sp =>
sp.GetRequiredService<ConvergenceClient>()
.RegisterKindAsync<Player>()
.GetAwaiter().GetResult());
builder.Services.AddSingleton<KindHandle<OrderView>>(sp =>
sp.GetRequiredService<ConvergenceClient>()
.RegisterKindAsync<OrderView>()
.GetAwaiter().GetResult());

Add the connection details to appsettings.json:

{
"Convergence": {
"Host": "127.0.0.1",
"Port": "3727",
"SourceId": "1"
}
}

Inject KindHandle<T> directly. There is no need to inject ConvergenceClient unless you need low-level access.

public class PlayerService(KindHandle<Player> players)
{
public Task UpdateAsync(Player p) => players.AssertAsync(p);
public Task<Player?> GetAsync(ReadOnlyMemory<byte> id)
=> players.QueryAsync(id);
}

Use a BackgroundService to process subscription notifications:

public class PlayerSubscriberService(KindHandle<Player> players)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
await foreach (var change in players.SubscribeAsync(
bootstrap: true, ct: ct))
{
// Process change...
}
}
}

Register it:

builder.Services.AddHostedService<PlayerSubscriberService>();

ConvergenceClient is thread-safe. Multiple threads can call AssertAsync, QueryAsync, and SubscribeAsync concurrently. A single client instance should be shared across the application rather than creating one per request.

KindHandle<T> is also thread-safe and designed to be used as a singleton.

Program.cs
var builder = WebApplication.CreateBuilder(args);
// ConvergeDB client
builder.Services.AddSingleton<ConvergenceClient>(_ =>
ConvergenceClient.ConnectAsync(new ConvergenceOptions
{
Host = builder.Configuration["Convergence:Host"]!,
Port = int.Parse(builder.Configuration["Convergence:Port"]!),
SourceId = byte.Parse(builder.Configuration["Convergence:SourceId"]!),
}).GetAwaiter().GetResult());
builder.Services.AddSingleton<KindHandle<Player>>(sp =>
sp.GetRequiredService<ConvergenceClient>()
.RegisterKindAsync<Player>()
.GetAwaiter().GetResult());
// Application services
builder.Services.AddScoped<PlayerService>();
builder.Services.AddHostedService<PlayerSubscriberService>();
var app = builder.Build();
app.MapGet("/players/{id:guid}", async (Guid id, PlayerService svc) =>
{
var player = await svc.GetAsync(EntityId.FromGuid(id));
return player is { } p ? Results.Ok(p) : Results.NotFound();
});
app.MapPut("/players/{id:guid}", async (Guid id, Player player, PlayerService svc) =>
{
await svc.UpdateAsync(player with { EntityId = EntityId.FromGuid(id) });
return Results.NoContent();
});
app.Run();