A WitRPC client talks to a remote service through a proxy: an object that implements your contract interface locally and forwards every call over the connection. WitRPC can build that proxy two ways, and the choice determines which packages your client project needs.

Source-generated (static) Runtime (dynamic)
Proxy built at compile time at first use
Extra package OutWit.Common.Proxy + .Generator OutWit.Communication.Client.DynamicProxy
Contract needs [ProxyTarget] attribute nothing
NativeAOT and trimming supported not available
Generic methods in the contract not supported supported
Startup cost none proxy emitted at runtime

Both paths produce the same programming model, speak the same wire protocol, and work against the same server. Nothing on the server side changes.

Source-generated proxies

A Roslyn source generator writes the proxy class during the build, so no code is emitted at runtime. This is the path for NativeAOT, trimming, and Blazor WebAssembly with AOT compilation, and it costs nothing at startup.

Add the attribute package to the contract project and mark the interface:

bash
dotnet add package OutWit.Common.Proxy
csharp
using OutWit.Common.Proxy.Attributes;

[ProxyTarget("ExampleServiceProxy")]
public interface IExampleService
{
    event Action<double> ProgressChanged;
    Task<string> ProcessDataAsync(string data);
}

Add the generator to the client project:

bash
dotnet add package OutWit.Common.Proxy.Generator

Building generates ExampleServiceProxy. Pass it to GetService:

csharp
var service = client.GetService<IExampleService>(x => new ExampleServiceProxy(x));

This overload lives in the core client package, so a client on this path needs no proxy runtime at all. One limitation to know in advance: the generator does not support generic methods in a contract.

Runtime dynamic proxies

Castle DynamicProxy emits the proxy class when you first request the service. Nothing to annotate, nothing to generate, which makes it the quicker path while prototyping or in applications that are not AOT-published.

bash
dotnet add package OutWit.Communication.Client.DynamicProxy
csharp
var service = client.GetService<IExampleService>();

The parameterless overload comes from that package. Its namespace is OutWit.Communication.Client, the same as the core client, so adding the package is all that is needed; call sites compile unchanged.

When client and server load the contract from differently named assemblies, relax the match:

csharp
var service = client.GetService<IExampleService>(strongAssemblyMatch: false);

Runtime proxy emission does not work under NativeAOT. An AOT-published client must use the source-generated path.

Why the split exists

Until version 2.4.0, Castle.Core came with the core packages, so every client carried a runtime code-generation dependency even when it never used one. Since 2.4.0 the dynamic path lives in its own opt-in package: OutWit.Communication, OutWit.Communication.Client, and OutWit.Communication.Server no longer depend on Castle.Core, and a client using source-generated proxies publishes under NativeAOT with no Castle assembly anywhere in its dependency graph.

Upgrading from an earlier version is a one-line change: if your code calls the parameterless GetService<T>(), add a reference to OutWit.Communication.Client.DynamicProxy. The source-generated path is unaffected.

Three extension packages sit on the dynamic path and reference it themselves: Client.Blazor, Client.DependencyInjection, and InterProcess.Host. If you use any of them, update to their current versions along with the client.

Choosing

Reach for source-generated proxies when the client is AOT-published or trimmed, runs in Blazor WebAssembly with AOT, or when you would rather keep the dependency graph minimal. Reach for runtime proxies when you want the shortest path to a working client, when the contract uses generic methods, or when the application is JIT-compiled anyway and one more dependency is not a concern.

The paths are not exclusive: different clients in the same solution can each pick their own, and switching later means changing one call site and one package reference.