DEV Community

Rasul
Rasul

Posted on

Example of Aspect-Oriented Paradigm by Dynamically Decorating Objects with DispatchProxy Class

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

In the .NET environment, there are many types of implementations of AOP. 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 loosely coupling with adding reusable code without modifying existing ones.

In our example, we will use DispatchProxy class which was created in 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 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
    }
}
Enter fullscreen mode Exit fullscreen mode
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
     }
 }
Enter fullscreen mode Exit fullscreen mode

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

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

The last code provides creating logging cross-cutting concerns through the proxy object. The input param is the instance of interface which will decorate to 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("resulhsn@gmail.com", "Test", "Hi Rasul");

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

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

Image description
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!

Top comments (0)