Esiur for .NET
Esiur is a distributed resource framework for building real-time services
without maintaining a separate RPC contract. A resource can expose properties,
methods, events, records, and related resources over the self-describing
EP protocol. Wire concepts and protocol names stay
consistent across languages, while each implementation presents idiomatic
awaitables โ .NET uses AsyncReply<T>, wrapped so it's
await-compatible like a Task<T>.
Install
For an ASP.NET Core service:
dotnet add package Esiur.AspNetCore --version 3.0.0For the standalone runtime or a client-only application:
dotnet add package Esiur --version 3.0.0The core runtime targets .NET Standard 2.0, so it can be consumed from any compatible application. Projects using the bundled source generator need a Roslyn 4.8+ toolchain (.NET SDK 8 or newer). Building this repository from source requires .NET SDK 10.
Define a resource
Mark the class partial so Esiur's source generator can implement
the resource lifecycle and generate exported properties from fields.
using Esiur.Resource;
[Resource]
public partial class CounterResource
{
[Export]
int count;
[Export]
public event ResourceEventHandler<int>? Changed;
[Export]
public string Hello(string name) => $"Hello, {name}!";
[Export]
public int Increment()
{
Count++;
Changed?.Invoke(Count);
return Count;
}
}
The private count field becomes the exported Count
property. Assigning it after the resource is attached publishes the
modification to subscribed peers. Methods and events explicitly marked with
[Export] become part of the remote definition.
ASP.NET Core quick start
Esiur.AspNetCore is the recommended way to host Esiur in a web
application. The host owns startup and shutdown, while Esiur owns session
authentication, encryption, resource authorization, and the resource graph.
using Esiur.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://localhost:8080");
builder.Services.AddEsiur(esiur =>
{
esiur
.AddMemoryStore("sys")
.AddResource<CounterResource>("sys/counter")
.AllowAnonymous() // Development only.
.IncludeExceptionMessages(); // Development only.
});
var app = builder.Build();
var webSockets = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromSeconds(30),
};
webSockets.AllowedOrigins.Add("http://localhost:8080");
app.UseWebSockets(webSockets);
app.MapEsiur("/esiur");
await app.RunAsync();
AddEsiur registers one host-managed Warehouse and
EpServer. Resources are constructed through dependency injection,
configuration is validated at host startup, and active WebSockets are drained
or aborted before resources are terminated during shutdown.
The endpoint returned by MapEsiur supports normal ASP.NET Core conventions:
app.MapEsiur("/esiur")
.RequireAuthorization("EsiurUpgrade")
.RequireRateLimiting("EsiurHandshakes");Connect a .NET client
Use the logical ep:// resource URL and select the WebSocket
transport with an EpConnectionContext:
using Esiur.Protocol;
using Esiur.Resource;
var client = new Warehouse();
dynamic counter = await client.Get<IResource>(
"ep://localhost/sys/counter",
new EpConnectionContext
{
WebSocketUri = new Uri("ws://localhost:8080/esiur"),
});
Console.WriteLine(await counter.Hello("Ada"));
Console.WriteLine(await counter.Increment());
await client.Close();
The ep:// URL identifies the logical connection and resource
path; WebSocketUri identifies the transport endpoint. Esiur
automatically requests the case-sensitive EP WebSocket
subprotocol โ see the protocol spec.
For native TCP, omit WebSocketUri and include the EP port in the logical URL:
dynamic counter = await client.Get<IResource>(
"ep://localhost:10518/sys/counter");Standalone hosting
Applications that don't use ASP.NET Core can compose the runtime directly.
Warehouse is the root container for stores, resources, protocol
handlers, security providers, policies, and lifecycle state.
using Esiur.Protocol;
using Esiur.Resource;
using Esiur.Stores;
var warehouse = new Warehouse();
await warehouse.Put("sys", new MemoryStore());
await warehouse.Put("sys/counter", new CounterResource());
await warehouse.Put("sys/server", new EpServer
{
Port = 10518,
AllowUnauthorizedAccess = true, // Development only.
});
await warehouse.Open();
try
{
await Task.Delay(Timeout.InfiniteTimeSpan);
}
finally
{
await warehouse.Close();
}
Use a separate Warehouse for each isolated server or client
runtime โ avoid relying on a static default warehouse in applications that
host more than one Esiur environment.
Authentication and encryption
Anonymous access is opt-in. Production services should omit
AllowAnonymous, register an authentication provider, register an
encryption provider, and require encryption.
using Esiur.AspNetCore;
using Esiur.Security.Authority.Providers;
using Esiur.Security.Cryptography;
var credentials =
new Dictionary<(string Domain, string Identity), PasswordHash>();
builder.Services.AddEsiur(esiur =>
{
esiur
.AddMemoryStore("sys")
.AddResource<CounterResource>("sys/counter")
.UsePasswordAuthentication((identity, domain) =>
credentials.TryGetValue((domain, identity), out var credential)
? credential
: null)
.UseEncryption(new AesEncryptionProvider())
.RequireEncryption()
.AllowWebSocketOrigins("https://app.example.com");
});
Create a PasswordHash once during enrollment with
PasswordAuthenticationProvider.CreateCredential(passwordBytes).
Clear the plaintext bytes after use and protect the stored verifier like a
password โ it is sufficient to authenticate through the
password-sha3-v1 protocol if stolen.
| Protocol | Role |
|---|---|
password-sha3-v1 | Simple password challenge-response for initializer, responder, or dual identity authentication |
ppap-mlkem768-v1 | ML-KEM-768 (post-quantum) authentication with password-derived or static identities |
aes-gcm | AES-256-GCM record protection derived from the authenticated session key |
The authentication provider abstraction supports multi-message handshakes.
PPAP uses that pipeline for initializer, responder, or dual identity
authentication, and supports password-derived Argon2id identities as well as
static ML-KEM identities. Applications with custom protocols can implement
IAuthenticationProvider and register it through
UseAuthentication. Protocol names are exact and case-sensitive โ
v3 does not negotiate legacy aliases.
PPAP is described in A. K. Zamil and A. D. Jasim, "A Multi-Factor Quantum-Resistant and Privacy-Preserving Authentication Protocol for Decentralized Systems," Mesopotamian Journal of CyberSecurity, vol. 5, no. 3, pp. 1272โ1291, 2025. Available online.
Entity Framework Core 10
Esiur.Stores.EntityCore integrates Esiur resource materialization and paths with EF Core 10.
dotnet add package Esiur.Stores.EntityCore --version 3.0.0
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 10.0.10Configure the database provider first, then attach the EntityStore with the typed UseEsiur extension:
using Esiur.Resource;
using Esiur.Stores.EntityCore;
using Microsoft.EntityFrameworkCore;
var warehouse = new Warehouse();
var store = await warehouse.Put("database", new EntityStore());
DbContextOptions<AppDbContext>? options = null;
options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite("Data Source=app.db")
.UseEsiur(store, () => new AppDbContext(options!))
.Options;
await warehouse.Open();
await using var db = new AppDbContext(options);
await db.Database.EnsureCreatedAsync();
var device = await db.Devices.AddResourceAsync(new Device { Name = "sensor-1" });using System.ComponentModel.DataAnnotations;
using Esiur.Resource;
using Microsoft.EntityFrameworkCore;
[Resource]
public partial class Device
{
[Key, Export]
int id;
[Export]
string name = string.Empty;
}
public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
: DbContext(options)
{
public DbSet<Device> Devices => Set<Device>();
}The repository test suite runs a complete SQLite schema, insert, materialization, and Warehouse lookup cycle. PostgreSQL (via Npgsql) and MySQL (via Oracle's EF provider) are also checked for model creation and SQL translation, without requiring external database servers.
Generate typed client models
EP is self-describing, so clients can work dynamically or generate strongly typed models. Install the v3 CLI as a .NET global tool:
dotnet tool install --global Esiur.CLI --version 3.0.0
esiur get-template ep://localhost:10518/sys/counter --dir Generated
Use --async-setters to generate asynchronous property setters.
The CLI also accepts --username and --password, but
command-line secrets may be visible to other processes or shell history โ
prefer a safer credential workflow when the environment is shared.
Runtime limits and security defaults
WarehouseConfiguration bounds work performed on untrusted input:
packet size, decoded allocation size, collection items and type-metadata
depth, attached/pending resources per connection, concurrent connections
globally and per IP address, encrypted record size, and repeated
rate-control denials.
builder.Services.AddEsiur(esiur => esiur
.AddMemoryStore("sys")
.AddResource<CounterResource>("sys/counter")
.LimitPendingWebSocketSendBytes(2 * 1024 * 1024)
.LimitTotalPendingWebSocketSendBytes(256L * 1024 * 1024)
.ConfigureWarehouse(configuration =>
{
configuration.Connections.MaximumConnections = 500;
configuration.Connections.MaximumConnectionsPerIpAddress = 20;
configuration.Connections.MaximumConnectionAttempts = 2_000;
}));
When deploying behind a reverse proxy, trust only known proxy addresses and
apply forwarded headers before UseWebSockets and
MapEsiur. Browser origin checks, ASP.NET Core endpoint
authorization, Esiur session authentication, and resource permissions are
separate layers โ configure all of them, rather than relying on one.
Build and test
The repository is pinned to .NET SDK 10:
dotnet restore Esiur.sln
dotnet build Esiur.sln --configuration Release
dotnet test Esiur.sln --configuration Release
dotnet list Esiur.sln package --vulnerable --include-transitiveThe main automated suite covers packet parsing, serialization, authentication, PPAP registration rotation, encrypted records, resource attachment limits, connection admission, WebSockets, ASP.NET Core hosting, Warehouse lifecycle, and the EF Core provider.
Packages
All first-party v3 packages share major version 3, to make compatibility clear.
| Package | Purpose | Target framework |
|---|---|---|
Esiur | Core runtime, EP protocol, stores, security, and source generator | .NET Standard 2.0 |
Esiur.AspNetCore | ASP.NET Core hosting, dependency injection, endpoint routing, WebSockets | .NET 8 / .NET 10 |
Esiur.Stores.EntityCore | Entity Framework Core 10 resource store | .NET 10 |
Esiur.Stores.MongoDB | MongoDB resource store | .NET 10 |
Esiur.CLI | Generate typed C# models from a remote EP resource | .NET 10 tool |