A WitRPC client calls a remote service through a proxy: an object that implements your contract interface locally and forwards every call over the connection. WitRPC builds that proxy one of two ways, and which one you pick determines which packages the client project references.
Castle DynamicProxy emits the proxy class at runtime. It needs no annotations and no build step, it intercepts method calls, property access and event subscriptions, and it handles generic methods, whose parameter types are unknown when the interface is declared. Runtime emission is not universally available, though: Ahead-of-Time environments such as Blazor WebAssembly and .NET Native AOT forbid generating code at runtime. For those, WitRPC generates the proxy at compile time with a Roslyn source generator. The docs call this the source-generated proxy; earlier versions called it the static proxy, and it is the same mechanism.
Since WitRPC 2.4.0 the runtime path ships as its own opt-in package. OutWit.Communication, OutWit.Communication.Client and OutWit.Communication.Server no longer depend on Castle.Core, so a client on source-generated proxies publishes under NativeAOT with no runtime code-generation dependency anywhere in its graph.
How to Use the Source-Generated Proxy
1. Add the OutWit.Common.Proxy Package to Your Contract Project
Install the OutWit.Common.Proxy package, which provides the base classes and attributes for proxy generation:
dotnet add package OutWit.Common.Proxy2. Mark Your Interface with the ProxyTarget Attribute
Apply the ProxyTarget attribute to the interface representing your contract, passing the name you want for the generated class:
using OutWit.Common.Proxy.Attributes;
[ProxyTarget("ServiceProxy")]
public interface IExampleService
{
event ExampleServiceEventHandler ProcessingStarted;
event ExampleServiceProgressEventHandler ProgressChanged;
event ExampleServiceProcessingEventHandler ProcessingCompleted;
bool StartProcessing();
void StopProcessing();
}In this case, a proxy class named ServiceProxy will be generated.
Note: Generic methods are not supported by the generator. Including generic methods in your interface will result in a compilation error, so a contract that has them needs those methods reshaped before it can go AOT.
3. Add the OutWit.Common.Proxy.Generator Package to Your Client Project
To enable proxy generation, add the OutWit.Common.Proxy.Generator package to your client project:
dotnet add package OutWit.Common.Proxy.GeneratorBuild the project, and the proxy class will be generated automatically.
Using the Source-Generated Proxy with WitRPC
Once the client is initialized:
var client = WitClientBuilder.Build(options =>
{
options.WithWebSocket("ws://localhost:5000");
options.WithJson();
options.WithEncryption();
});
await client.ConnectAsync(TimeSpan.FromSeconds(5), CancellationToken.None);Pass the generated class to GetService:
var service = client.GetService<IExampleService>(interceptor => new ServiceProxy(interceptor));This overload lives in the core client package, so a client on this path needs no proxy runtime at all. Beyond this call, the rest of the workflow with WitRPC remains unchanged.
Using the Dynamic Proxy
The runtime path needs no attribute and no generator, but it does need the package that carries Castle DynamicProxy:
dotnet add package OutWit.Communication.Client.DynamicProxyvar 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 and existing call sites compile unchanged. If you are upgrading a project written against WitRPC 2.3 or earlier and GetService<T>() suddenly does not compile, this missing package reference is why.
Advantages of the Source-Generated Proxy
Compatibility with AoT: source-generated proxies work in Ahead-of-Time compilation environments, such as Blazor WebAssembly and .NET Native AOT, and survive trimming.
Improved Startup Performance: since the proxy code is generated at compile time, nothing is emitted at runtime, resulting in faster application startup.
No Dependency on Reflection.Emit: unlike DynamicProxy, source-generated proxies do not rely on Reflection.Emit, making them suitable for platforms restricting dynamic IL generation.
Smaller dependency graph: the core client carries no Castle.Core, so a client on this path pulls in no runtime code-generation library at all.
Disadvantages of the Source-Generated Proxy
Less Flexible: the proxy is fixed at compile time, making it less adaptable for scenarios where runtime behavior needs dynamic adjustment.
No generic methods: the generator does not support them, while DynamicProxy does.
Increased Build Complexity: using source generators introduces additional build steps and dependencies.
Conclusion
The choice depends on your application's requirements. For AoT environments, trimmed builds, or anywhere dynamic IL generation is unavailable, source-generated proxies are the answer. DynamicProxy remains an excellent choice for maximum flexibility and ease of use in JIT-compiled applications, and it is one package reference away.
The two paths are not exclusive: different clients in the same solution can each pick their own. Client Proxies in the guides covers both in reference form, including the comparison table and the migration note for 2.4.0.