The developer requested an instance of a remote service and the remoting infrastructure returned a proxy (a Transparent Proxy).
Depending on the activation mode, each call to this proxy service was handled in the server by a new instance or by the same instance. This was also related to the life cycle of the instances on the remoting server.
This can be seen summarized in the WellKnownObjectMode enumeration.
publicenumWellKnownObjectMode{//Every call is serviced by the same instanceSingleton=1,//Every call is serviced by a new instance.SingleCall}
Problems 😰
With the arrival of NET, Microsoft abandoned this technology.
How could we migrate this technology to NET in a Windows Forms Application with a SingleCall Remoting implementation?
Microsoft offered several alternatives, for example streamjsonrpc
The StreamJsonRpc library offers JSON-RPC 2.0 over any .NET Stream, WebSocket, or Pipe. With bonus support for request cancellation, client proxy generation, and more.
vs-StreamJsonRpc
StreamJsonRpc
StreamJsonRpc is a cross-platform, .NET portable library that implements the
JSON-RPC wire protocol.
It works over Stream, WebSocket, or System.IO.Pipelines pipes, independent of the underlying transport.
gRPC is a modern, open source, high-performance remote procedure call (RPC) framework that can run anywhere. gRPC enables client and server applications to communicate transparently, and simplifies the building of connected systems.
gRPC functionality for .NET Core 3.0 or later includes:
Grpc.AspNetCore – An ASP.NET Core framework for hosting gRPC services. gRPC on ASP.NET Core integrates with standard ASP.NET Core features like logging, dependency injection (DI), authentication and authorization.
Grpc.Net.Client – A gRPC client for .NET Core that builds upon the familiar HttpClient. The client uses new HTTP/2 functionality in .NET Core.
Grpc.Net.ClientFactory – gRPC client integration with HttpClientFactory. The client factory allows gRPC clients to be centrally configured and injected into your app with DI.
But what happens if you are not allowed to use an IOC? (insert multiple and diverse causes here) 😭
Not everything is lost.
We can create a class that is a RealProxy that makes the appropriate calls to the remote server and returns a TransparentProxy, which as if it were a black box allows us to invoke the methods of our interface and our new server will respond.
Suppose that in our legacy application we have a static class called RemotingServiceFactory that returns instances of IPayment and other services.
One example of use would be as follows:
// This is a Transparent Proxy returned by // the RemotingServiceFactory classIPaymentproxy=RemotingServiceFactory.GetService<IPayment>();Basketbasket=new();basket.AddProduct(newProduct{Name="Bananas",Quantity=10});// This is the actual call to remote servicePaymentresponse=awaitproxy.Checkout(basket);
We can modify RemotingServiceFactory.GetService to forward
calls from some services to our new server and be able to incrementally migrate our services.
We store the types of those services that we have migrated in a dictionary called _httpServices so that if when requesting a service, its type is among them, we return our new TransparentProxy.
Wait a minute, what is that interface called IInterceptor?
It's a class that intercepts the calls to our IPayment and other services and make the real call without the need to implement that interface 😎
publicclassSingleDataSetInterceptor:IInterceptor{......// This method is called every time we call a method in our interfacepublicvoidIntercept(IInvocationinvocation){invocation.ReturnValue=InvokeHttpPostAsync(invocation.Method.DeclaringType?.FullName,invocation.Method.Name,(DataSet)invocation.GetArgumentValue(0),invocation.Method.ReturnType);}privateasyncTask<DataSet>InvokeHttpPostAsync(string?classPath,stringmethodName,DataSet?parameter,TypereturnType){ArgumentNullException.ThrowIfNull(classPath);ArgumentNullException.ThrowIfNull(methodName);ArgumentNullException.ThrowIfNull(parameter);usingHttpResponseMessageresponse=await_httpClient.PostAsync($"/{classPath}/{methodName}",newObjectContent(parameter.GetType(),parameter,_httpSendFormatter));response.EnsureSuccessStatusCode();return(DataSet)awaitresponse.Content.ReadAsAsync(returnType,_httpReceiveFormatters);}}
Now, we need some magic to glue all the parts, and that glue is the fantastic library Castle.Core
Castle.Core includes a lightweight runtime proxy generator that we can use to create TransparentProxies of our interfaces without being implemented.
Castle Core, including Castle DynamicProxy, Logging Services and DictionaryAdapter
Castle Core
Castle Core provides common Castle Project abstractions including logging services. It also features Castle DynamicProxy a lightweight runtime proxy generator, and Castle DictionaryAdapter.
ProxyGeneratorgenerator=newProxyGenerator();varinterceptor=newSingleDataSetInterceptor(...);// This is magic !!IPaymentproxy=generator.CreateInterfaceProxyWithoutTarget<IPayment>(interceptor);Paymentresponse=awaitproxy.Checkout(...);
If we include this in the private method GetHttpService of the static class RemotingServiceFactory then we have completed our journey:
//_generator is a ProxyGeneratorprivatestaticTGetHttpService<T>(IInterceptorinterceptor)whereT:class{return_generator.CreateInterfaceProxyWithoutTarget<T>(interceptor);}