Terminology
NET: net core 6.0+
NETFRAMEWORK: classic or legacy .NetFramework
History π
A technology called Remoting was once in fashion. This technology was built as an alternative to DCOM.
It was available for NETFRAMEWORK.
It provided classes and interfaces that allowed developers to create and configure distributed applications.
These classes and interfaces lived in the namespace System.Runtime.Remoting
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.
public enum WellKnownObjectMode
{
//Every call is serviced by the same instance
Singleton = 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
microsoft / vs-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.
Bonus features beyond the JSON-RPC spec include:
- Request cancellation
- .NET Events as notifications
- Dynamic client proxy generation
- Support for compact binary serialization via MessagePack
- Pluggable architecture for custom message handling and formatting.
Learn about the use cases for JSON-RPC and how to use this library from our documentation.
Or gRpc
grpc / grpc-dotnet
gRPC for .NET
gRPC for .NET
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.
For more information, see An introduction to gRPC on .NET.
gRPC for .NET is now the recommended implementation!
Starting from May 2021, gRPC for .NET is the recommendedβ¦
Alternate Solution π
Personally I find it easier to forward http calls to a REST API if you had a stateless server.
You have more control and i think it is more standard.
Now imagine that you have 1000+ interfaces with their respective methods.
How can you redirect those calls to our new REST server?
Let's take an example to better understand the problem. Below we can see a payment service with a method:
public interface IPayment
{
Task<Payment> Checkout(Basket basket);
}
One assumption here (and most of the methods) is that Basket and Payment classes inherits from System.Data.Dataset π¨
One possibility is to use the Service Locator pattern and Inversion of Control.
Since we have these interfaces, we can decorate them with some attribute that indicates that they participate in IOC.
Then we can call an instance calling something like:
IPayment payment = ServiceLocator.Current.GetInstance<IPayment>();
payment.Checkout(...)
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 class
IPayment proxy = RemotingServiceFactory.GetService<IPayment>();
Basket basket = new ();
basket.AddProduct(new Product { Name = "Bananas", Quantity = 10 });
// This is the actual call to remote service
Payment response = await proxy.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.
public static T GetService<T>() where T : class
{
if (_httpServices.TryGetValue(typeof(T), out IInterceptor? interceptor))
{
// New
return GetHttpService<T>(interceptor);
}
else
{
// Legacy
return GetRemotingService<T>();
}
}
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 π
public class SingleDataSetInterceptor : IInterceptor
{
...
...
// This method is called every time we call a method in our interface
public void Intercept(IInvocation invocation)
{
invocation.ReturnValue = InvokeHttpPostAsync(invocation.Method.DeclaringType?.FullName, invocation.Method.Name
, (DataSet)invocation.GetArgumentValue(0), invocation.Method.ReturnType);
}
private async Task<DataSet> InvokeHttpPostAsync(string? classPath, string methodName, DataSet? parameter, Type returnType)
{
ArgumentNullException.ThrowIfNull(classPath);
ArgumentNullException.ThrowIfNull(methodName);
ArgumentNullException.ThrowIfNull(parameter);
using HttpResponseMessage response = await _httpClient.PostAsync($"/{classPath}/{methodName}"
, new ObjectContent(parameter.GetType(), parameter, _httpSendFormatter));
response.EnsureSuccessStatusCode();
return (DataSet)await response.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.
castleproject / Core
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.
See the documentation.
Releases
See the Releases.
Debugging symbols are available in symbol packages in the AppVeyor build artifacts since version 4.1.0. For example, here are the artifacts for 4.1.0.
License
Castle Core is Β© 2004-2022 Castle Project. It is free software, and may be redistributed under the terms of the Apache 2.0 license.
Contributing
Browse the contributing section of our Home repository to get involved.
Building
Platforms
NuGet Feed
Windows & Linux
Preview Feed
On Windows
build.cmd
Compilation requires a C# 9 compiler, an up-to-date .NET Core SDK, and MSBuild 15+ (which should be included in the former).
Running the unit tests additionally requires the .NET Framework 4.6.2+ as well as the .NET Core 2.1, 3.1 and 6.0 runtimes toβ¦
ProxyGenerator generator = new ProxyGenerator();
var interceptor = new SingleDataSetInterceptor(...);
// This is magic !!
IPayment proxy = generator.CreateInterfaceProxyWithoutTarget<IPayment>(interceptor);
Payment response = await proxy.Checkout(...);
If we include this in the private method GetHttpService of the static class RemotingServiceFactory then we have completed our journey:
//_generator is a ProxyGenerator
private static T GetHttpService<T>(IInterceptor interceptor) where T : class
{
return _generator.CreateInterfaceProxyWithoutTarget<T>(interceptor);
}
As allways all the code is hosted in Github.
Be kind and love your family π
InterfaceProxy
Create lightweight dynamic proxies of Interfaces without Classes through Castle.Core
See RemotingServiceFactory.GetService and SingleDataSetInterceptor
See post in https://dev.to/netdefender/interface-proxy-5djm
Top comments (0)