This walkthrough builds a minimal working example: a service with methods and events, a server hosting it over WebSocket, and a client that calls a method and receives an event callback. By the end you will have seen the full WitRPC loop, from interface to remote call to real-time notification.

Installation

Add the WebSocket transport packages to the two projects. Each transport package brings in the WitRPC core with it.

bash
# Server project:
dotnet add package OutWit.Communication.Server.WebSocket

# Client project:
dotnet add package OutWit.Communication.Client.WebSocket

Step 1: Define a service interface

The service contract is a shared C# interface. It declares the methods clients can call and the events they can subscribe to:

csharp
public interface IExampleService
{
    // Events the server raises to notify clients:
    event Action ProcessingStarted;
    event Action<double> ProgressChanged;
    event Action<string> ProcessingCompleted;

    // Methods clients call remotely:
    bool StartProcessing();
    void StopProcessing();
    Task<string> ProcessDataAsync(string data);
}

Both sides compile against this interface. The server implements it; the client receives a proxy for it.

Step 2: Implement the service

The implementation lives on the server and contains the actual logic. Raising an event here delivers it to every subscribed client:

csharp
public class ExampleService : IExampleService
{
    // Initialize events with empty delegates to avoid null checks
    public event Action ProcessingStarted = delegate { };
    public event Action<double> ProgressChanged = delegate { };
    public event Action<string> ProcessingCompleted = delegate { };

    public bool StartProcessing()
    {
        ProcessingStarted();   // Notify subscribers
        return true;
    }

    public void StopProcessing()
    {
        // Stop logic omitted in this minimal example
    }

    public async Task<string> ProcessDataAsync(string data)
    {
        await Task.Delay(100);              // Simulate work
        return $"Processed: {data}";
    }
}

In a fuller implementation, ProcessDataAsync would report progress through ProgressChanged(percent) along the way and finish with ProcessingCompleted(result); the events are declared here so the client side can show the subscription pattern.

Step 3: Set up the server

The server is configured through a fluent builder. This example listens on WebSocket port 5000, serializes with JSON, enables encryption, and requires an access token:

csharp
var server = WitServerBuilder.Build(options =>
{
    options.WithService(new ExampleService());
    options.WithWebSocket("http://localhost:5000", maxClients: 100);
    options.WithJson();
    options.WithEncryption();
    options.WithAccessToken("your-secret-token");
});

server.StartWaitingForConnection();

WithService registers the implementation clients will reach. After the builder runs, StartWaitingForConnection() begins listening for clients.

Step 4: Connect the client

The client mirrors the server's configuration: same transport, same serializer, same security settings. Note the ws:// scheme on the client side:

csharp
var client = WitClientBuilder.Build(options =>
{
    options.WithWebSocket("ws://localhost:5000");
    options.WithJson();
    options.WithEncryption();
    options.WithAccessToken("your-secret-token");
});

await client.ConnectAsync(TimeSpan.FromSeconds(5), CancellationToken.None);
var service = client.GetService<IExampleService>();

ConnectAsync establishes the connection, waiting up to five seconds. GetService<IExampleService>() then returns the proxy: an object that implements the interface locally and forwards every call to the server.

Security settings must match on both ends. A client without the right token, or with encryption settings different from the server's, is rejected during the handshake.

Step 5: Call a method and receive an event

With the proxy in hand, remote calls look like local ones, and server events arrive at ordinary C# event handlers:

csharp
// Subscribe before calling methods, so no callback is missed
service.ProcessingStarted += () => Console.WriteLine("Processing Started");
service.ProgressChanged  += progress => Console.WriteLine($"Progress: {progress}%");
service.ProcessingCompleted += result => Console.WriteLine($"Completed: {result}");

// Invoke remote methods
service.StartProcessing();
var result = await service.ProcessDataAsync("Hello");
Console.WriteLine($"Result from server: {result}");

Calling service.StartProcessing() executes ExampleService.StartProcessing() on the server, which raises ProcessingStarted; the client's handler prints its message in real time. ProcessDataAsync("Hello") runs on the server and returns its result to the awaiting client. The output:

Processing Started
Result from server: Processed: Hello

What you built

A shared interface, a server hosting its implementation, and a client calling it through a proxy, with a server-raised event delivered back in real time. That is the whole WitRPC pattern; everything else in the framework builds on it.

From here: