Aspect Oriented Programming (AOP) in C# with SOLID

Posted by: Yacoub Massad , on 9/10/2016, in Category Patterns & Practices
Views: 177279
Abstract: Aspect Oriented Programming (AOP) in C# using SOLID principles. We will discuss challenges posed by context-independence in SOLID code bases, and provide a solution for them.

Aspect Oriented Programming (AOP) tries to solve the problem of code duplication and code tangling that developers encounter while addressing cross cutting concerns. Consider the following code example that tries to address the logging concern for a class:

public class DocumentSource : IDocumentSource
{
    //..

    public Document[] GetDocuments(string format)
    {
        try
        {
            using (var context = CreateEFContext())
            {
                var documents =
                    context
                        .Documents
                        .Where(c => c.Name.EndsWith("." + format))
                        .ToArray();

                logger.LogSuccess(
                    "Obtained " + documents.Length + " documents of type " + format +
                    Environment.NewLine +
                    "Connection String: " + connectionString);

                 return documents;
            }
        }
        catch (Exception ex)
        {
           logger.LogError(
               "Error obtaining documents of type " + format +
               Environment.NewLine +
               "Connection String: " + connectionString, ex);

            throw;
        }
    }

    //..
}

The main purpose of this method is to read documents from some database. It also logs success and failure information.

This article is published from the DNC Magazine for Developers and Architects. Download this magazine from here [PDF] or Subscribe to this magazine for FREE and download all previous and current editions

Here is how the method would look like without logging:

public Document[] GetDocuments(string format)
{
    using (var context = CreateEFContext())
    {
        return
            context
                .Documents
                .Where(c => c.Name.EndsWith("." + format))
                .ToArray();
    }
}

Clearly, the logging code has made the original method less readable. It has tangled the real method code with logging code.

This is also a violation of the Single Responsibility Principle.

Also, we expect to find the same logging pattern in many methods all over the code base. Basically, we expect to find the following pattern:

try
{
    //Do something here

    logger.LogSuccess(…                
    //..
}
catch (Exception ex)
{
    logger.LogError(…
    throw;
}

Aspect Oriented Programming

Aspect Oriented Programming (AOP) allows us to keep the original code as-is, and provides us a way of weaving an aspect (e.g. logging) into the code. An aspect is supposed to be defined in a generic way so that it can be applied to many objects in the application. If we take logging as an example, this means that the pattern shown above will only be maintained in a single unit of code (e.g. class) called an aspect, and then applied to many code units.

SOLID is a set of principles that intends to make code more maintainable. If we apply such principles, we will end up with a lot of small classes that each does a single thing well. Also, classes become highly composable, context independent, and thus reusable.

Editorial Note: To learn more about SOLID, please refer to our tutorials on SOLID principles.

If the DocumentSource class, for example, is to be used in many contexts, i.e. many instances of this class are to be used each with different configuration, logging cannot be hardcoded into the class itself. Each instance might want to be logged in a different way. For example, one instance might obtain documents from an on-premises database, while another instance might obtain documents from a database on the cloud. Although such context information is not known by the DocumentSource class, it needs to be logged to the log file.

As an additional example of a challenge made by context independence for AOP, consider the example of a retry aspect. A retry aspect can be applied to any object to make some method retry execution on failure. Because different instances are in different contexts, they might require different number of retries before they give up. We cannot, therefore, hardcode the number of retries in the class itself.

This article will discuss some options for doing AOP, and will also show how to deal with context independence.

I have created a repository on GitHub that contains the sample code for the ideas discussed in this article. You can view this repository at https://github.com/ymassad/AOPExamples.

AOP via decorators

One way to solve the problems described in the previous example is to put the logging logic into a decorator. Here is an example:

public class LoggingAwareDocumentSource : IDocumentSource 
{
    private readonly IDocumentSource  decoratedDocumentSource;
    //..

    public Document[] GetDocuments(string format)
    {
        try
        {
            var documents = decoratedDocumentSource.GetDocuments(format);

            logger.LogSuccess(
                "Obtained " + documents.Length + " documents of type " + format);

            return documents;
        }
        catch (Exception ex)
        {
            logger.LogError(
                "Error obtaining documents of type " + format, ex);

            throw;
        }
    }
}

We then remove the logging code from the original class.

Then, in the Composition Root, we decorate the DocumentSource object in the following way:

var source =
    new LoggingAwareDocumentSource (
        new DocumentSource (connectionString),
        new Logger(…));

If you want to learn more about how to compose objects in the Composition Root, see the Clean Composition Roots with Pure Dependency Injection (DI) article.

The solution provided here has two problems.

The First Problem: Specificity

The first problem is that this decorator works only for IDocumentSource. If we want to do the same thing for another interface, say IEmailSender, we need to create another decorator. In other words, each decorator is specific to a single interface.

One way to fix this problem is to minimize the number of interfaces in the application to a few (e.g. two or three interfaces).

For example, we can create two generic interfaces, one for queries, and another one for commands:

public interface IQueryHandler<TQuery, TResult>
{
    TResult Handle(TQuery query);
}
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

..and then make all (or most) classes implement one of these interfaces. Of course, for each class, TCommand, TQuery, or TResult might be different. For example, our DocumentSource class would look something like this:

public class DocumentSource : IQueryHandler<GetDocumentsQuery, GetDocumentsResult>
{
    //..
    public GetDocumentsResult Handle(GetDocumentsQuery query)
    {
        using (var context = CreateEFContext())
        {
            return
                new GetDocumentsResult(
                    context
                        .Documents
                        .Where(c => c.Name.EndsWith("." + query.Format))
                        .ToArray());
        }
    }
    //..
}

..and the logging decorator can be generic as shown here:

public class LoggingAwareQueryHandler<TQuery, TResult> : IQueryHandler<TQuery,TResult>
{
    private readonly IQueryHandler<TQuery, TResult> decoratedHandler;
    //..

    public TResult Handle(TQuery query)
    {
        try
        {
            var result = decoratedHandler.Handle(query);

            logger.LogSuccess(...);

            return result;
        }
        catch (Exception ex)
        {
            logger.LogError(..., ex);
            throw;
        }
    }
}

Note: I learned about this idea of separating classes into command handlers and query handlers from the .NET Junkie blog. More specifically, from these two articles: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=91 and https://cuttingedge.it/blogs/steven/pivot/entry.php?id=92. You might want to read them for more information.

Note that this handler can decorate any IQueryHandler, regardless of TQuery and TResult. This is what makes it work as an aspect.

We also need to create another decorator for ICommandHandler. So for each aspect, we have two decorators, which is somewhat acceptable.

Although this solution gives us a lot of flexibility, one could argue that such approach makes the code a bit verbose. For example, the following code:

var result = parser.Parse(document);

will look something like this:

var result = parser.Handle(new ParseQuery(document));

Also, the following code:

private readonly IDocumentParser parser;

will look something like this:

private readonly IQueryHandler<ParseQuery,ParseResult> parser;

Like most things, this approach has its advantages and disadvantages.

The Second Problem: Missing details

If you look carefully at the logging messages that we output via the first decorator (the LoggingAwareDocumentSource class), the “connectionString” of the database is not logged. And in the case where we used the QueryHandler approach, even the number of documents returned is not logged.

This problem, however, is not specific to the use of decorators, or the idea of splitting the classes into query handlers or command handlers. This problem exists because of the generic nature of the logging aspect. Any generic solution has this problem, and there are solutions. Mainly, we need to tell the aspect how to obtain these details. One way to do this is to annotate specific parameters/members with attributes. Consider the GetDocumentsResult class that we used as the return value type for our DocumentSource class:

public class GetDocumentsResult
{
    [LogCount("Number of Documents")]
    public Document[] Documents { get; private set; }

    public GetDocumentsResult(Document[] documents)
    {
        Documents = documents;
    }
}

Notice how we have annotated the Documents property with a custom LogCount attribute. This attribute is defined like this:

public class LogCountAttribute : Attribute
{
    public string Name { get; private set; }

    public LogCountAttribute(string name)
    {
        Name = name;
    }
}

There is nothing special about this attribute. It only holds a “Name” property which will be used as a description when we log the annotated property. The magic should happen in the logging aspect. In our previous example, this is the LoggingAwareQueryHandler decorator. This decorator should use Reflection to examine the properties of both the TQuery object and the TResult object, and look for these special attributes. If they are found, depending on the attribute, it should read some data and include it in the log. For example, we should expect to find something like “…Number of Documents: 120…” in the log file.

We can have several other attributes to annotate different kinds of things. For example, we can create the obvious Log attribute to annotate properties of which value we want to log.

Take a look at the source code of the GetDocumentsQuery class and see how the Format property is annotated with the Log attribute. This will make the aspect to log the value of this property.

Take a look at the source code of the LoggingAwareQueryHandler class. It has two dependencies on an interface called IObjectLoggingDataExtractor. This interface represents an abstraction of a unit that can extract logging information from some object. One extractor dependency will extract logging data from the query object, and another extractor dependency will extract logging data from the result object. This makes the logging aspect extensible. We can easily add more logging attributes and provide different implementations of this interface to extract logging data based on these attributes. Take a look at the different implementations of this interface in the AOP.Common project.

You can run the Application.CQS application to see the results. Currently the application is configured to log to the console.

Logging context specific information

We still didn’t log the “connectionString” of the database. Additionally, we might want to log context information that the DocumentSource class itself does not know about. For example, one DocumentSource instance might represent documents from department A, and another instance might represent documents from department B. Such information is only known to the application itself, not individual classes. The Composition Root is the entity that represents the application and has all the knowledge about the application. It is the entity that needs to know which instance represents what. In other words, it is the entity that knows the context for each object in the object graph.

Take a look at the Composition Root of the Application.CQS application, i.e. the Program class. It contains the following code:

var queryHandler =
    new FakeDocumentSource("connectionString1")
        .AsLoggable(
            new LoggingData { Name = "connectionString", Value = "connectionString1" },
            new LoggingData { Name = "Department", Value = "A" });

This code creates an instance of the FakeDocumentSource class. This class is like the DocumentSource class described in this article, but it returns fake data instead of data from a real database. I did this to make it easier for readers to run the application without a real database.

Notice the call to the AsLoggable extension method. This method is defined in the QueryHandlerExtensionMethods class. It takes an array of LoggingData data objects and uses one implementation of the IObjectLoggingDataExtractor interface, i.e. the ConstantObjectLoggingDataExtractor class, to force the logging of some constant logging data. In our particular case, we want to log the connection string and the department information.

Metadata classes

One could argue that we are still coupling the logging logic with the original class since the attributes are annotating members of the class itself. There could be a situation where in one instance we want to log the number of documents, and in another instance we don’t want to.

One solution to this problem is to move the annotations to a special class or classes, and inform the logging aspect about it. For example, we could create a class called GetDocumentsResultMetaData1 that contains the same properties as the GetDocumentsResult class and annotate the properties in GetDocumentsResultMetaData1 with the logging attributes that we want for a particular context. Then we tell the logging aspect about this class. If we need to log differently in a different context, we create another metadata class, and so on.

The details of this method is beyond the scope of this article, but could be the subject of a future one.

AOP via dynamic proxies

Let’s go back to the first problem that we discussed; the specificity problem. We solved it previously by architecting the system in such a way that we have only a few interfaces. Although it is a valid approach, we might decide to architect our system to have more specific interfaces. How can we solve the specificity problem in this case? We can use dynamic proxies.

A dynamic proxy (in this context) is a class generated at runtime that can be used to wrap an object to apply some aspects to it.

So instead of creating hundred decorators at design time, we create one generic aspect, and then use it to generate the hundred decorators at runtime.

Take a look at the FakeDocumentSource class in the Application.CastleDynamicProxy project in the source code. This class is annotated with some attributes. As we have done before, these attributes will be used by the aspect to extract data for logging.

Take a look at the LoggingAspect class. It implements the IInterceptor interface from the Castle DynamicProxy library. This interface has one method; the Intercept method:

void Intercept(IInvocation invocation);

This method allows us to intercept calls to our objects, e.g. the FakeDocumentSource class. We can use the invocation parameter to access the invocation context. This context includes information such as the intercepted method arguments, the return value of the method, the method metadata, etc. We can use it for example to get the method description from the MethodDescriptionAttribute that we used to annotate the GetDocuments method in the FakeDocumentSource class.

The IInvocation interface also has a “Proceed” method that is used to control when to invoke the original method.

In the LoggingAspect class, we first collect information about the method itself, such as the method description from the MethodDescription attribute. Then we collect logging data from the method arguments, and finally invoke the original method. If an exception is thrown, we log the logging data we obtained so far along with the exception itself. If no exception is thrown, we extract logging data from the return value and record all logging data.

DynamicProxy works by using the Reflection API in the .Net framework to generate new classes at runtime. For an example on how this can be done, take a look at the example provided by Microsoft in the documentation of the TypeBuilder class.

Logging context specific information

We can log context information as we did before. There is no difference. Take a look at the Composition Root of the Application.CastleDynamicProxy application and the AsLoggable extension method for more details.

A note about Reflection and Performance

We have relied on Reflection to extract logging information. Basically, we searched for parameters or properties that are annotated with specific attributes, and then read their values. This takes more time than directly accessing parameters or properties.

For example, it is faster to run this code:

var value = query.Format;

than it is to run this code:

var value = query.GetType().GetProperty("Format").GetValue(query);

If we log events frequently, the performance cost would be significant.

To fix this problem, instead of making the proxies search for the attributes and extract data via Reflection every time they are invoked, we search for the attributes during the process of creating the proxies, and generate the proxies in such a way that they contain code that directly access the correct pieces of data (properties/parameters).

The details of this approach is beyond the scope of this article, but we may cover this subject in a future one.

AOP via compile-time weaving

Another method for doing AOP is via compile-time weaving. As we did earlier, we annotate our classes/methods/properties/etc. with certain attributes. But this time, a post-compilation process starts, searches for these attributes, and adds extra IL code to our methods inside the compiled assemblies to make the aspects work.

One of the popular compile-time AOP tools in .NET is PostSharp. It contains a lot of built-in aspects such as the logging aspect, and can be used to create our own aspect. Take a look at the many examples provided in the PostSharp website at http://samples.postsharp.net/

One thing to note here is that aspects are applied to classes, not objects. As far as I know, there is no easy way to configure aspects to work differently for different instances.

AOP via T4

Text Template Transformation Toolkit (T4) is a template based text generation framework from Microsoft. We can use this tool to generate code at design-time.

Basically, we can create a T4 template that represents an aspect, and then use it to generate many classes that each represent the customized application of that aspect to a specific unit. For example, we can create a T4 template for a logging aspect and use it to generate fifty decorators (at design time) that each represent a logging decorator that knows how to log information for the class/interface that it decorates. This is basically the same as AOP via decorators, but now the decorators are generated automatically from a template at design time.

Check my article on AOP in C# via T4.

For more information about T4, take a look at the documentation form MSDN: https://msdn.microsoft.com/en-us/library/bb126478.aspx

Conclusion:

AOP allows us to remove code duplication and enhances code readability by moving code related to cross-cutting concerns into a single place where it can be easily maintained. This article provided some options for doing AOP. It also discussed the challenge posed by context-independence in SOLID code base, and provided a solution for such a challenge.

Download the entire source code of this article over here (Github).

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Yacoub Massad is a software developer and works mainly on Microsoft technologies. Currently, he works at Zeva International where he uses C#, .NET, and other technologies to create eDiscovery solutions. He is interested in learning and writing about software design principles that aim at creating maintainable software. You can view his blog posts at criticalsoftwareblog.com. He is also the creator of DIVEX(https://divex.dev), a dependency injection tool that allows you to compose objects and functions in C# in a way that makes your code more maintainable. Recently he started a YouTube channel about Roslyn, the .NET compiler.



Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!