AdvancedRpcLib 1.0.8

dotnet add package AdvancedRpcLib --version 1.0.8
NuGet\Install-Package AdvancedRpcLib -Version 1.0.8
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AdvancedRpcLib" Version="1.0.8" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add AdvancedRpcLib --version 1.0.8
#r "nuget: AdvancedRpcLib, 1.0.8"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install AdvancedRpcLib as a Cake Addin
#addin nuget:?package=AdvancedRpcLib&version=1.0.8

// Install AdvancedRpcLib as a Cake Tool
#tool nuget:?package=AdvancedRpcLib&version=1.0.8

AdvancedRPC

AdvancedRPC is a remote procedure call library for .NET. It differs from common solutions like REST, GRPC or WebSockets in that it supports an object hierarchy similar to .NET Remoting. I wrote the library mainly as a replacement for .NET Remoting to make our corporate application ready for .NET Core. It relies heavily on the ability to make remote procedure calls on objects.

Features

  • Communication via TCP and Named Pipes
  • Support for impersonation with Named Pipes
  • Deep object hierarchies
  • Events and callbacks
  • No need for serialization annotations, just publish an interface
  • Support for multiple clients with notification on connection and disconnection
  • .NET 4.8, .NET Standard 2.0 and .NET Standard 2.1
  • Communication between .NET Framework, .NET Core and Unity apps (not tested on Xamarin yet, but should work there too)
  • Very easy to setup: No need to start a web service or define proto files. Just define an interface that is shared between applications and you are ready.

Example

Common interface definition

public interface IRpcServer 
{
    IRpcObject CreateObject(string name);
}

public interface IRpcObject 
{
    string Name { get; }

    void ChangeName(string name);

    event NameChanged;
}

Server implementation

class RpcServer : IRpcServer 
{
    IRpcObject CreateObject(string name)
    {
        return RpcObjectImpl(name);
    }
}

class RpcObjectImpl : IRpcObject 
{
    public RpcObjectImpl(string name)
    {
        Name = name;
    }

    string Name { get; private set; }

    void ChangeName(string name)
    {
        Name = name;
        NameChanged?.Invoke(this, EventArgs.Empty);
    }

    event NameChanged;
}

class Program
{
    static async Task Main(string[] args)
    {
        var server = new NamedPipeRpcServerChannel(new BinaryRpcSerializer(), 
                            new RpcMessageFactory(), "myipcchannelname");
        server.ObjectRepository.RegisterSingleton<RpcServer>();
        await server.ListenAsync();

        Console.WriteLine("Press key to quit");
        Console.ReadKey();
    }
}

Client implementation

class Program
{
    static async Task Main(string[] args)
    {
        var client = new NamedPipeRpcClientChannel(new BinaryRpcSerializer(), 
                            new RpcMessageFactory(), "myipcchannelname");        
        await client.ConnectAsync(TimeSpan.FromSeconds(5));

        var rpcServerObj = await client.GetServerObjectAsync<IRpcServer>();
        var nameObj = rpcServerObj.CreateObject("Jon Doe")
        nameObj.NameChanged += (sender, e) => Console.WriteLine(((IRpcObject)sender).Name);

        // This calls the method on the server and invokes
        // the event NameChanged on the client.
        nameObj.ChangeName("Jane Doe"); 

        Console.WriteLine("Press key to quit");
        Console.ReadKey();
    }
}

See unit tests for more advanced scenarios.

Ahead of time code generation

For platforms that do not support dynamic code generation (i.e. Unity, Xamarin iOS) it is necessary to generate the proxy code in advance. AdvancedRpcLib supports this by annotating RPC interface definitions with the AotRpcObjectAttribute and using the package AdvancedRpc.MSBuild. The package will then generate the proxy files and the class AotRpcObjects during build.

The generation of proxy objects is only supported for RPC clients. It might work in some scenarios for servers as well though, if you do not need events or delegates.

To use the generated proxies, use AotRpcObjectRepository instead of the default RpcObjectRepository. An examle would look like this:

[AdvancedRpcLib.AotRpcObject]
public interface IRpcServer 
{
    void DoSometing();
}

class Main 
{
    static async Task Main() 
    {
        IPAddress ip = ...
        int port = ...

        var rpcClientChannel = new TcpRpcClientChannel(
                        new BinaryRpcSerializer(),
                        new RpcMessageFactory(),
                        ip,
                        port,
                        new AotRpcObjectRepository(true, AotRpcObjects.GetImplementationTypes()),
                        () => new AotRpcObjectRepository(false, AotRpcObjects.GetImplementationTypes()));
        
        await rpcClientChannel.ConnectAsync();
        var rpcServer = await rpcClientChannel.GetServerObjectAsync<IRpcServer>();

        // rpcServer will be the pregenerated proxy type
        // from here everything is like normal...                        
    }
}

Some Notes

  • If you return a plain static object that doesn't need to know about server changes, use the Serializable attribute on the implementation. In that case the object will be serialized and copied to the client or server without creating a proxy object. This can be more efficient for data objects if you have a lot of properties and deep hierarchies. This behaves like a REST call.
  • Do not return or pass IEnumerable, as this will result in a remote call for every MoveNext when iterating over it. Instead, use an array in those cases.
  • Watch out for memory leaks. AdvancedRPC handles a lot of scenarios for you but take care to remove your event listeners.
  • CAREFUL! Every remote call can throw an exception if the server goes down. The same is true for events, if the clients disconnects.
  • If you transfer large objects and use .NET Framework 4.7.2 or 4.8 please add an AppCompat switch in your App.config to improve performance
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    
      <AppContextSwitchOverrides value="Switch.System.Runtime.Serialization.UseNewMaxArraySize=true" />
  </runtime>
</configuration>

Restrictions

  • Method overloads with same parameter count are not supported (yet). Overloads with different parameter count are possible though.
  • Named Pipe impersonation limitations:
    • doesn't work with .NET Standard 2.0 (for now)
    • only works on Windows
  • IEnumerable doesn't work for .NET Core (the interfaces use ByRef Values). There might be a workaround to support this.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.8 509 6/2/2022
1.0.7 406 5/19/2022
1.0.6 421 5/3/2022
1.0.5 313 12/14/2021
1.0.4 517 9/20/2020
1.0.3 490 8/29/2020
1.0.2 400 8/28/2020
1.0.1 409 8/26/2020
1.0.0 402 8/25/2020

1.0.8 Using Guid instead of int for instance ids to eliminate an inter process race condition
     1.0.7 Bug fixes
     1.0.6 Bug fixes
     1.0.5 Bug fixes
     1.0.4 Ahead of Time compilation of client proxy classes to support Unity
     1.0.3 Fixed Named Pipe Channel for .NET Standard and x86 targets (AccessViolationException)
     1.0.2 Performance optimizations for large argument object graphs
     1.0.1 Fixed Serializable objects
     1.0.0 Initial release