PicoJsonRpc 2026.4.5

dotnet add package PicoJsonRpc --version 2026.4.5
                    
NuGet\Install-Package PicoJsonRpc -Version 2026.4.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PicoJsonRpc" Version="2026.4.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PicoJsonRpc" Version="2026.4.5" />
                    
Directory.Packages.props
<PackageReference Include="PicoJsonRpc" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PicoJsonRpc --version 2026.4.5
                    
#r "nuget: PicoJsonRpc, 2026.4.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PicoJsonRpc@2026.4.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PicoJsonRpc&version=2026.4.5
                    
Install as a Cake Addin
#tool nuget:?package=PicoJsonRpc&version=2026.4.5
                    
Install as a Cake Tool

PicoNode

A layered, AOT-native networking stack for .NET — from raw TCP/UDP sockets to a fully featured HTTP web framework.

NuGet License .NET

English | 简体中文 | 繁體中文 | Deutsch | Español | Français | 日本語 | 한국어 | Português (Brasil) | Русский

┌─────────────────────────────────────────────────────────────┐
│  PicoNode: layered networking for .NET                      │
│  ✓ Raw TCP/UDP socket transports with async I/O             │
│  ✓ HTTP/1.1 + HTTP/2 + WebSocket protocols                  │
│  ✓ Web framework with middleware, routing, static files      │
│  ✓ Integrated with PicoHex ecosystem (PicoDI/PicoLog/PicoCfg)│
│  ✓ Native AOT compatible across all net10.0 layers           │
│  ✓ Minimal runtime dependencies                             │
└─────────────────────────────────────────────────────────────┘

Why PicoNode?

Feature PicoNode ASP.NET Core
Dependency model No Microsoft framework deps (PicoHex-native libraries only); layer pick-and-choose Microsoft.AspNetCore.App framework reference
Request parsing Span-based streaming, zero-copy System.IO.Pipelines String-based with IO.Pipelines adapter
HTTP/2 Inline HPACK decoder, frame-level control Transparent via Kestrel; limited low-level access
AOT Support ✅ Native — all net10.0 libraries ⚠️ Requires trimming
DI / Logging / Config PicoDI + PicoLog + PicoCfg (PicoHex native) Microsoft.Extensions.*
WebSocket RFC 6455 frame codec with message handler abstraction Transparent via middleware
Line count ~18K for the full stack ~1M+ for ASP.NET Core

Design priority: PicoNode prioritizes allocation efficiency and AOT compatibility. ValueTask on hot-path delegates, ArrayPool-based buffer management, and optional delegates (no forced allocations) are deliberate trade-offs — they keep the transport layer compact and predictable.

The PicoHex Ecosystem

PicoNode is part of the PicoHex family and integrates natively with:

Library Purpose NuGet
PicoDI Zero-reflection compile-time DI PicoDI.Abs
PicoLog Structured logging with AOT safety PicoLog.Abs
PicoCfg Source-generated configuration binding PicoCfg.Abs
PicoNode.Abs        Core interfaces                          (net10.0, zero deps)
    ↓
PicoNode             TCP & UDP transports + ILogger           (net10.0)
    ↓
PicoNode.Http        HTTP/1.1 + HTTP/2 + WebSocket            (net10.0)
    ↓
PicoNode.Web         Web framework + PicoDI ISvcContainer     (net10.0)
    ↓
PicoWeb              Ready-to-run web server + PicoCfg        (net10.0)
PicoJsonRpc           JSON-RPC 2.0 over stdio (NDJSON)      (net10.0)

Quick Start

Installation

dotnet add package PicoNode

Installing PicoNode brings in the TCP/UDP transport. Reference PicoNode.Http or PicoNode.Web for higher-level layers.

Package Architecture

PicoNode ships as layered NuGet packages. Pick exactly the abstraction level you need:

Package Install when… What you get
PicoWeb You want a ready-to-run web server WebServer + WebApp + HTTP + TCP (all transitive)
PicoNode.Web You want the web framework without hosting WebApp, routing, middleware, static files, DI
PicoNode.Http You want raw HTTP protocol handling HTTP/1.1 + HTTP/2 + WebSocket, HttpRouter
PicoNode You want raw TCP/UDP transports TcpNode, UdpNode, socket lifecycle, metrics
PicoNode.Abs You're writing a handler or extension INode, ITcpConnectionHandler, core contracts
PicoWeb  →  PicoNode.Web  →  PicoNode.Http  →  PicoNode.Abs
PicoWeb  →  PicoNode  →  PicoNode.Abs

TCP Echo Server

using System.Net;
using PicoNode;
using PicoNode.Abs;

var node = new TcpNode(new TcpNodeOptions
{
    Endpoint = new IPEndPoint(IPAddress.Loopback, 7001),
    ConnectionHandler = new EchoHandler(),
});

await node.StartAsync();
Console.ReadLine();
await node.DisposeAsync();

sealed class EchoHandler : ITcpConnectionHandler
{
    public Task OnConnectedAsync(ITcpConnectionContext c, CancellationToken ct)
        => Task.CompletedTask;
    public Task OnClosedAsync(ITcpConnectionContext c, TcpCloseReason r,
        Exception? e, CancellationToken ct) => Task.CompletedTask;

    public ValueTask<SequencePosition> OnReceivedAsync(
        ITcpConnectionContext connection,
        ReadOnlySequence<byte> buffer,
        CancellationToken ct)
    {
        _ = connection.SendAsync(buffer, ct);
        return ValueTask.FromResult(buffer.End);
    }
}

HTTP Server (Low-Level)

using System.Net;
using PicoNode;
using PicoNode.Http;

var node = new TcpNode(new TcpNodeOptions
{
    Endpoint = new IPEndPoint(IPAddress.Loopback, 7002),
    ConnectionHandler = new HttpConnectionHandler(new HttpConnectionHandlerOptions
    {
        RequestHandler = new HttpRouter(new HttpRouterOptions
        {
            Routes =
            [
                Route<HttpRequestHandler>.MapGet("/", static (_, _) =>
                    ValueTask.FromResult(new HttpResponse
                    {
                        StatusCode = 200, ReasonPhrase = "OK",
                        Headers = [new("Content-Type", "text/plain")],
                        Body = "Hello from PicoNode.Http"u8.ToArray(),
                    })),
            ],
        }).HandleAsync,
        ServerHeader = "PicoNode",
    }),
});

await node.StartAsync();
Console.ReadLine();
await node.DisposeAsync();

Web Application (DI First + Delegate)

using PicoNode.Web;
using PicoWeb;

var api = new WebApiBuilder()
    .ConfigureApp(_ => new WebAppOptions { ServerHeader = "MyApp" })
    // ConfigureApp receives the CURRENT options — later calls can build on
    // earlier configuration instead of starting from defaults.
    .RegisterScoped<IUserService, UserService>()
    .Build();

api.App.MapGet("/", static (WebContext ctx, CancellationToken _) =>
    ValueTask.FromResult(Results.Text(200, "Hello, World!")));

api.App.MapGet("/users/{id}", async (WebContext ctx, CancellationToken _) =>
{
    var svc = (IUserService)ctx.Services.GetService(typeof(IUserService))!;
    var user = await svc.GetByIdAsync(ctx.RouteValues["id"]);
    var bytes = PicoJetson.JsonSerializer.SerializeToUtf8Bytes(user);
    return Results.Json(200, bytes);
});

api.App.MapPost("/echo", async (WebContext ctx, CancellationToken _) =>
{
    using var reader = new StreamReader(ctx.Request.BodyStream);
    var body = await reader.ReadToEndAsync();
    return Results.Text(200, body);
});

await api.RunAsync("http://+:8080");

Web Application (Controller-based)

// Controllers/UsersController.cs
using PicoJetson;

public class UsersController
{
    public UserDto GetUser(int id) { return new UserDto { Id = id }; }
}

// Program.cs
var api = new WebApiBuilder()
    .RegisterScoped<UsersController>()
    .Build();

// Controllers.Gen auto-generates endpoint stubs (DTO serializers: PicoJetson.Gen)
await api.RunAsync("http://+:8080");

Configuration

PicoNode supports two configuration modes:

Code-First (inline)

var options = new TcpNodeOptions
{
    Endpoint = new IPEndPoint(IPAddress.Any, 8080),
    MaxConnections = 500,
    IdleTimeout = TimeSpan.FromMinutes(5),
};
var node = new TcpNode(options);

PicoCfg Binding (AOT-safe, source-generated)

var config = await Cfg.CreateBuilder()
    .Add(new Dictionary<string, string>
    {
        ["App:Name"] = "PicoCfg",
        ["App:Enabled"] = "true",
    })
    .BuildAsync();

var settings = CfgBind.Bind<AppSettings>(config, "App");

public sealed class AppSettings
{
    public string? Name { get; set; }
    public bool Enabled { get; set; }
}

Runtime Reload

// TcpNode supports runtime config reload (except Endpoint)
var options = new TcpNodeOptions
{
    Endpoint = new IPEndPoint(IPAddress.Loopback, 8080),
    Config = config, // ICfgRoot for live reload
};
// Node starts a reload loop watching for config changes

Key Options

TcpNodeOptions
Option Default Description
Endpoint (required) Local endpoint to bind
ConnectionHandler (required) ITcpConnectionHandler
MaxConnections 1000 Maximum concurrent connections
IdleTimeout 2 min Time before idle connections are closed
DrainTimeout 5 sec Grace period on shutdown
SslOptions null TLS/SSL configuration
NoDelay true TCP_NODELAY (Nagle disabled)
Logger null PicoLog ILogger for structured diagnostics
UdpNodeOptions
Option Default Description
Endpoint (required) Local endpoint to bind
DatagramHandler (required) IUdpDatagramHandler
DispatchWorkerCount 1 Concurrent datagram workers
DatagramQueueCapacity 1024 Per-worker queue depth
QueueOverflowMode DropNewest Behavior when queues are full
Logger null PicoLog ILogger
HttpConnectionHandlerOptions
Option Default Description
RequestHandler (required) HttpRequestHandler delegate
ServerHeader null Value for the Server header
MaxRequestBytes 8192 Maximum request size in bytes
MaxRequestBodySize 67108864 (64 MB) Maximum request body size in bytes
StreamingResponseBufferSize 4096 Buffer size for streamed response bodies
RequestTimeout 30 sec Maximum time to receive a complete request
WebSocketMessageHandler null WebSocket message handler
WebSocketMaxMessageSize 262144 (256 KB) Maximum reassembled WebSocket message size
Logger null PicoLog ILogger

Logging

PicoNode uses PicoLog for structured diagnostics. All non-fatal errors are logged with operation context:

var logger = new LoggerFactory([new ConsoleSink()])
    .CreateLogger("PicoNode.Tcp");

var node = new TcpNode(new TcpNodeOptions
{
    Endpoint = new IPEndPoint(IPAddress.Loopback, 7001),
    ConnectionHandler = handler,
    Logger = logger, // All transport faults logged here
});

// Log output:
// [Error] Operation tcp.accept failed: AcceptFailed - System.Net.Sockets.SocketException
// [Warning] Operation tcp.reject.limit failed: SessionRejected
// [Debug] Socket shutdown during TLS teardown failed

Log levels by fault code:

  • Error: StartFailed, StopFailed, AcceptFailed, ReceiveFailed, SendFailed, HandlerFailed, TlsFailed, DatagramReceiveFailed, DatagramHandlerFailed
  • Warning: SessionRejected, DatagramDropped
  • Debug: Socket shutdown during cleanup (best-effort operations)

Dependency Injection

PicoNode.Web requires ISvcContainer at construction time (DI First). Scopes are created per-request automatically.

Manual DI resolution in handlers

using PicoNode.Web;
using PicoWeb;
using PicoJetson;

var container = new SvcContainer();
container.RegisterScoped<IDatabase, SqlDatabase>();

var app = new WebApp(container);
app.MapGet("/db", async (WebContext ctx, CancellationToken _) =>
{
    var db = (IDatabase)ctx.Services.GetService(typeof(IDatabase))!;
    var data = await db!.QueryAsync("...");
    var bytes = PicoJetson.JsonSerializer.SerializeToUtf8Bytes(data);
    return Results.Json(200, bytes);
});

app.Build();

Resolving services inside handlers

Handlers always use the WebRequestHandler signature (WebContext, CancellationToken). Services come from the request scope via ctx.Services — there is no parameter injection:

app.MapGet("/users/{id}", async (WebContext ctx, CancellationToken _) =>
{
    var svc = (IUserService)ctx.Services.GetService(typeof(IUserService))!;
    var user = await svc.GetByIdAsync(ctx.RouteValues["id"]);
    var bytes = PicoJetson.JsonSerializer.SerializeToUtf8Bytes(user);
    return Results.Json(200, bytes);
});

AOT-compatible serialization

PicoJetson source generators run at compile time. Handlers must call SerializeToUtf8Bytes<T>() directly in user code to trigger generator:

// ✅ Triggers PicoJetson.Gen — UserDto serializer generated
var bytes = PicoJetson.JsonSerializer.SerializeToUtf8Bytes(user);

// ❌ Does NOT trigger generator (cross-assembly generic)
Results.Json<UserDto>(200, user);

WebApiBuilder (convenience)

using PicoNode.Web;
using PicoWeb;

var api = new WebApiBuilder()
    .RegisterScoped<IUserService, UserService>()
    .ConfigureJson(o => o.PropertyNamingPolicy = JsonNamingPolicy.CamelCase)
    .Build();

api.App.MapGet("/api/users/{id}", async (WebContext ctx, CancellationToken _) =>
{
    var svc = (IUserService)ctx.Services.GetService(typeof(IUserService))!;
    var user = await svc.GetByIdAsync(ctx.RouteValues["id"]);
    var bytes = PicoJetson.JsonSerializer.SerializeToUtf8Bytes(user);
    return Results.Json(200, bytes);
});

await api.RunAsync("http://+:5000");

WebApiBuilder with Controllers (endpoint registration)

// 1. Controller in Controllers/ folder (convention)
//    Controllers/UsersController.cs
public class UsersController
{
    public UserDto GetUser(int id) { return new UserDto { ... }; }
    public List<UserDto> GetAllUsers() { return ...; }
}

// 2. Controllers.Gen auto-registers controllers in DI via a generated
//    [ModuleInitializer] (no manual registration needed).
// 3. Call EndpointRegistrar (auto-generated by Controllers.Gen) to wire the routes:
EndpointRegistrar.RegisterAll(app);

// 4. Then run the app:
new WebApiBuilder()
    .RegisterScoped<UsersController>()
    .Build()
    .RunAsync("http://+:5000");

The Controllers.Gen source generator:

  • Scans the Controllers/ folder (or [ApiController] classes)
  • Generates endpoint stubs that resolve controllers from DI and register them

PicoWeb.Gen emits a build-time diagnostic (PWR001) for app.MapGet/MapPost handler return types; it generates no source. DTO serializers are produced by PicoJetson.Gen from explicit SerializeToUtf8Bytes<T>() call sites.

Note: In a project with no controllers that references an application already exporting EndpointRegistrar (e.g. an integration test referencing a sample app), Controllers.Gen emits no registrar of its own and RegisterAll binds to the referenced app's one — avoiding the CS0436 duplicate-type error and a silent no-op registration.

Note: The controller-based pattern requires PicoJetson.Gen for automatic DTO serialization registration. For the MapXX pattern, call PicoJetson.JsonSerializer.SerializeToUtf8Bytes<T>() explicitly in your handler.


## Built-in Middleware

### Compression

```csharp
var compression = new CompressionMiddleware(
    CompressionLevel.Fastest, minimumBodySize: 860);
app.Use(compression.InvokeAsync);

Supports Brotli, Gzip, and Deflate. Auto-selects the best encoding from the client's Accept-Encoding header.

Static Files

var staticFiles = new StaticFileMiddleware(
    "/path/to/wwwroot", requestPathPrefix: "/static");
app.Use(staticFiles.InvokeAsync);

Serves files from a root directory. Prevents directory traversal. Maps 30+ file extensions to MIME types.

CORS

app.Use(async (ctx, next, ct) =>
{
    var corsOptions = new CorsOptions
    {
        AllowedOrigins = ["https://example.com"],
        AllowedMethods = ["GET", "POST"],
        AllowCredentials = true,
    };
    var preflight = CorsHandler.HandlePreflight(ctx.Request, corsOptions);
    if (preflight is not null)
        return preflight;
    var response = await next(ctx, ct);
    // Add CORS response headers
    foreach (var header in CorsHandler.GetResponseHeaders(ctx.Request, corsOptions))
    {
        response.Headers.Add(header.Key, header.Value);
    }
    return response;
});

Cookies & Multipart

// Cookie parsing
var cookies = CookieParser.Parse(context.Request.HeaderFields);

// Set-Cookie
var setCookie = new SetCookieBuilder("session", "abc123")
    .Path("/").HttpOnly().Secure().SameSite("Strict").MaxAge(3600)
    .Build();

// Multipart form data
// Limits (MaxPartSizeBytes/MaxTotalSizeBytes) are enforced on both the in-memory
// Body path and the streaming BodyStream path; exceeding one throws InvalidDataException.
var form = await MultipartFormDataParser.ParseAsync(
    context.Request,
    new MultipartFormDataParserOptions
    {
        MaxPartSizeBytes = 64 * 1024 * 1024, // per part (default)
        MaxTotalSizeBytes = 64 * 1024 * 1024, // all parts (default)
        MaxBoundaryLength = 70, // default
    }
);
foreach (var field in form?.Fields ?? [])
    Console.WriteLine($"{field.Name} = {field.Value}");
foreach (var file in form?.Files ?? [])
    Console.WriteLine($"{file.FileName}: {file.ContentType} ({file.Content.Length} bytes)");

Metrics

Both TcpNode and UdpNode expose real-time counters:

// TCP
var tcpMetrics = node.GetMetrics();  // only TcpNode
Console.WriteLine($"Accepted: {tcpMetrics.TotalAccepted}");
Console.WriteLine($"Active: {tcpMetrics.ActiveConnections}");
Console.WriteLine($"Sent: {tcpMetrics.TotalBytesSent}");
Console.WriteLine($"Received: {tcpMetrics.TotalBytesReceived}");

// UDP counters available via internal state
// (UdpNode tracks datagrams, bytes, and drops internally)

Projects

Project Target Description
PicoNode.Abs net10.0 Core interfaces: INode, ITcpConnectionHandler, IUdpDatagramHandler, fault codes, enums
PicoNode net10.0 TcpNode and UdpNode — production-grade async socket transports
PicoNode.Http net10.0 HttpConnectionHandler, HttpRouter — HTTP/1.1, HTTP/2, WebSocket
PicoNode.Web net10.0 WebApp, WebRouter, middleware, static files, compression, CORS, DI
PicoWeb net10.0 WebServer — thin host wiring WebApp to TcpNode

Samples

Sample Port Description
PicoNode.Samples.Echo 7001 (TCP), 7002 (UDP) Raw TCP/UDP echo server
PicoNode.Samples.Http 7003 HTTP routing with HttpRouter
PicoWeb.Samples 7004 Full web app with middleware and DI
dotnet run --project samples/PicoWeb.Samples/PicoWeb.Samples.csproj

Building & Testing

# Build the entire solution
dotnet build PicoNode.slnx -c Release

# Run all tests
dotnet test --solution PicoNode.slnx -c Release

# Run a specific test project
dotnet test --project tests/PicoNode.Http.Tests/PicoNode.Http.Tests.csproj -c Release

# AOT publish check
dotnet publish src/PicoWeb/PicoWeb.csproj -c Release -r win-x64 -p:PublishAot=true

Benchmarks

Microbenchmarks are provided via PicoBench:

dotnet run --project benchmarks/PicoNode.Http.Benchmarks/PicoNode.Http.Benchmarks.csproj -c Release -- quick

Benchmarks cover HTTP parsing, router dispatch (hit/miss/405), full pipeline, and localhost round-trips.

Requirements

  • .NET 10.0+ (PicoNode, PicoNode.Http, PicoNode.Web, PicoWeb)
  • .NET 10.0 (PicoNode.Abs — maximum compatibility)
  • PicoHex ecosystem (optional): PicoDI, PicoLog, PicoCfg

License

MIT © 2025 XiaoFei Du


<p align="center"> <b>PicoNode</b> — layered networking for .NET </p>

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.4.5 74 9/18/2026
2026.4.4 51 9/18/2026
2026.4.3 53 9/18/2026
2026.4.2 114 9/16/2026
2026.4.1 84 9/16/2026
2026.4.0 78 9/16/2026
2026.3.0 92 9/10/2026