.NET Backend

Aspect-Oriented Programming with Autofac

Today we are going to have a little break from our CQRS/ES journey, but I hope you’ll like that article anyway. Meet Aspect-Oriented Programming! An approach that’s going to change the way of writing our code and more importantly will help us to keep it clean and simple (in most cases).

 

The idea

Writing the code we can often observe that the certain group of the non-business functionalities is copied in many areas, and moreover, it affects other parts of our system. Here we can include: creating database transactions, authorizing the user, logging exceptions thrown by our application, etc. Mentioned group has a unique name – Cross-cutting concerns (I’ll be using ‘CCC’ as a shorthand in the rest of this article). So where is the problem? Well, developers (even good ones) mostly don’t know what to do with such a code. Of course, we can encapsulate logging into a service, which then will be injected into the class. Yes, we can split our method into small pieces so that we won’t have to deal with 300 lines of code at once. But that’s not the point. Even if we are trying to hide our cross-cutting concerns, we still are breaking Single Responsibility Principle, aren’t we? So, in all of that, it would be nice to have some „mechanism” that would allow us to remove CCC from business logic but then in runtime „inject” them if necessary. That’s what AOP is all about.

In general, there are two ways of implementing AOP (that make sense to me):

  • Dependency injection container
  • Post-compile tools e.g. PostSharp

 

Today we’ll become more familiar with a first approach using Autofac with an extension called DynamicProxy2.

 

Typical scenario

Let’s start with an example that will help us to understand AOP (if you are not interested in that just move to the next section of an article). Our application will be used for creating and deleting some Items using the Entity Framework 6 and Microsoft SQL Server 2014. The entity looks like below:

 


public class ItemEntity
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public int Quiantity { get; set; }

    public bool IsDeleted { get; set; }

    public ItemEntity()
    {
        IsDeleted = false;
    }
}

 

To encapsulate EF DbContext, we’ll create a simple service for Create and Delete operations. The implementation looks as follows:

 

public class ItemService : IItemService
{
    Context Context { get; }

    public ItemService(Context context)
    {
        Context = context;
    }


    public ItemEntity Add(ItemEntity item)
    {
        var isAlreadyInDatabase = Context.Items.Any(i => i.Name == item.Name);

        if(isAlreadyInDatabase)
            throw new ArgumentException("Item already in database");

        Context.Items.Add(item);
        Context.SaveChanges();

        return item;
    }

    public void Delete(int id)
    {
        var item = Context.Items.FirstOrDefault(i => i.Id == id);

        if(item == null)
            throw new ArgumentException("Item does not exist");
        if(item.IsDeleted)
            throw new ArgumentException("Item already deleted");

        item.IsDeleted = true;
        Context.SaveChanges();
    }
}

 

Next step is to register our service and Context using Autofac:

 

public class DependencyInjection
{
    public static IContainer Register()
    {
        var containerBuilder = new ContainerBuilder();       

        containerBuilder.RegisterType<Context>().AsSelf();

        containerBuilder.RegisterType<ItemService>().As<IItemService>();        

        return containerBuilder.Build();
    }
}

 

The last thing is implementation of our Program class:

 

class Program
{
    static void Main(string[] args)
    {
        var container = DependencyInjection.Register();
        var itemService = container.Resolve<IItemService>();

        Add(itemService, "Item1", 10);
        Add(itemService, "Item1", 10); // should throw an exception

        Console.ReadKey();
    }

    static void Add(IItemService itemService, string itemName, int itemQuantity)
    {
        using (var transaction = new TransactionScope())
        {
            try
            {
                Console.WriteLine($"Add method invoked");

                itemService.Add(new ItemEntity
                {
                    Name = itemName,
                    Quiantity = itemQuantity
                });

                transaction.Complete();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error has occured: {ex.Message}");
            }
        }

        Console.WriteLine($"Add method finished");
    }
}

 

The code here looks bad. No doubt about it. We took care about transactions and exception handling, but we finished with over 20 lines of code in the Add method. Let’s run this app:

 

sample1

 

The result is correct. The first operation succeeded because there was no Item1 in the database. The second operation failed since we wanted to insert an item with the same name. I think that is time to clean this mess using the AOP…

 

Creating the aspects

As I wrote, to accomplish our objective, we’ll use an extension for Autofac called DynamicProxy2. To get that simply type the following command in NuGet package console:

 

Install-Package Autofac.Extras.DynamicProxy2

 

Now, we are ready to refactor our code. The first thing, we are going to do is to create an aspect class. The implementation looks as follows:

 


public class TransactionAspect : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine($"{invocation.Method.Name} method invoked");

        using (var transaction = new TransactionScope())
        {
            try
            {
                invocation.Proceed();
                transaction.Complete();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error has occured: {ex.Message}");
            }
        }

        Console.WriteLine($"{invocation.Method.Name} method finished");
    }
}

 

Let’s stop here for a while. As we can see TransactionAspect implements IInterceptor interface delivered by Autofac extension. It contains only one method – Intercept – with an argument of type IInvocation. Using the invocation object, we get access to certain parameters of invoked method (e.g. name, arguments, return value, target type etc.) and even more, we can easily modify them. Moving forward, notice that our implementation of Intercept method looks the same as the one from the Program class. The only difference is that instead of invoking the ItemService’s method, now we are doing the same using the invocation object. And here comes the best part. The invocation represents any method of a class that applied our aspect. That means that we created only one place in our code to manage database transactions which then can be quickly „injected” into methods that will need that. So, we not only cleaned the code but we also applied DRY rule (Don’t repeat yourself). There’s still one more question. How does the Autofac know which class uses which aspects? The answer is simple. We’ll point that during dependency injection registration. Here’s our modified code:

 


public class DependencyInjection
{
    public static IContainer Register()
    {
        var containerBuilder = new ContainerBuilder();

        containerBuilder.RegisterType<Context>().AsSelf();

        containerBuilder.RegisterType<ItemService>().As<IItemService>()
            .EnableInterfaceInterceptors()
            .InterceptedBy(typeof(TransactionAspect));

        containerBuilder.RegisterType<TransactionAspect>();

        return containerBuilder.Build();
    }
}

 

It is worth mentioning that our aspect will be injected into each of the IItemService’s method. Therefore, you should think carefully where is a suitable place for it. Since we already have everything that is needed for us, we can make refactor of our primal code in the class Program:

 


class Program
{
    static void Main(string[] args)
    {
        var container = DependencyInjection.Register();
        var itemService = container.Resolve<IItemService>();

        itemService.Add(new ItemEntity {Name = "Item1", Quiantity = 10});
        itemService.Add(new ItemEntity { Name = "Item1", Quiantity = 10 });
      
        Console.ReadKey();
    }
}

 

Now it looks way better than it was before. Let’s  run our application again to see the result:

 

sample1

 

It looks that we didn’t broke anything 😉 Okay, let’s play with aspects a little bit more, just to check what can we do with that. How about changing the value of arguments and return value? The implementation of aspect for this purpose may look like this:

 


public class ChangeArgumentsAspect : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        var arguments = invocation.Arguments;

        foreach (var argument in arguments)
        {
            ItemEntity itemEntity;

            if ((itemEntity = argument as ItemEntity) != null)
                itemEntity.Name += "| Name changed by interceptor";
        }

        invocation.Proceed();

        var result = invocation.ReturnValue as ItemEntity;

        if (result != null) result.Quiantity = 420;
    }
}

 

The code is quite simple. We get the array of all method’s argument using the Arguments property which next we iterate over. If the argument is of type ItemEntity, we concatenate its name with some additional text. Next, we need to invoke the method using mentioned Proceed which allows us to modify the state of the return value. In our case, we change the Quantity of the item. Let’s check how it works:

 

sample2

 

sample3

 

That looks cool! Of course, in this case, we did not encapsulate any CCC, but I just wanted to demonstrate what else you can do with that. You get the idea, right?

 

Conclusion

I know that the post was not very detailed and showed only a fraction of the possibilities that  AOP and Autofac offer us. Despite this, I hope that for some of you that may be helpful for future work and will allow you to write code even better. In conclusion, it is worth considering the drawbacks of this solution. The only thing that comes to mind is that people not familiar with AOP can spend long hours wondering how it is possible that some method does more than it should. The whole project is available on my github. As always, I encourage you to follow me on Twitter or leave a like on Facebook just to be up to date with my next posts.