Skip to content

svan-jansson/Netler.NET

Repository files navigation

netler logo

Build Status NuGet

Netler.NET

A library for cross-process method calls over TCP. Designed for calling .NET methods from Elixir (see hexdocs.pm/netler), and usable anywhere you need fast binary RPC between processes. All messages are serialised with MessagePack.

Getting Started

dotnet add package Netler.NET

Server

Basic route registration

using Netler;

var server = Server.Create(config =>
{
    config.UsePort(5544);
    config.UseRoutes(routes =>
    {
        routes.Add("Add", param =>
        {
            var a = Convert.ToInt32(param[0]);
            var b = Convert.ToInt32(param[1]);
            return a + b;
        });
    });
});

await server.Start();

Typed routes with AddTyped

AddTyped infers parameter and return types from the lambda. The C# compiler enforces the types; the wire format is unchanged.

config.UseRoutes(routes =>
{
    routes.AddTyped("Add",    (int a, int b) => a + b);
    routes.AddTyped("Double", (int x) => x * 2);
    routes.AddTyped("Ping",   () => "pong");
    routes.AddTyped("Log",    (string msg) => { /* void handler */ });
});

Explicit composition with Params.Decode

Params.Decode wraps a typed delegate into the Func<object[], object> signature that IRoutes.Add expects. Useful when you want to separate the handler from the route registration, or pass an existing method reference.

static int Add(int a, int b) => a + b;

config.UseRoutes(routes =>
{
    routes.Add("Add",    Params.Decode<int, int, int>(Add));
    routes.Add("Double", Params.Decode((int x) => x * 2));
    routes.Add("Ping",   Params.Decode(() => "pong"));
});

Complex types

Any type annotated with [MessagePackObject] can be used as a parameter or return value.

using MessagePack;

[MessagePackObject]
public class EchoRequest  { [Key(0)] public string Text { get; set; } }

[MessagePackObject]
public class EchoResponse { [Key(0)] public string Message { get; set; }
                            [Key(1)] public int    Length  { get; set; } }

config.UseRoutes(routes =>
{
    routes.AddTyped("Echo", (EchoRequest req) =>
        new EchoResponse { Message = req.Text, Length = req.Text.Length });
});

Monitoring the client process

The server can watch a client OS process and react automatically when it exits.

Server.Create(config =>
{
    config.UsePort(5544);
    config.UseClientPid(clientProcessId);
    config.UseClientDisconnectBehaviour(ClientDisconnectBehaviour.DisposeServer);
});
ClientDisconnectBehaviour Effect
ShutdownApplication Calls Environment.Exit(0) — default
DisposeServer Stops the server; application keeps running
KeepAlive No action

Custom logger

using Microsoft.Extensions.Logging;

ILogger logger = loggerFactory.CreateLogger<MyApp>();

Server.Create(config =>
{
    config.UsePort(5544);
    config.UseLogger(logger);
    config.UseRoutes(routes => { /* ... */ });
});

Client

Typed calls (recommended)

using Netler;

using var client = new Client(5544);

var sum  = await client.InvokeAsync<int>("Add",    new object[] { 2, 3 });
var pong = await client.InvokeAsync<string>("Ping", new object[0]);

// Complex return type — EchoResponse must carry [MessagePackObject]
var reply = await client.InvokeAsync<EchoResponse>("Echo",
    new object[] { new EchoRequest { Text = "hello" } });

Untyped calls

var raw = await client.InvokeAsync("Add", new object[] { 2, 3 });
var sum = Convert.ToInt32(raw);

Remote to another machine

using var client = new Client("192.168.1.10", 5544);

Error handling

When a route handler throws an unhandled exception on the server, the client receives a RemoteInvokationFailed exception containing the server-side error message.

using Netler.Exceptions;

try
{
    await client.InvokeAsync("Divide", new object[] { 10, 0 });
}
catch (RemoteInvokationFailed ex)
{
    Console.WriteLine($"Server error: {ex.Message}");
}

About

Server and Client for cross-process method calls over TCP using MessagePack

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages