Lightweight RPC framework enabling bidirectional communication with interface-based contracts over Named Pipes, TCP/IP, and WebSockets. Supports .NET servers/clients and Node.js/Web clients.
- 🔄 Asynchronous: Fully async/await compatible API
- 🔥 One-way Calls: Fire-and-forget methods (methods returning non-generic Task)
- 📞 Callbacks: Bidirectional communication with callback interfaces
- ⚡ Task Scheduling: Configurable task scheduler support
- 📡 Multiple Transports: Named Pipes, TCP/IP, WebSockets or custom transport
- 🔒 Security: Client authentication and impersonation for Named Pipes
- 📦 JSON Serialization: Built-in JSON serialization with Newtonsoft.Json
- 🏗️ DI Support: Integration with Microsoft.Extensions.DependencyInjection
- ⏰ Cancellation & Timeouts: Comprehensive cancellation token and timeout support
- 🔄 Auto-reconnect: Broken connections are re-established transparently; you keep using the same proxy instance.
- 🛡️ Interception: BeforeConnect, BeforeIncommingCall and BeforeOutgoingCall interception capabilities
- 📊 Stream Access: Direct access to underlying transport streams
- 🌐 Cross-platform: .NET 6, .NET Framework 4.6.1, and .NET 6 Windows support
dotnet add package UiPath.Ipcpublic interface IComputingService
{
    Task<float> AddFloats(float x, float y, Message m = null!, CancellationToken ct = default);
    Task<bool> Wait(TimeSpan duration, CancellationToken ct = default);
}
public interface IComputingCallback
{
    Task<string> GetThreadName();
}public sealed class ComputingService : IComputingService
{
    public async Task<float> AddFloats(float a, float b, Message m = null!, CancellationToken ct = default)
    {
        return a + b;
    }
    public async Task<bool> Wait(TimeSpan duration, CancellationToken ct = default)
    {
        await Task.Delay(duration, ct);
        return true;
    }
}Creating a server is done by instantiating the
IpcServerclass, setting its properties and calling theStartmethod.
await using var serviceProvider = new ServiceCollection()
    .AddScoped<IComputingService, ComputingService>()
    .BuildServiceProvider();
await using var server = new IpcServer
{
    Transport = new NamedPipeServerTransport { PipeName = "computing" },
    ServiceProvider = serviceProvider,
    Endpoints = new() { typeof(IComputingService) }
};
server.Start();
await server.WaitForStart();Creating a client is done by 1st implementing all the callback interfaces you'll want to expose as a client:
public sealed class ComputingCallback : IComputingCallback
{
    public Task<string> GetThreadName() => Task.FromResult(Thread.CurrentThread.Name);
}and then instantiating the IpcClient, setting its properties, obtaining a proxy via the GetProxy<T> method and using that proxy.
await using var serviceProvider = new ServiceCollection()
    .AddScoped<IComputingCallback, ComputingCallback>()
    .BuildServiceProvider();
var client = new IpcClient
{
    Transport = new NamedPipeClientTransport { PipeName = "computing" },
    ServiceProvider = serviceProvider,
    Callbacks = new() { typeof(IComputingCallback) }
}
var computingService = client.GetProxy<IComputingService>();
var three = await computingService.AddFloats(1, 2);public class ComputingCallback : IComputingCallback
{
    public async Task<string> GetThreadName()
    {
        return Thread.CurrentThread.Name ?? "Unknown";
    }
    public async Task<int> AddInts(int x, int y)
    {
        return x + y;
    }
}
// Server can call back to client
public async Task<string> GetCallbackThreadName(TimeSpan waitOnServer, Message message, CancellationToken cancellationToken)
{
    await Task.Delay(waitOnServer, cancellationToken);
    return await message.Client.GetCallback<IComputingCallback>().GetThreadName();
}var services = new ServiceCollection()
    .AddLogging(builder => builder.AddConsole())
    .AddSingleton<IComputingService, ComputingService>()
    .AddSingleton<ISystemService, SystemService>()
    .BuildServiceProvider();
var ipcServer = new IpcServer
{
    ServiceProvider = services,
    // ... other configuration
};var ipcServer = new IpcServer
{
    Scheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler,
    // ... other configuration
};This hierarchy is used for creating and hosting servers and clients respectively.
public abstract class IpcBase { ... } public sealed class IpcServer : IpcBase { ... } public sealed class IpcClient : IpcBase { ... }
This class defines the settings shared between servers and clients.
| Property | Type | Notes | 
|---|---|---|
| ServiceProvider | IServiceProvider? | Optional, defaults to null: Resolves services when handling incomming calls. | 
| Scheduler | TaskScheduler? | Optional, defaults to the thread pool: Schedules incomming calls. | 
| RequestTimeout | TimeSpan? | Optional, defaults to infinity: Interval after which the honoring of requests will time out. | 
Declared properties
| Property | Type | Notes | 
|---|---|---|
| Endpoints | ContractCollection | Required: The collection of ContractSettings, which specifies the services to be exposed over Ipc. | 
| Transport | ServerTransport | Required: The server's transport, meaning whether it accepts connection over Named Pipes, TCP/IP, WebSockets, or a custom communication mechanism. | 
Methods
| Method | Description | 
|---|---|
| void Start() | This method starts hosting the current IpcServerinstance, meaning that it's imminent the transport will start listening and accepting connections, and those connections' calls will start to be honored.It's thread-safe, idempotent and fire&forget in nature, meaning it doesn't wait for the listener to become active. Further changes to the otherwise mutable IpcServerinstance  have no effect on the listener's settings or its exposed service collection.Exceptions: - InvalidOperationException: wrong configurations, such anullor invalid transport.- ObjectDisposedException: theIpcServerinstance had been disposed. | 
| Task WaitForStart() | This method calls Startand then awaits for the connection accepter to start. It's thread-safe and idempotent. | 
| ValueTask DisposeAsync() | Stops the connection accepter and cancels all active connections before completing the returned ValueTask. | 
*Declared properties
| Property | Type | Notes | 
|---|---|---|
| Callbacks | ContractCollection | Optional: The collection of ContractSettings, which specifies the services to be exposed over Ipc as callbacks. | 
| Transport | ClientTransport | Required: The client's transport, meaning whether it connects to the server over Named Pipes, TCP/IP, WebSockets, or a custom communication mechanism. | 
Methods
| Method | Notes | 
|---|---|
| TProxy GetProxy<TProxy>() where TProxy : class | Returns an Ipc proxy of the specified type, which is the gateway for remote calling. This method is idempotent, meaning that it will cache its result. | 
ContractCollectionis a type-safe collection that holdsContractSettingsinstances, mapping service interface types to their configuration. It implementsIEnumerable<ContractSettings>and provides convenientAddmethods for different scenarios.
Add Methods:
| Method | Description | 
|---|---|
| Add(Type contractType) | Adds a contract type that will be resolved from the service provider when needed (deferred resolution). | 
| Add(Type contractType, object? instance) | Adds a contract type with a specific service instance. If instanceisnull, uses deferred resolution. | 
| Add(ContractSettings endpointSettings) | Adds a pre-configured ContractSettingsinstance directly. | 
ContractSettingsrepresents the configuration for a single service contract, including how the service instance is created/resolved, task scheduling, and call interception.
Properties:
| Property | Type | Description | 
|---|---|---|
| Scheduler | TaskScheduler? | Optional: Custom task scheduler for this specific contract. Inherits from IpcBase.Schedulerif not set. | 
| BeforeIncomingCall | BeforeCallHandler? | Optional: Interceptor called before each incoming method call on this contract. | 
Constructors:
| Constructor | Description | 
|---|---|
| ContractSettings(Type contractType, object? serviceInstance = null) | Creates settings for a contract type with optional direct service instance. If serviceInstanceisnull, uses deferred resolution. | 
| ContractSettings(Type contractType, IServiceProvider serviceProvider) | Creates settings for a contract type with explicit service provider for dependency injection. | 
Service Resolution Strategies:
- Direct Instance: When you provide a service instance, that exact instance is used for all calls
- Deferred Resolution: When no instance is provided, the service is resolved from the IpcServer'sServiceProviderwhen needed
- Injected Resolution: When you provide a specific IServiceProvider, services are resolved from that provider
Usage Examples:
// Direct instance
var settings1 = new ContractSettings(typeof(IComputingService), new ComputingService());
// Deferred resolution (will use IpcServer.ServiceProvider)
var settings2 = new ContractSettings(typeof(IComputingService));
// Custom service provider
var customProvider = new ServiceCollection()
    .AddTransient<IComputingService, ComputingService>()
    .BuildServiceProvider();
var settings3 = new ContractSettings(typeof(IComputingService), customProvider);
// With advanced configuration
var settings4 = new ContractSettings(typeof(IComputingService))
{
    Scheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler,
    BeforeIncomingCall = async (callInfo, ct) =>
    {
        Console.WriteLine($"Calling {callInfo.Method.Name}");
    }
};