Today’s guest blogger is CRM MVP George Doubinski who offers this interesting post.
It’s been a while since I’ve posted anything juicy. But today’s idea is too good not to share it.
Let’s have a look at a typical function in a typical workflow extension inside a typical workflow assembly, say, Business Productivity Workflow Tools:
And here is the use:
Two numbers and a math operator which is a choice of +, –, *, / and a mighty Mod. How exciting. So if I want, I don’t know, log or exp or anything else, I’ll have to extend the source code, recompile, test and deploy.
At this point the story’s got a bit more interesting. While I was exploring different ways to build weighted revenue for an opportunity using the calculator, Shilpa has beaten me to the punch with an excellent post on how to calculate it using just CRM 4.0 built-in features. Doh! Lesson: quite often the simplest solution in CRM is just around the corner.
Unfortunately, there is still a limit to what CRM can do out of the box. One of our customers is obsessed with calculating errors in their estimates, i.e. ((actual revenue – estimated revenue) / estimated revenue), as one of their sales army KPIs. Initially we’ve implemented this as part of the reporting but later added a new attribute and code to update its value as opportunities are won. The latter approach means that new field can be added to the views and used to filter and sort the records. Well, there is no built-in division and there is no Abs function in the original calculator so it turned out to be indeed a case of “extend the source code, recompile, test and deploy”.
Instead, what we ended up with looks like this:
Inspired by the brilliant The Last Configuration Section Handler I’ll Ever Need, we’ve decided to generalise some of the workflow extension code and give workflow designers a simple scripting mechanism.
How did we do it? Every single .NET framework installation comes with a full C# and VB compilers. And, what’s more, code can be compiled dynamically. So the “heart” of our code performing the actual calculation looks like the following:
1: static decimal TwoDecimalCalculator(decimal d1, decimal d2, string whatToDo)
2: {
3: string codeWrap = @"
4: namespace Georged
5: {{
6: public static class Compiled
7: {{
8: public static decimal SomeInternalMagic(decimal d1, decimal d2)
9: {{
10: return ({0});
11: }}
12: }}
13: }}";
14:
15: // insert user's string and compile code into an assembly
16: string code = string.Format(codeWrap, whatToDo);
17: Assembly a = Compiler.CompileAssembly(code);
18:
19: //get a type from the newly-compiled assembly
20: Type t = a.GetType("Georged.Compiled");
21:
22: // get static method information
23: MethodInfo mi = t.GetMethod("SomeInternalMagic");
24:
25: // drumroll... invoke the method
26: return (decimal) mi.Invoke(null, new object[] { d1, d2 });
27: }
In this scenario, the only limitation on formula is that it:
- Must refer to two input variables as d1 and d2
- Must be a valid .NET expression of type decimal or of a type that has an implicit cast to decimal.
This is to ensure that, once the expression is inserted into the class Compiled, the resulting code is a valid .NET code that can be compiled on-the-fly. For example, the following expression will return the smallest of the two values:
d1 < d2 ? d1 : d2
Since the original idea was conceived, CRM 2011 Beta was released. While the original code should have worked unchanged, I could not resist the temptation to test new cool ways to write code. The result is good – gone verbose DependencyProperty declarations and CRM-specific data types, the code has shrunk and is much more readable now.
There is another, much more important reason to re-write the code. While custom workflow assemblies do work with CRM 2011 workflow, they are hopeless when it comes to dialogs, which is an awesome addition to the CRM toolbelt. Using the custom assembly above it’s not difficult, for example, to add a sophisticated mortgage calculator baked directly into the dialogs for a finance company call centre.
This Visual Studio 2010 project requires .NET 4 framework and CRM 2011 SDK. And, of course, CRM 2011 Beta to deploy.
If you’d like to take this code for a spin with CRM 4.0, this Visual Studio 2008 project requires .NET 3.5 and Microsoft CRM 4.0 SDK to build.
The usual warnings apply: use at your own risk, swim between the flags, choking hazard – small parts – not suitable for children under 3 years.
But wait, there is more! Sales tax and freight calculations often are quite complex and location-dependent. Creating a workflow that would correctly calculate taxes and freight charges in all circumstance is a formidable task. So how about storing the external (and hopefully thoroughly tested) code inside a custom entity and then use dynamic strategy to calculate tax and freight on the invoice where you can adjust calculations without recompiling, publishing or deploying anything! Unfortunately, the code too large to fit in the margin and will have to wait until next time.
Happy coding!