Example of Aspect Oriented Paradigm with DispatchProxy Class

In this article, we will understand how to implement cross-cutting concerns (CCC) with a dynamic proxy class.

In the .NET environment, there are many types of AOP implementations. In our case, we have to focus on dispatching any classes through the proxy object.

As you know, AOP is a paradigm that provides better modularity with SoC (separation of concerns). It helps to increase loose coupling by adding reusable code without modifying existing ones.

In our example, we will use the DispatchProxy class, which was created in the System.Reflection namespace as a .NET Standard library.

Cross-Cutting Concerns Example In .NET Application

DispatchProxy class works based on Interfaces. It provides dynamic wrapping of any type with additional code implementations.

That part is understandable as to what DispatchProxy is, but we need to be careful because one of the old alternatives is the RealProxy class, which provides the same thing but has more than it. RealProxy has cross-process remoting, which DispatchProxy is not.

Let’s start with an example.

Assume that we have two different application services. EmailMessageSender and ServerInfoRetriever. As you can see, the names represent what they are able to do.

public interface IEmailMessageSender
{
    public bool TrySendMessage(string to, string subject, string message);
}
public class EmailMessageSender : IEmailMessageSender
{
    // Simulation
    public bool TrySendMessage(string to, string subject, string message)
    {
        Console.WriteLine($"Message sent to the {to}");
        return true; // fake info
    }
}
public record ServerInfo(string State, DateTime ActivationDate, string Version);
 public interface IServerInfoRetriever
 {
     public ServerInfo GetInfo(IPAddress address);
 }
 public class ServerInfoRetriever : IServerInfoRetriever
 {
     // Simulation
     public ServerInfo GetInfo(IPAddress address)
     {
         Console.WriteLine($"{address.ToString()} Server Info Retrieved!");
         return new ServerInfo("In process", DateTime.Now, "1.0.0"); // fake info
     }
 }

To implement our DispatchProxy, we should start deriving from it and then implement/write our logic inside it.

public class LoggingDecoratorProxy<T> : DispatchProxy
{
    private readonly Logger _logger;
    public T Target { get; internal set; }
    public LoggingDecoratorProxy() : base()
    {
        _logger = new LoggerConfiguration()
            .WriteTo.Console().CreateLogger(); // used serilog
    }
    protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
    {
        try
        {
            _logger.Information("{TypeName}.{MethodName}({Arguments})", targetMethod.DeclaringType.Name, targetMethod.Name, args);
            var result = targetMethod.Invoke(Target, args);
            _logger.Information("{TypeName}.{MethodName} returned -> {ReturnValue}", targetMethod.DeclaringType.Name, targetMethod.Name, result);

            return result;
        }
        catch (TargetInvocationException exc)
        {

            _logger.Warning(exc.InnerException, "{TypeName}.{MethodName} threw exception: {Exception}", targetMethod.DeclaringType.Name, targetMethod.Name, exc.InnerException);
            throw exc.InnerException;
        }
    }
}

So, the code above shows that we have to inherit from DispatchProxy and override the Invoke method (which provides invocation dynamically through the reflection) implement additional logic then call the main object method through the reflection.

Everything is clear. We are almost ready to finish.

public static class LoggingDecoratorProxyFactory
{
    public static TInterface Create<TInterface, TConcrete>(TConcrete instance) where TConcrete : class, TInterface where TInterface : class
    {
        if (!typeof(TInterface).IsInterface)
            throw new Exception($"{typeof(TInterface).Name} must be interface!");
        LoggingDecoratorProxy<TInterface> proxy = LoggingDecoratorProxy<TInterface>.Create<TInterface, LoggingDecoratorProxy<TInterface>>()
                    as LoggingDecoratorProxy<TInterface>;
        proxy.Target = instance;
        return proxy as TInterface;
    }
}

The last code provides creating logging cross-cutting concerns through the proxy object. The input param is the instance of the interface that will decorate our proxy logging decorator. Inside the factory, decorate dynamically with DispatchProxy.Create method, then wrapped interface returned to the client.

The last part is the app, which is running and getting results.

IEmailMessageSender emailSender = LoggingDecoratorProxyFactory.Create<IEmailMessageSender, EmailMessageSender>(new EmailMessageSender());
emailSender.TrySendMessage("[email protected]", "Test", "Hi Rasul");

Console.WriteLine("------------------------------------------------------------------------------------------");

IServerInfoRetriever retriever = LoggingDecoratorProxyFactory.Create<IServerInfoRetriever, ServerInfoRetriever>(new ServerInfoRetriever());
retriever.GetInfo(IPAddress.Parse("127.0.0.1"));

Console result

As you can see in our sample, we created a cross-cutting concern (logging invocation) and decorated dynamically through the DispatchProxy in runtime with any objects. This way, DispatchProxy allowed us to implement AOP in any desired type.

Conclusion

The DispatchProxy class is a powerful tool in the .NET ecosystem for creating dynamic proxies, allowing developers to intercept and modify method invocations at runtime. This capability enables flexible and clean implementations of cross-cutting concerns like logging, caching, and performance monitoring without cluttering the core business logic. By leveraging DispatchProxy, developers can maintain a modular and decoupled codebase, enhancing both maintainability and extensibility.

Stay tuned!