SheepAOP Part 1 – Getting Started

SheepAOP released its first preview yesterday. It’s by no mean anywhere near complete, but I just wanted to put it out there so people could start playing with it, and try out whatever basic functionality there is. Throughout this blog post series, I will be exploring various bits and pieces of those functionalities.

This post is based on the current build of SheepAOP Preview-Release 1 Preview Release 1.1

This Series

This is what I currently have in mind about things that I plan to cover in the next several posts:

  1. Getting Started with SheepAOP
  2. Pointcuts and SAQL Basics
  3. Aspects Lifecycles & Instantiations
  4. Integrating with IoC containers
  5. Aspects Inheritance & Polymorphism
  6. Attributive Aspects (ala PostSharp)
  7. Unit-testing your aspects
  8. Extending SheepAop

I’m going to skip the whole boring introduction speech on Aspect Orientation, and will assume you have at least some basic prior knowledge of AOP concepts and technologies. If you’re not familiar with AOP, there are countless articles around AspectJ in the information-superhighway that is the Internet, which I highly recommend as a fantastic resource to learn about the concept of AOP. Now let’s get started on SheepAOP.

Your First Aspect

In SheepAOP, an aspect is just a normal POCO class that you decorate with Aspect attribute. For example:

[Aspect]
public class TransactionAspect
{
}

Idioms

SheepAOP uses the same formal AOP idioms as it was coined over a decade ago, and popularized by the de-facto AOP framework of all time: AspectJ. The critical element in this AOP design is the join-point model.

A join-point is a well-defined point in the program flow. There are many different types of them, encompassing the actions of calling a method, instantiating a class, setting/getting a property, accessing/assigning a field, etc. On most other AOP frameworks for .net out there, such as PostSharp, you typically target these join-points by placing marker attributes around these join-points (e.g. methods/properties/fields/classes), so the framework will pick them up and weave with croscutting behaviors.

SheepAOP, in contrast, uses the well-known AOP mechanic to target your join-points. This mechanic is formally known as pointcut.

Pointcuts

Pointcut is an aspect-oriented-programming mechanic acting as a query-expression to pick out certain join-points in your program flow.

Simple Pointcut

The following is a SheepAop example to pick out each join-point that is an execution of a method that has the signature void MyNamespace.MyService.GetCustomer(int):

[SelectMethods("'void MyNamespace.MyService::GetCustomer(System.Int32)'"]
public void MyPointcut(){}

The language you use to define pointcuts in SheepAOP is called SAQL (SheepAop Query Language), which will be covered in depth in the next post of the series.

More Complex Criteria

You can define vastly expressive pointcut queries using complex conditions. For example, the following pointcut will pick out “all public ‘GetXxx’ and ‘ListXxx’ methods within all classes that implement IRepository<T>“.

[SelectMethods("Public & Name:('Get*'| 'List*') & InType:Implements:'Sheep.Data.IRepository`1'")]
public void RepositoryQueriesPointcut(){}

That was a quick glimpse of SAQL that provides you an immensely rich pointcut model for expressing join-point queries that AspectJ can’t.

Pointcut Composition

A SheepAOP pointcut can contain a group of multiple query-expressions. It’s done by simply defining multiple Select Attributes, for example:

[SelectMethods("Public & Name:('Get*'| 'List*') & InType:Implements:'Sheep.Data.IRepository`1'")]
[SelectPropertyGet("Public & InType:Implements:'Sheep.Data.IRepository`1")]
public void RepositoryQueriesPointcut(){}

Note: Pointcut composition is not available yet in the current preview-release of SheepAop. Very simple to implement, but it’s a low priority feature. (Now supported)

Modularizing Pointcuts

A pointcut can be built out of other pointcuts, usually for modularity and reusability reasons. The SheepAop syntax to reference to another pointcut will look familiar to Razor users:  @pointcutName.
As an example, we will refactor the previous example, and additionally, we’ll also expand the pointcut to also pick out “all implementations of IDataService<T>” (in addition to IRepository<T>). And while we’re there, why not also add another pointcut to pick out all Update methods too.

[SelectMethods("Public & Name:('Get*'| 'List*') & InType:@Repositories")]
[SelectPropertyGet("Public & InType:Implements:@Repositories")]
public void RepositoryQueriesPointcut(){}

[SelectMethods("Public & Name:('Update*') & InType:@Repositories")]
public void RepositoryUpdatesPointcut(){}

[SelectTypes("Implements:'Sheep.Data.IRepository`1'")]
[SelectTypes("Implements:'Sheep.Data.IDataService`1'")]
public void Repositories(){}

(Tips: The “Repositories” pointcut above can actually be written more succinctly as follows:)

[SelectTypes("Implements:('Sheep.Data.IRepository`1' | 'Sheep.Data.IDataService`1')")]
public void Repositories(){}

Advice

So pointcuts pick out join-points, but they don’t do anything apart from picking out join-points. To actually implement crosscutting behavior, we use advice. Advice brings together a pointcut (to pick out join-points) and a body of code (to run at each of those join-points).

SheepAOP has several different kinds of advice (and still growing), but the most common one you’ll often find yourself using is the Around advice, which is an advice that runs as a join-point is reached, and has explicit control over whether the program proceeds with the join point execution. For example, let’s put an advice to log around our Queries and Updates pointcuts in our previous example.

[Around("RepositoryQueriesPointcut", "RepositoryUpdatesPointcut")]
public object LogQueriesAndUpdates(MethodJoinPoints jp)
{
   Console.WriteLine("Entering method {0} on object {1} with args {2}",
   jp.Method, jp.This, jp.Args);
   try
   {
      object result = jp.Execute();
      Console.WriteLine("Exits normally with return-value {0}", result);
      return result;
   }
   catch(Exception e)
   {
      Console.Writeln("Exits with exception: {0}", e);
      throw;
   }
}

Joinpoint.Execute() is a callback to your original join-point execution. Calling this method will transfer your advice to proceed with the original execution of your join-point.
But it doesn’t have to stay exactly original. You can alter the elements of this callback, such as changing the method arguments, or even changing the target instance of the method itself (i.e. the ‘this’ of the method), as shown in the following example:

[Around("MyPointcut")]
public object MyAdvice(MethodJoinPoints jp)
{
   jp.Args[0] = "My Replaced Value"; // Replacing 1st argument
   jp.Args[1] = 10*(int)jp.Args[1]; // Multiplying 2nd argument by 10
   jp.This = _anotherRepositoryInstance; // Redirecting the method call to another instance

   return jp.Execute(); // Executing the join-points using altered arguments and instance
}

Beyond Logging

Within an Around advice, you can do more than intercepting calls. You can alter completely the whole flow of the execution. For instance, you can quite easily make all your repository-query methods to only return a lazy-execution proxies, and therefore will defer your actual join-points execution only when the proxy gets triggered. (I.e., similar to Future-Query in NHibernate 3.0).

[Around("RepositoryQueriesPointcut")]
public object DeferQueries(MethodJoinPoints jp)
{
   return ProxyGenerator.CreateProxy(
   baseType: jp.Method.ReturnType,
   initTarget: ()=> jp.Execute());
}

Now all your query methods within all your repositories will return a proxy of their return-values. For instance, in the following example..

var customer = customerRepository.GetById(id);
// some other code doing some other thing
// eventually, it executes customer.FirstName
txtFirstName.Text = customer.FirstName;

… the repository method on #1 will NOT actually execute, not until later on when we trigger the property (FirstName) of the customer-proxy object on line #4, which will then execute your actual customerRepository.GetById() method and retrieve your actual customer object to fulfill the proxy.

Note: Why do we need this if we already have NHibernate’s future-query? Well, in cases where you’re not using NHibernate, or even an RDBMS at all. For instance: web-service, remote calls, I/O read, or costly calculations: anything that you want to avoid executing until when it’s really needed.
SheepAop offers an elegant solution to defer (as well as to cache) these costly executions, without adding unnecessary crosscutting noise to your actual program code, even within static methods)

Putting It All Together

Just to recap our logging-aspect example, the following is the complete code for our aspect.

[Aspect]
public class LoggingAspect
{
   [SelectMethods("Public & Name:('Get*'| 'List*') & InType:@Repositories")]
   [SelectPropertyGet("Public & InType:Implements:@Repositories")]
   public void RepositoryQueriesPointcut(){}

   [SelectMethods("Public & Name:('Update*') & InType:@Repositories")]
   public void RepositoryUpdatesPointcut(){}

   [SelectTypes("Implements:'Sheep.Data.IRepository`1'")]
   [SelectTypes("Implements:'Sheep.Data.IDataService`1'")]
   public void Repositories(){}

   [Around("RepositoryQueriesPointcut", "RepositoryUpdatesPointcut")]
   public object LogQueriesAndUpdates(MethodJoinPoints jp)
   {
      Console.WriteLine("Entering method {0} on object {1} with args {2}",
      jp.Method, jp.This, jp.Args);
      try
      {
         object result = jp.Execute();
         Console.WriteLine("Exits normally with return-value {0}", result);
         return result;
      }
      catch(Exception e)
      {
         Console.Writeln("Exits with exception: {0}", e);
         throw;
      }
   }
}

Setting Up

To enable SheepAOP compiler to weave your code automatically during compilation time, you will simply need to hook a post-compilation task within your .csproj file. If download SheepAOP into your project using NuGet (coming soon), everything will be set up automatically for you. There’s no additional step required.

However, if you’re to configure your SheepAOP manually (which is the case with the current preview-release), you need to perform the following steps:

  1. Download SheepAOP binaries. This post is based on SheepAop Preview v0.1.1, downloadable at http://sheepaop.codeplex.com/releases/view/65923.
  2. Copy all the binary files into a local folder. I recommend to check them in as part of your project’s version-control system.
  3. Right-click on your project, then select ‘Unload Project’.
    image
  4. Right-click again on your (currently-unloaded) project and select ‘Edit xxx.csProj’.
    image
  5. Within your .csproj file, locate this line: “<Import Project=”$(MSBuildToolsPath)\Microsoft.CSharp.targets” />”. Right underneath, add the following lines:
    <PropertyGroup>
       <SheepAopLocation>$(MSBuildProjectDirectory)\Libs</SheepAopLocation>
    </PropertyGroup>
    <UsingTask TaskName="PostCompileWeaveTask" AssemblyFile="$(SheepAopLocation)\SheepAop.Tasks.dll" />
    <Target Name="AfterBuild">
       <PostCompileWeaveTask TargetFile="$(MSBuildProjectDirectory)\$(OutputPath)$(MSBuildProjectName).exe" SheepAopLocation="$(SheepAopLocation)" />
    </Target>
    

    Change the SheepAopLocation property accordingly depending on where you place your SheepAop binaries (on step#2). In this example, my SheepAop binaries is under Libs folder within my project.

  6. Now right-click your project and select ‘Reload Project’. SheepAop is now hooked to your project. Now every time you build your project, SheepAop will automatically execute a post-compilation task to weave your aspects into your assemblies.

Summary

We have touched some of the basic features of SheepAOP. We have explored some standard AOP mechanics such as Aspects, Pointcuts, Join-Points, and Advices, and how they work in SheepAOP. We have built our first simple example of a SheepAop aspect, and finally hook it with the post-compilation task of your project. In the next article, we will explore more about the basics of SAQL syntax, and various types of pointcuts that SheepAOP supports.

Advertisements

14 thoughts on “SheepAOP Part 1 – Getting Started

  1. How must the .csproj file be modified to work with Preview 2? It seems some things have changed, e.g. there is no longer a SheepAop.Tasks.dll.

    1. That’s correct. The download on the codeplex site is only for the Query-Analyzer (which also includes SheepAspect.dll distribution). Installation for SheepAspect itself is now only supported via NuGet (i.e., manual installation is no longer supported).
      It’s still possible to do it manually (placing the dll and editing your csproj yourself) but it’s too cumbersome. Is there any particular reason you need the manual option?

      1. Maybe you should add a big note to the codeplex main page (it’s really confusing as there is no mention that you don’t download SheepAspect itself).
        I didn’t think that manual installation was too cumbersome and I liked the fact that I see what and where something was added/changed. nuget may be great for new projects, but (at least for us) it doesn’t fit very well into our existing solution structures (consisting of several web site and class library projects) where we already have setup a directory structure for 3rd-party libs.

      2. I had put (on the download page) what I thought was a pretty big sign. “SheepAspect Download is NOT Here”. I’ll add another one on the homepage.
        In regard to non-nuget installation, I suppose I will post an updated instruction on setting up your csproj manually.
        And thanks for trying out this early release

  2. Thanks for your feedback. Please note, that I didn’t mean the codeplex download page. I downloaded from the main page (http://sheepaspect.codeplex.com/) using the big, green button. On the main page, there is no indication that it’s not SheepAspect which is available for download.

  3. I see that aspects use proxy based approach with serialization/boxing of arguments and return like “public object LogQueriesAndUpdates(MethodJoinPoints jp)”. Is there alternative in SheepAOP? I mean that performance of these is really slow and AOP cannot be applied broadly. Unlike AspectJ which does inline code basing on query allowing much broaderAOP appliance, including FOSD (search “An Overview of Feature-Oriented Software Development”).

    So image method “int Sum(int a,int b){ return a + b;}”. Current approach will kill its performance if I want to add check that “if (a<0 || b <0) throw new ArgumentException(); ".
    Actually I want to inline this check instead of using proxying like this:
    "int Sum(int a,int b){if (a<0 || b <0) throw new ArgumentException(); return a + b;}"

    This is possible as I know with AspectJ.

    Current approach fails not only of performance, but what about "jp.Args[99]"? This will throw error in runtime. Do you check during aspect weaving access to Args?

    1. You’re right that there is some overhead compared to AspectJ compiler, but still a massive improvement compared to the traditional runtime (dynamic-proxy) based AOP (e.g. Spring AOP, or castle’s interceptors), which is still generally regarded as a negligible (or at least acceptable) overhead. I decided that the effort of optimising even further past that point is of a quickly diminishing value.

  4. Regarding SAQL? How errors reported into Build/IDE during weaving if SAQL is badly written or do not match any code?

    What about some intellisence and be consider compile SAQL of some C# DSL(and analyze its code):


    [SelectMethods(“‘void MyNamespace.MyService::GetCustomer(System.Int32)'”]
    public void MyPointcut(){}

    to something like (similar to libs mocking libraries and libraries using Expressions to provide DSL):

    public Pointcut MyPointcut()
    {
    return Pointcut.Method(x=> GetCustomer(Arg.IsAny);
    }

    or if need to match any patterned method

    public interface IMyPattern
    {
    Customer GetCustomer(int a);
    }

    public Pointcut MyPointcut()
    {
    return Pointcut.Pattern.Method(x=> GetCustomer(Arg.IsAny);
    }

    If need to match any overload (e.g. passing not only int but also some string filter)

    public interface IMyPattern
    {
    Customer GetCustomer(params Arg[] anyNumberOfArgs);
    }
    return Pointcut.Pattern.Method(x=> GetCustomer());

    1. Sorry very late reply, missed your comment. Regarding the error report, it will appear as normal visual studio build error, e.g. if your SAQL contains syntax error.
      Re intellisense, it will be nice indeed. Unfortunately last time I changed it is not a trivial task to extend Visual Studio’s intellisense support, and therefor was quite far down my priority list.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s