Skip to content
Bruno Sonnino
Menu
  • Home
  • About
Menu

Aspect Oriented Programming with DispatchProxy

Posted on 29 October 2022

Sometime ago, I wrote this article for the MSDN Magazine, about Aspect Oriented Programming and how it could solve cross-cutting concerns in your application, like:

  • Authentication
  • Logging
  • Data audit
  • Data validation
  • Data caching
  • Performance measuring

The article shows how to use the Decorator pattern and the RealProxy class to create a Dynamic Proxy to solve these issues in a simple manner. If you want to have a full introduction on the subject, I suggest that you take a look at the article. The time has passed, things changed a lot and we are now close to the introduction of .NET 7. The RealProxy class doesn't exist anymore, as it's based in Remoting, which was not ported to .NET Core. Fortunately, we still have the System.Reflection.DispatchProxy class that can solve the problem.

With this class, we can still write proxies that decorate our classes and allow us to implement AOP in our programs. In this article, we will use the DispatchProxy class to create a dynamic proxy that allows us to implement a filter for the methods to be executed and execute other functions before and after the method execution.

In the command prompt, create a new console app with:

dotnet new console -o DynamicProxy
cd DynamicProxy
code .
PowerShell

In Program.cs, we will define a Customer record (put it at the end of the code):

record Customer(string Id, string Name, string Address);
C#

Then, add a new Repository.cs file and add an IRepository interface in it:

public interface IRepository<T>
{
    void Add(T entity);
    void Delete(T entity);
    IEnumerable<T> GetAll();
}
C#

The next step is to create the generic class Repository that implements this interface:

public class Repository<T> : IRepository<T> 
{
    private readonly List<T> _entities = new List<T>();
    public void Add(T entity)
    {
        _entities.Add(entity);
        Console.WriteLine("Adding {0}", entity);
    }

    public void Delete(T entity)
    {
        _entities.Remove(entity);
        Console.WriteLine("Deleting {0}", entity);
    }

    public IEnumerable<T> GetAll()
    {
        Console.WriteLine("Getting entities");
        foreach (var entity in _entities)
        {
            Console.WriteLine($"  {entity}");
        }
        return _entities;
    }
}
C#

As you can see, our repository class is a simple class that will store the entities in a list, delete and retrieve them.

With this class created, we can add the code to use it in Program.cs:

Console.WriteLine("***\r\n Begin program\r\n");
var customerRepository = new Repository<Customer>();
var customer = new Customer(1, "John Doe", "1 Main Street");
customerRepository.Add(customer);
customerRepository.GetAll();
customerRepository.Delete(customer);
customerRepository.GetAll();
Console.WriteLine("\r\nEnd program\r\n***");
C#

If you run this program, you will see something like this:

Now, let's say we want to implement logging to this class, and have a log entry for every time it enters a method and another entry when it exits. We could do that manually, but it would be cumbersome to add logging before and after every method.

Using the DispatchProxy class, we can implement a proxy that will add logging to any class that implements the IRepository interface. Create a new file RepositoryLoggerProxy.cs and add this code:

using System.Reflection;

class RepositoryLogger<T> : DispatchProxy where T : class
{
    T? _decorated;

    public T? Create(T decorated)
    {
        var proxy = Create<T, RepositoryLogger<T>>() as RepositoryLogger<T>;
        if (proxy != null)
        {
            proxy._decorated = decorated;
        }
        return proxy as T;
    }

    protected override object? Invoke(MethodInfo? methodInfo, object?[]? args)
    {
        if (methodInfo == null)
        {
            return null;
        }

        Log($"Entering {methodInfo.Name}");
        try
        {
            var result = methodInfo.Invoke(_decorated, args);
            Log($"Exiting {methodInfo.Name}");
            return result;
        }
        catch
        {
            Log($"Error {methodInfo.Name}");
            throw;
        }
    }

    private static void Log(string msg)
    {
        Console.ForegroundColor = msg.StartsWith("Entering") ? ConsoleColor.Blue :
            msg.StartsWith("Exiting") ? ConsoleColor.Green : ConsoleColor.Red;
        Console.WriteLine(msg);
        Console.ResetColor();
    }
}
C#

The RepositoryLogger class inherits from DispatchProxy and has a Create method that will create an instance of a class that implements the interface that's decorated. When we call the methods of this class, they are intercepted by the overriden Invoke method and we can add the logging before and after executing the method.

To use this new class, we can use something like:

Console.WriteLine("***\r\n Begin program\r\n");
var customerRepository = new Repository<Customer>();
var customerRepositoryLogger = new RepositoryLogger<IRepository<Customer>>().Create(customerRepository);
if (customerRepositoryLogger == null)
{
    return;
}
var customer = new Customer(1, "John Doe", "1 Main Street");
customerRepositoryLogger.Add(customer);
customerRepositoryLogger.GetAll();
customerRepositoryLogger.Delete(customer);
customerRepositoryLogger.GetAll();
Console.WriteLine("\r\nEnd program\r\n***");
C#

Now, running the code, we get:

We have logging entering and exiting the class without having to change it. Remove logging is as simple as changing one line of code.

With this knowledge, we can extend our proxy class to do any action we want. To add actions before, after and on error is just a matter of passing them in the creation of the proxy. We can create a DynamicProxy class with this code:

using System.Reflection;

class DynamicProxy<T> : DispatchProxy where T : class
{
    T? _decorated;
    private Action<MethodInfo>? _beforeExecute;
    private Action<MethodInfo>? _afterExecute;
    private Action<MethodInfo>? _onError;
    private Predicate<MethodInfo> _shouldExecute;

    public T? Create(T decorated, Action<MethodInfo>? beforeExecute, 
        Action<MethodInfo>? afterExecute, Action<MethodInfo>? onError, 
        Predicate<MethodInfo>? shouldExecute)
    {
        var proxy = Create<T, DynamicProxy<T>>() as DynamicProxy<T>;
        if (proxy == null)
        {
            return null;
        }
        proxy._decorated = decorated;
        proxy._beforeExecute = beforeExecute;
        proxy._afterExecute = afterExecute;
        proxy._onError = onError;
        proxy._shouldExecute = shouldExecute ?? (s => true);
        return proxy as T;
    }

    protected override object? Invoke(MethodInfo? methodInfo, object?[]? args)
    {
        if (methodInfo == null)
        {
            return null;
        }
        if (!_shouldExecute(methodInfo))
        {
            return null;
        }
        _beforeExecute?.Invoke(methodInfo);
        try
        {
            var result = methodInfo.Invoke(_decorated, args);
            _afterExecute?.Invoke(methodInfo);
            return result;
        }
        catch
        {
            _onError?.Invoke(methodInfo);
            throw;
        }
    }
}
C#

In the Create method, we pass the actions we want to execute before after and on error after each method. We can also pass a predicate to filter the methods we don't want to execute. To use this new class, we can do something like this:

Console.WriteLine("***\r\n Begin program\r\n");
var customerRepository = new Repository<Customer>();
var customerRepositoryLogger = new DynamicProxy<IRepository<Customer>>().Create(customerRepository,
    s => Log($"Entering {s.Name}"),
    s => Log($"Exiting {s.Name}"),
    s => Log($"Error {s.Name}"),
    s => s.Name != "GetAll");
if (customerRepositoryLogger == null)
{
    return;
}
var customer = new Customer(1, "John Doe", "1 Main Street");
customerRepositoryLogger.Add(customer);
customerRepositoryLogger.GetAll();
customerRepositoryLogger.Delete(customer);
customerRepositoryLogger.GetAll();
Console.WriteLine("\r\nEnd program\r\n***");

static void Log(string msg)
{
    Console.ForegroundColor = msg.StartsWith("Entering") ? ConsoleColor.Blue :
        msg.StartsWith("Exiting") ? ConsoleColor.Green : ConsoleColor.Red;
    Console.WriteLine(msg);
    Console.ResetColor();
}
C#

Executing this code will show something like:

Note that, with this code, the method GetAll isn't executed, as it was filtered by the predicate.

As you can see, this is a very powerful class, as it can implement many different aspects for any interface (the DispatchProxy class only works with interfaces). For example, if I want to create my own mocking framework, where I don't execute any method of a class, I can change the code of the Invoke method to

protected override object? Invoke(MethodInfo? methodInfo, object?[]? args)
{
    if (methodInfo == null)
    {
        return null;
    }
    _beforeExecute?.Invoke(methodInfo);
    try
    {
        object? result = null;
        if (_shouldExecute(methodInfo))
        {
            result = methodInfo.Invoke(_decorated, args);
        }
        _afterExecute?.Invoke(methodInfo);
        return result;
    }
    catch
    {
        _onError?.Invoke(methodInfo);
        throw;
    }
}
C#

And create the proxy with something like this:

var customerRepositoryLogger = new DynamicProxy<IRepository<Customer>>().Create(customerRepository,
    s => Log($"Entering {s.Name}"),
    s => Log($"Exiting {s.Name}"),
    s => Log($"Error {s.Name}"),
    s => false);
C#

In this case, the real functions won't be called, just the methods before and after the call:

As you can see, the DispatchProxy class allows the creation of powerful classes that add aspects to your existing classes, without having to change them. With the DynamicProxy class you just have to add the actions to execute and the filter for the functions to be executed.

All the source code for this article is at https://github.com/bsonnino/DynamicProxy

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • May 2025
  • December 2024
  • October 2024
  • August 2024
  • July 2024
  • June 2024
  • November 2023
  • October 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • June 2022
  • April 2022
  • March 2022
  • February 2022
  • January 2022
  • July 2021
  • June 2021
  • May 2021
  • April 2021
  • March 2021
  • February 2021
  • January 2021
  • December 2020
  • October 2020
  • September 2020
  • April 2020
  • March 2020
  • January 2020
  • November 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • April 2019
  • March 2019
  • February 2019
  • January 2019
  • December 2018
  • November 2018
  • October 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • November 2017
  • October 2017
  • September 2017
  • August 2017
  • June 2017
  • May 2017
  • March 2017
  • February 2017
  • January 2017
  • December 2016
  • November 2016
  • October 2016
  • September 2016
  • August 2016
  • July 2016
  • June 2016
  • May 2016
  • April 2016
  • March 2016
  • February 2016
  • October 2015
  • August 2013
  • May 2013
  • February 2012
  • January 2012
  • April 2011
  • March 2011
  • December 2010
  • November 2009
  • June 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • July 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007
  • July 2007
  • Development
  • English
  • Português
  • Uncategorized
  • Windows

.NET AI Algorithms asp.NET Backup C# Debugging Delphi Dependency Injection Desktop Bridge Desktop icons Entity Framework JSON Linq Mef Minimal API MVVM NTFS Open Source OpenXML OzCode PowerShell Sensors Silverlight Source Code Generators sql server Surface Dial Testing Tools TypeScript UI Unit Testing UWP Visual Studio VS Code WCF WebView2 WinAppSDK Windows Windows 10 Windows Forms Windows Phone WPF XAML Zip

  • Entries RSS
  • Comments RSS
©2025 Bruno Sonnino | Design: Newspaperly WordPress Theme
Menu
  • Home
  • About