DEV Community

Volatile Delegate
Volatile Delegate

Posted on

Interface Proxy

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
}
Enter fullscreen mode Exit fullscreen mode

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

GitHub logo 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

codecov Join the chat at https://gitter.im/vs-streamjsonrpc/Lobby

StreamJsonRpc

NuGet package Build Status

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:

  1. Request cancellation
  2. .NET Events as notifications
  3. Dynamic client proxy generation
  4. Support for compact binary serialization via MessagePack
  5. 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

GitHub logo 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);
}
Enter fullscreen mode Exit fullscreen mode

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(...)
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>();
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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.

GitHub logo 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

NuGet

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(...);
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

As allways all the code is hosted in Github.

Be kind and love your family πŸ’–

Top comments (0)