2026-07-19 23:06:54 +03:00
2026-07-19 01:06:23 +03:00
2026-07-17 02:19:05 +03:00
2026-07-19 23:06:54 +03:00
2026-07-18 00:44:14 +03:00
2026-07-19 01:06:23 +03:00
2026-07-18 19:05:32 +03:00
2026-07-19 01:06:23 +03:00
2026-06-10 17:47:21 +03:00
2026-07-18 00:44:14 +03:00
2026-07-18 00:44:14 +03:00
2021-02-22 13:01:13 +03:00
2026-05-25 14:12:56 +03:00
2026-07-18 00:44:14 +03:00

Esiur for .NET

CI NuGet License

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.

Version 3 focuses on a smaller public API, first-class ASP.NET Core hosting, extensible authentication, authenticated encryption, bounded resource usage, and predictable lifecycle behavior. The protocol remains designed for multiple language implementations: wire concepts and protocol names stay consistent, while each implementation can present idiomatic awaitables such as .NET AsyncReply<T>, Dart Future, or JavaScript Promise.

Highlights

  • Live distributed properties, method calls, and events.
  • Self-describing resource, record, enum, and type metadata.
  • C# source generation with [Resource] and [Export].
  • Native TCP and ASP.NET Core WebSocket transports.
  • Isolated Warehouse runtimes with memory, Entity Framework Core, and MongoDB stores.
  • Multi-step authentication providers with initializer, responder, and dual identities.
  • Password challenge-response and PPAP ML-KEM-768 authentication.
  • AES-256-GCM encrypted records and protected post-authentication key rotation.
  • Permissions, rate-control, auditing, parser limits, connection limits, and attachment limits.
  • Graceful startup and shutdown that settle resource lifecycle operations.

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, and 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

The core runtime can be consumed by any compatible .NET Standard 2.0 application. Projects using the bundled source generator need a Roslyn 4.8 or newer toolchain, such as .NET SDK 8 or newer. Building this repository requires .NET SDK 10.

Install

For an ASP.NET Core service:

dotnet add package Esiur.AspNetCore --version 3.0.0

For the standalone runtime or a client:

dotnet add package Esiur --version 3.0.0

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");

ASP.NET Core authorization protects the HTTP upgrade only. It does not replace Esiur authentication or per-resource authorization.

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.

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 do not 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 the 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.

The ASP.NET Core password shortcut accepts precomputed Esiur credentials from an application-owned cache or account store:

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.

Esiur v3 currently includes these canonical protocol names:

Protocol Role
password-sha3-v1 Simple password challenge-response for initializer, responder, or dual identity authentication
ppap-mlkem768-v1 ML-KEM-768 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. Password registrations use a versioned nonce and encapsulation key. Successful rotation is performed only through a dedicated exchange after encryption is active, preventing the registration nonce from becoming a permanent network identifier. Persistent PPAP registration stores must implement atomic compare-and-rotate behavior.

Applications with custom protocols can implement IAuthenticationProvider and register the provider through UseAuthentication. Protocol names are exact and case-sensitive; v3 does not negotiate legacy aliases.

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.10

Configure 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 through Npgsql and MySQL through 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 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. The default configuration includes limits for:

  • Packet size, decoded allocation size, collection items, and type-metadata depth.
  • Attached and pending resources per connection.
  • Concurrent connections and connection attempts globally and per IP address.
  • Encrypted record size.
  • Repeated rate-control denials.

ASP.NET Core hosting also provides per-connection and host-wide pending WebSocket send limits. Configure these for the expected workload rather than disabling them:

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 and should be configured together.

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-transitive

The 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 EF Core provider integration.

Version 3 compatibility

Version 3 is an intentional compatibility boundary:

  • All distributed first-party packages use major version 3.
  • ASP.NET Core applications use AddEsiur and MapEsiur instead of the legacy integration.
  • Authentication and encryption providers negotiate canonical protocol names without aliases.
  • EntityCore targets .NET 10 and EF Core 10.
  • Legacy packet and hosting APIs removed during the v3 cleanup are not retained as compatibility shims.

Within the v3 family, minor and patch package versions may advance independently. Consumers should keep the same major version across Esiur packages.

License

Esiur is available under the MIT License.

S
Description
Esiur Library for .Net Framework
Readme
69 MiB
Languages
C# 98.7%
PowerShell 1.3%