AdvancedRpcLib 1.0.4
See the version list below for details.
dotnet add package AdvancedRpcLib --version 1.0.4
NuGet\Install-Package AdvancedRpcLib -Version 1.0.4
<PackageReference Include="AdvancedRpcLib" Version="1.0.4" />
paket add AdvancedRpcLib --version 1.0.4
#r "nuget: AdvancedRpcLib, 1.0.4"
// Install AdvancedRpcLib as a Cake Addin #addin nuget:?package=AdvancedRpcLib&version=1.0.4 // Install AdvancedRpcLib as a Cake Tool #tool nuget:?package=AdvancedRpcLib&version=1.0.4
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 | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. 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. |
-
.NETFramework 4.8
- Microsoft.Extensions.Logging.Abstractions (>= 3.1.5)
-
.NETStandard 2.0
- Microsoft.Extensions.Logging.Abstractions (>= 3.1.5)
- System.IO.Pipes.AccessControl (>= 4.5.1)
- System.Reflection.Emit (>= 4.7.0)
-
.NETStandard 2.1
- Microsoft.Extensions.Logging.Abstractions (>= 3.1.5)
- System.IO.Pipes.AccessControl (>= 4.5.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
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