WitRPC

website·Indexed 31 days ago

https://witrpc.io

An interface-driven remote procedure call (RPC) framework for modern .NET (package family OutWit.Communication). It enables distributed communication between .NET components using shared C# interfaces, supporting various transports including WebSockets, TCP, Named Pipes, Memory-Mapped Files (MMF), and REST. Features include source-generated proxies, server-to-client events, automatic reconnection, and integration with ASP.NET Core dependency injection and Blazor WebAssembly.

Tokens
53.4K
Snippets
214
Records
288
Agent score
54%

What's inside WitRPC

  1. Overview of WitRPC IPC Framework

    WitRPC is a high-performance inter-process communication (IPC) framework for .NET designed as a modern successor to WCF. It allows developers to define service interfaces (Service Contracts) to establish full-duplex communication between processes. It is particularly useful for integrating legacy components (e.g., 32-bit processes) with modern platforms or running parallel independent processes on the same machine.

    Supported transports include:

    • Named Pipes
    • TCP
    • WebSocket
    • Memory-Mapped Files (MMF): Optimized for ultra-fast, low-latency duplex communication and large data transfers on a local machine.
  2. Overview of WitRPC RPC framework

    WitRPC (package family OutWit.Communication) is a remote procedure call (RPC) framework for .NET 6 through .NET 10, supporting Windows, Linux, macOS, and Blazor WebAssembly. It allows distributed components (separate processes, local network services, or internet applications) to communicate via a full-duplex connection where calls and notifications flow in both directions. The framework handles serialization, transport, security, and error handling, making remote service calls feel like local object interactions.
  3. Best practices for designing WitRPC service interfaces

    To maintain a scalable architecture, avoid 'kitchen sink' interfaces that combine unrelated functionality. Instead, keep interfaces focused on a single responsibility (e.g., separating Authentication from Profile management). This improves maintainability and allows for more granular service composition.
    // GOOD: Focused interfaces
    public interface IUserAuthService
    {
        Task<AuthResult> LoginAsync(string email, string password);
    }
    
    public interface IUserProfileService
    {
        Task<UserProfile> GetProfileAsync();
    }
    
    // BAD: Kitchen sink interface
    public interface IUserService
    {
        Task<AuthResult> LoginAsync(...);
        Task<UserProfile> GetProfileAsync(...);
        Task<Preferences> GetPreferencesAsync(...);
        Task<List<Activity>> GetActivityLogAsync(...);
    }
  4. Understand WitRPC's Proxy Generation mechanism

    WitRPC enables remote method calls by creating a proxy class at runtime or compile-time that implements the target service interface. When a developer calls client.GetService<T>(), WitRPC generates a proxy that intercepts all method calls and routes them through an interceptor to be sent over the network.

    There are two implementation paths:

    1. Runtime Generation: Uses Castle DynamicProxy (via the OutWit.Communication.Client.DynamicProxy package). This is the default for non-AOT scenarios.
    2. Compile-time Generation: Uses a Roslyn source generator, which is preferred for AOT-publishable applications to avoid the overhead and dependencies of Castle.Core.
    // Usage
    ITaskService service = client.GetService<ITaskService>();
    bool result = service.StartTask("MyTask");
  5. Orchestrate multiple services via Service Composition

    In a composite architecture, a high-level 'orchestrator' service can depend on other registered services to execute complex workflows. This keeps individual services focused while allowing the orchestrator to manage the business logic across multiple domains.
    public class CheckoutService : ICheckoutService
    {
        private readonly IInventoryService _inventory;
        private readonly IPaymentService _payment;
        private readonly IOrderService _orders;
    
        public CheckoutService(IInventoryService inventory, IPaymentService payment, IOrderService orders)
        {
            _inventory = inventory;
            _payment = payment;
            _orders = orders;
        }
    
        public async Task<CheckoutResult> ProcessCheckoutAsync(Cart cart)
        {
            if (!await _inventory.ReserveStockAsync(cart.Items)) return CheckoutResult.OutOfStock();
            var payment = await _payment.ChargeAsync(cart.Total);
            var order = await _orders.CreateOrderAsync(cart);
            return CheckoutResult.Success(order);
        }
    }
    
    // Register all involved services in the composite
    options.WithServices()
        .AddService<IInventoryService>(inventoryService)
        .AddService<IPaymentService>(paymentService)
        .AddService<IOrderService>(orderService)
        .AddService<ICheckoutService>(checkoutService)
        .Build();
  6. Compare gRPC and WitRPC architectural approaches

    gRPC uses a schema-first (contract-first) approach where APIs are defined in .proto files using Protocol Buffers. This allows for cross-language consistency (C#, Go, Java, Python, etc.) but requires a code generation step (protoc) and introduces more files into the project.

    WitRPC uses a code-first (interface-first) approach where the API is defined directly as a C# interface. This eliminates the need for schema files and code generation, providing native IDE support and faster iteration for .NET-to-.NET communication, but it is limited to the .NET ecosystem.

  7. NativeAOT Compatibility in WitRPC 3.0

    The wire path is now NativeAOT-ready. The encryption handshake, wire-model serializer registrations, and proxy type resolution no longer rely on reflection that would be removed during trimming. This allows AOT-published clients to complete encrypted round-trips against a live server.
  8. Compare WitRPC and gRPC for .NET applications

    WitRPC and gRPC offer different trade-offs for distributed .NET applications. gRPC is a contract-first framework using Protocol Buffers, ideal for polyglot environments and cloud-native infrastructure. WitRPC is an interface-first framework using C#, optimized for pure .NET stacks, local IPC, and rapid iteration.

    Key Comparison:

    • Language Support: gRPC supports multiple languages (Go, Python, Java, etc.); WitRPC is limited to .NET.
    • API Versioning: gRPC provides formal versioning via ProtoBuf field numbers; WitRPC relies on C# interface evolution.
    • Local IPC: WitRPC is significantly faster for local IPC (up to 40% faster) due to support for Memory-Mapped Files.
    • Developer Experience: WitRPC eliminates .proto files and code generation, allowing for faster iteration and direct IDE support.
    • Streaming: gRPC has formal bidirectional streaming; WitRPC uses C# events for server-to-client push.
  9. Implement a Host/Agent process model with OutWit.InterProcess

    The OutWit.InterProcess extension implements a Host/Agent model where a main application (the host) manages the lifecycle of child processes (agents). The host launches, monitors, and shuts down agents, communicating with them via standard WitRPC proxies. This architecture provides crash isolation (an agent crash doesn't take down the host), support for mixed platforms/runtimes, privilege separation, and a dynamic plugin architecture where modules can be replaced without restarting the application.
  10. Resources for implementing SignalR in .NET

    SignalR is part of ASP.NET Core and is used for real-time web functionality. Key resources include the official documentation for introduction and configuration, a classic chat example tutorial, and official samples available on GitHub.
  11. Migrate from WitRPC 2.x to 3.x

    Protocol 3 is not wire-compatible with 2.x. Follow these steps for migration:

    1. Update both client and server simultaneously (no mixed-mode support).
    2. Set WithMaxConcurrentRequests(1) on servers initially to ensure thread safety during audit.
    3. Explicitly mark idempotent methods using WithRetryPolicy to maintain retry behavior.
    4. Install specific serializer plugin packages if using MessagePack or ProtoBuf.
    5. Update REST integrations to use the new plain-JSON contract.
  12. Handle events across multiple services

    Events are contract-scoped. A client must subscribe to an event using the proxy of the specific interface that declares that event. Since version 3.0, the framework enforces this by stamping events with the contract ID; proxies for different contracts will drop events they do not declare.
    public interface INotificationService
    {
        event Action<string, string> NotificationReceived;
        void SendNotification(string userId, string message);
    }
    
    // Subscribe using the proxy that declares the event
    var notificationService = client.GetService<INotificationService>();
    notificationService.NotificationReceived += (userId, message) =>
    {
        Console.WriteLine($"Notification for {userId}: {message}");
    };