Joydip Kanjilal
Contributor

How to use DispatchProxy for AOP in .NET Core

how-to
Nov 14, 20247 mins
C#Development Libraries and FrameworksMicrosoft .NET

Take advantage of the DispatchProxy class in C# to implement aspect-oriented programming by creating proxies that dynamically intercept method calls.

shutterstock 289153913 upward view of silver silos against a blue sky with clouds
Credit: irina d'elena / Shutterstock

Aspect-oriented programming (AOP) decomposes an application’s source code into distinct aspects that isolate the core business logic from cross-cutting concerns. With AOP, cross-cutting concerns like logging and authentication become “aspects” that you program in one place and then apply in all of the places they’re needed. Thus AOP enhances modularity, makes your code more readable and maintainable, and helps you reduce coding errors. You can learn more about AOP from my previous article here.

We’ve examined how we can implement AOP in C# using PostSharp and Autofac. In this article, we’ll look at implementing AOP in C# using the DispatchProxy class. To work with the code examples provided in this article, you should have Visual Studio 2022 Preview installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 Preview here.

Create a console application project in Visual Studio 2022 Preview

First off, let’s create a .NET Core 9 console application project in Visual Studio 2022 Preview. Assuming you have Visual Studio 2022 Preview installed, follow the steps outlined below to create a new .NET Core 9 console application project.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Click Next.
  7. In the “Additional information” window shown next, choose “.NET 9.0 (Preview)” as the framework version you would like to use.
  8. Click Create.

We’ll use this .NET 9 console application project in the subsequent sections of this article.

The perils of cross-cutting concerns

Cross-cutting concerns are application concerns that span several modules or layers of an application. Typical examples include logging, authentication, caching, performance monitoring, and transaction management. These concerns add a degree of complexity to the application’s core business logic.

Aspect-oriented programming is a proven approach to addressing these challenges. By modularizing these concerns, AOP decreases code clutter and improves code readability and maintainability, thereby enabling your application’s source code to be flexible.

The DispatchProxy class in C#

The most commonly used technique for implementing aspect-oriented programming is to intercept method calls in AOP, using a dynamic proxy for code interception. The DispatchProxy class provides a way to create proxy objects in C# and intercept method calls. This feature helps implement aspects without polluting or complicating an application’s business logic with cross-cutting concerns.

Essentially, DispatchProxy is an abstract class pertaining to the System.Reflection namespace. It contains the declaration of only two methods, an abstract method named Create and a generic method named Invoke as shown below.


protected abstract object Invoke(MethodInfo targetMethod, object[] args);
public static T Create() where TProxy : DispatchProxy

Using the DispatchProxy class in C#

Consider the following code that contains an interface and a class that implements the interface.


interface IMyInterface
{
    void Display(string text);
}
class MyClass : IMyInterface
{
    public void Display(string text)
    {
        Console.WriteLine(text);
    }
}

Create another class named MyClassDispatchProxy that extends the DispatchProxy class and implements the IMyInterface interface. We’ll use this class to create proxy object shortly.


class MyClassDispatchProxy : DispatchProxy where T : class, IMyInterface
{
    private IMyInterface Target { get; set; }
    protected override object Invoke
    (MethodInfo targetMethod, object[] args)
    {
        return targetMethod.Invoke(Target, args);
    }
    public static T CreateProxy(T target)
    {
        var proxy = Create>() as MyClassDispatchProxy;
        proxy.Target = target;
        return proxy as T;
    }
}

You can use the following piece of code to invoke the Display method using the proxy instance.


IMyInterface decoratedObject = MyClassDispatchProxy.CreateProxy(new MyClass());
decoratedObject.Display("This is a text message for testing purposes only.");

When you call the CreateProxy method, it calls the Create<T> method of the abstract base class to create a proxy object for you. When you call any method on the proxy instance called IMyInterface, the proxy will automatically call the Invoke() method.

Use DispatchProxy to add logging capabilities

Let us now update the MyClassDispatchProxy class to include logging capabilities and change its name to CustomLogger. To do this, create a new C# class named CustomLogger that extends the DispatchProxy class.


public class CustomLogger : DispatchProxy where T : class
{
    private readonly ILogger _logger;
    private T target;
    protected override object Invoke
    (MethodInfo targetMethod, object[] args)
    {
       throw new NotImplementedException();
    }
    public static T Create(T target)
    {
       throw new NotImplementedException();
    }
}

The CustomLogger class shown in the preceding code uses Serilog to log data in this example. The following code snippet shows how you can update the Invoke method we created in the MyClassDispatchProxy class earlier to incorporate logging capabilities.


protected override object Invoke
    (MethodInfo targetMethod, object[] args)
    {
        if(targetMethod == null)
            throw new ArgumentNullException(nameof(targetMethod));
        _logger.Information($"Entering method: {targetMethod.Name}...");
         var result = targetMethod.Invoke(target, args);
        _logger.Information($"Exiting method: {targetMethod.Name}...");
        return result;
    }

Note how the method calls are logged before and after invocation in the Invoke method. If the targetMethod instance is null, the Invoke method throws an exception. In the Create method, we create a new instance of the CustomLogger<T> class and then set the target object based on which the proxy object will intercept method calls.

Complete source code for DispatchProxy logging example

The complete source code of the CustomLogger example is given below for your reference.


public class CustomLogger : DispatchProxy where T : class
{
    private readonly ILogger _logger;
    private T target;
    public CustomLogger()
    {
        _logger = new LoggerConfiguration()
        .WriteTo.Console()
        .CreateLogger();
    }
    protected override object Invoke
    (MethodInfo targetMethod, object[] args)
    {
        if(targetMethod == null)
            throw new ArgumentNullException(nameof(targetMethod));
        _logger.Information($"Entering method: {targetMethod.Name}...");
         var result = targetMethod.Invoke(target, args);
        _logger.Information($"Exiting method: {targetMethod.Name}...");
        return result;
    }
    public static T Create(T target)
    {
        var proxy = Create>() as CustomLogger;
        proxy.target = target;
        return proxy as T;
    }
}

Run the application

Finally, create an instance of type IMyInterface that you would want to create a proxy object for and pass it to the Create<T> method of the CustomLogger<T> class as shown in the code below.


var myObject = new MyClass();
var proxy = CustomLogger.Create(myObject);
proxy.Display("Test message");

When you run the above piece of code, you’ll be able to see the text messages displayed before and after the method invocation as shown in Figure 1.

DispatchProxy example

Figure 1. DispatchProxy in action!

IDG

DispatchProxy proxies interfaces, not classes

The DispatchProxy class in C# enables you to intercept method calls and modify method invocations at runtime by using reflection. It is an excellent choice in applications where interface-based programming is used. Moreover, because the DispatchProxy class is available as part of the .NET Core library, you do not need to use any third-party libraries to implement aspect-oriented programming.

However, remember that DispatchProxy works only with interfaces, i.e., it proxies interfaces, not classes. You should also be aware of the potential performance implications (and be prepared to address any performance challenges) that might creep in because of the dynamic nature of proxy creation when working with DispatchProxy.

Joydip Kanjilal
Contributor

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoft’s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobb’s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author