Recently I was asked to look into an interesting issue which we faced while we were improving our application. We were using NVelocity for formatting needs in our web application over last several years. I expect that you are already familiar with NVelocity but in case you'r not NVelocity is ported version of Velocity for .NET. You can read more about it from Castle Project.
In our application written in ASP.NET(C#), we were using Active Record pattern so many business related behaviours were on same objects. It means if we have an object in NVelocity context we were able to call the methods through NVelocity directly on object. Some of these methods were on our collection objects which were specialized for each domain object. For example if we have an entity class OrderItem then we also have OrderItemCollection which provides strongly typed access on OrderItem objects. So we have certain utility method specific to such strongly typed collections right inside the collection objects for example in order to calculate the Total amount of all order items we may have a Total() method right inside OrderItemCollection. So far so good this was all working really good when formatting Emails through NVelocity. For instance NVelocity was able to understand , execute and print output for following code.
due to some requirements. Ideally we wanted to keep the API similar to the old one so best pick was to move the methods from specialized collections to IList extensions. This is where we broke the Emails. NVelocity had no clue of extension methods so calls like $order.Items.Total() were printing the same piece of code instead of calculated values. I been asked to figure out if there is any easy way out where ideally we would like to keep things similar to the old days. After doing some Google searches I figured out one possibility and that is the IDuck interface in NVelocity. This interface allows you to intercept the member calls on object implementing IDuck interface and gives your code a chance to locate and execute member method or property and return the output. Now in our case I have to make sure that we are able to handle following two cases
As its clear from above method signatures that in order to implement these methods one will be needing to use reflection to find and execute methods on corresponding classes so most of helper class was doing reflection. The process was like first load all extension classes, then when helper class gets the invoke call we need to find member method on available extension classes and finally if we found a match we invoke method by passing it input parameters passed by nvelocity.
This solved the first case where we needed the ability to call extension methods on system types like decimal. In order to do that all we needed was to add one more parameter to nvelocity context of type NVelocityHelper and then use that parameter to call extension methods on system types as below.
The second part was more important where we needed to mend our broken nvelocity calls which we had on custom collection in past but now are extension methods. The only option to handle this case was to first extend generic List class into our custom generic list class let's say ExList and then by implementing the IDuck interface on it. This gave our custom generic list a chance to intercept the calls from nvelocity context on our collections which then we mapped to extension methods by using codes written in NVelocityHelper.
The solution was really amazing and handy. We were able to retain our existing Email formatting codes written around nvelocity while using extension methods instead of custom strongly typed collections. Hopefully this post may help some one else looking for something along the same lines.
In our application written in ASP.NET(C#), we were using Active Record pattern so many business related behaviours were on same objects. It means if we have an object in NVelocity context we were able to call the methods through NVelocity directly on object. Some of these methods were on our collection objects which were specialized for each domain object. For example if we have an entity class OrderItem then we also have OrderItemCollection which provides strongly typed access on OrderItem objects. So we have certain utility method specific to such strongly typed collections right inside the collection objects for example in order to calculate the Total amount of all order items we may have a Total() method right inside OrderItemCollection. So far so good this was all working really good when formatting Emails through NVelocity. For instance NVelocity was able to understand , execute and print output for following code.
$order.Items.Total()
Now while we were improving the things we switched from specialized strongly typed collections to IList- We can call any extension method from NVelocity context. This is more important for the cases where we have extensions defined on system types like decimal etc.
- We can call extension methods on our generic collections like the old days for example $order.Items.Total()
In order to intercept extension methods calls I created a helper class that can intercept and translate extension methods by using nvelocity's IDuck interface. IDuck interface requires you to implement following three members
- object GetInvoke(string propName) :- When implemented this will correspond to property getter and will be called when reading value from property.
- object Invoke(string method, params object[] args) :- When implemented this will be called against any method call in NVelocity context.
- void SetInvoke(string propName, object value):- When implemented this will correspond to property setter and will be called when setting value on a property within NVelocity context.
$helper.MyExtensionMethod(price)
This call will simply give NVelocityHelper class a chance to find MyExtensionMethod on available extension classes and if found it will execute and return the results.The second part was more important where we needed to mend our broken nvelocity calls which we had on custom collection in past but now are extension methods. The only option to handle this case was to first extend generic List
The solution was really amazing and handy. We were able to retain our existing Email formatting codes written around nvelocity while using extension methods instead of custom strongly typed collections. Hopefully this post may help some one else looking for something along the same lines.