The Microsoft Dynamics CRM Blog
News and views from the Microsoft Dynamics CRM Team

The Last Workflow Extension I’ll Ever Need

The Last Workflow Extension I’ll Ever Need

  • Comments 4

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:

image

And here is the use:

image

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.

Sigh

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.

Meh

Instead, what we ended up with looks like this:

image

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!

George Doubinski



  • doesn't this allow you to execute arbitary code?

    You might find a mathematical expression language such as NCalc far better suited for this purpose. - see http://NClac.codeplex.com

    David

  • David,

    re: arbitrary code execution. Not exactly but that's the whole idea. The code must comply with the type requirements above, is written by a workflow developer (and is, therefore, governed by security roles) and is not available to or controlled by the users.

    re: NCalc - spot on. I did do some research on expression engines and, indeed, NCalc was on top of my list. The challenge with any parsing is how to pass strongly-typed parameters and how to extract (strongly-typed) results. In this post I've tried to maintain the balance between flexibility and ease of use but the reference to "code too large to fit in the margin" is a hint that there is more to come. Stay tuned!

    George

  • For CRM2011 what about defining the function in a webresource and invoking the function in a sandboxed plugin rather than a custom workflow - is this possible?

    Chris

  • Chris,

    our initial requirement was to provide a simple "engine" for calculations in the workflow which has no access to sandboxing.

    However, the idea to use webresources is quite interesting but I have not looked into it yet. My initial thoughts were to use custom entity to store the code - I think that would give us a bit more flexibility as far as code security is concerned.

    Most certainly, 2011 gives us more options and not enough time to look at them all!

    George

Page 1 of 1 (4 items)
Leave a Comment
  • Please add 5 and 4 and type the answer here:
  • Post