The OutWit.Communication.Server.DependencyInjection and OutWit.Communication.Client.DependencyInjection packages integrate WitRPC with Microsoft.Extensions.DependencyInjection. Servers and clients register in the container by name, start with the host, resolve their service implementations from DI, and hand out injectable proxies. The result: RPC configured the way everything else in an ASP.NET Core application is configured.
Registering servers and clients
Registration happens in the standard service collection, typically in Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddWitRpcServer("my-server", options =>
{
options.WithTcp(5000, maxNumberOfClients: 100);
options.WithJson();
});
builder.Services.AddWitRpcClient("my-service", options =>
{
options.WithWebSocket("ws://localhost:5000");
options.WithJson();
options.WithAccessToken("client-secret");
});
var app = builder.Build();
app.Run();Names identify configurations, so an application can register several servers and clients with different transports side by side. Registration alone does not start anything; it stores the configuration and makes the factories (IWitServerFactory, IWitClientFactory) available.
The configuration lambda receives a builder context that supports every regular With... option and additionally exposes ServiceProvider, so a configuration can resolve anything from the container:
builder.Services.AddWitRpcServer("my-server", context =>
{
context.WithNamedPipe("MyServicePipe");
context.WithJson();
context.WithService(context.ServiceProvider.GetRequiredService<IMyService>());
});Resolving service implementations from DI
Writing GetRequiredService by hand is rarely necessary; two registration shapes do it for you.
One interface per server. The generic overload registers the implementation in DI and wires it to the server in one call:
builder.Services.AddWitRpcServer<IMyService, MyService>("my-server", options =>
{
options.WithNamedPipe("MyServicePipe");
options.WithJson();
});MyService is registered as a singleton, constructed by the container with all its constructor dependencies, and served by the RPC server. The service class stays an ordinary DI citizen.
Several interfaces per server. AddWitRpcServerWithServices builds a composite service → from DI-managed implementations:
builder.Services.AddWitRpcServerWithServices("api-server",
options =>
{
options.WithTcp(5000, maxNumberOfClients: 50);
options.WithJson();
},
registration =>
{
registration.AddService<IUserService, UserService>();
registration.AddService<IOrderService, OrderService>();
registration.AddService<INotificationService, NotificationService>();
});Each AddService<TInterface, TImplementation>() registers the implementation in the container and marks the interface for hosting. Implementations already registered elsewhere are picked up with AddService<TInterface>() alone, and a factory overload AddService<TInterface>(sp => ...) covers custom construction. When the server starts, all marked services are resolved from DI and exposed over one connection.
Auto-start and auto-connect
By default, registered servers and clients wait for you. Two flags tie them to the host lifecycle instead:
builder.Services.AddWitRpcServer("my-server", options =>
{
options.WithTcp(5000, maxNumberOfClients: 100);
options.WithJson();
}, autoStart: true);
builder.Services.AddWitRpcClient("my-service", options =>
{
options.WithTcp("127.0.0.1", 5000);
options.WithJson();
}, autoConnect: true, connectionTimeout: TimeSpan.FromSeconds(30));autoStart: true registers a hosted service that calls StartWaitingForConnection() on the named server when the application starts. autoConnect: true does the same for clients, calling ConnectAsync with the given timeout (30 seconds when omitted) and disconnecting gracefully on shutdown. With several registrations, each decides for itself; leaving a flag off keeps manual control for cases where a connection should open later, on demand.
Injecting service proxies
The typed client registration turns a remote interface into an injectable dependency:
builder.Services.AddWitRpcClient<IMyService>("my-service", options =>
{
options.WithNamedPipe("MyServicePipe");
options.WithJson();
options.WithEncryption();
});After this, any class can take the interface through its constructor and receive the RPC proxy:
public class MyController : Controller
{
private readonly IMyService m_myService; // the RPC proxy
public MyController(IMyService myService)
{
m_myService = myService;
}
public async Task<IActionResult> GetData()
{
var result = await m_myService.GetDataAsync();
return Ok(result);
}
}Controllers and services call remote APIs through plain interfaces, with no networking code in sight. Two conditions apply. The client must be connected before the proxy is used, so pair typed clients with autoConnect: true (or connect through the factory during initialization). And the interface must be the shared contract the server hosts, as always.
Factories for manual control
Both factories live in the container and give runtime access to configured instances by name:
public class RpcController
{
private readonly IWitServerFactory m_servers;
private readonly IWitClientFactory m_clients;
public RpcController(IWitServerFactory servers, IWitClientFactory clients)
{
m_servers = servers;
m_clients = clients;
}
public void RestartServer()
{
var server = m_servers.GetServer("my-server");
server.StopWaitingForConnection();
server.StartWaitingForConnection();
}
public async Task<string> CallManually()
{
var client = m_clients.GetClient("my-service");
if (client.ConnectionState != ReconnectionState.Connected)
await client.ConnectAsync(TimeSpan.FromSeconds(5), CancellationToken.None);
var service = m_clients.GetService<IMyService>("my-service");
return await service.GetDataAsync();
}
}GetServer(name) and GetClient(name) build the instance from its stored configuration on first access; GetService<TService>(name) returns a proxy from a named client directly. The auto-start hosted services use these same factories internally, so manual and automatic control compose: auto-start what should always run, drive the rest through the factories.
Health checks build on this naming too: AddWitRpcClient("my-service") on the health checks builder monitors the named client's connection, as described in Resilience & Health Checks →.