Its funny how having extra time during a pandemic lets your mind wander. Recently I wrote a parser to allow script functionality in a personal non CRM related project of mine in a language I am calling ianScript (its my language I can call it what I like and scripts are usually faster to work with than all this fancy GUI window nonsense) and I started thinking what if you added some scripting functionality into a Dynamics365 plugin. Before everyone throws their hands up in horror and tells me it would be an abomination I really couldn’t see it ever being used in production but as a bit of fun I think it could be quite interesting. But then boredom does funny things to the mind. And yes if you added a full parser and scripting engine it would sit on Azure rather than as a simple plugin but any work done on it is simply a proof of concept to amuse myself.
My thinking goes something like this, currently we have a VAT rate of 20%. Most of us have written automation to cope with the financial aspects of Dynamic365 to automatically calculate VAT. But what if it changes. Simple I hear you say, I modify my automation to cope with the new percentage rate, or if I have been really clever I pick up the percentage value from an entity record in the system and the job is done. So far yes that makes sense.
But how about instead of embedding the calculation in the plugin you pass the calculation you want along with the values you want to calculate. To make this easier to play with I have created it as a simple workflow plugin, I know workflows aren’t really flavour of the month these days but along with the non unified interface and dialogs (shock!) it made it quick to test.
So in my idea you pass in the variables and the expression. You could of course just pass in the expression with the variable values in it but in my mind making the inputs more complicated only leads to errors.
So I have 4 input fields, VAR1-VAR3 which are the variables, and EXPRESSION which is the expression, and a single output field called RESULT.
So in the VAT example VAR1 would have a value of the amount you wish to calculate say 80, VAR2 would have the rate of 20 and expression would contain @a+(@a * (@b/100)). The @a and @b are the values that will be replaced as the expression gets evaluated with the variable values as they get passed in.
For the proof of concept it would have been really useful if C# had its own EVAL function like JavaScript, but unfortunately it doesn’t. The JavaScript EVAL function is a not often used function that allow the string passed to it to be evaluated and return a result directly. Obviously doing anything like that could be a massive security hole but if its only done between friends then what could the harm be. Fortunately C# has a workaround. In the DataTable class there is a function called Compute that allows the same thing, it is really designed to work with data in the table but for a proof of concept it does what I need.
DataTable table = new DataTable();
return Convert.ToString(table.Compute(expression, String.Empty));
So these two lines do the heavy lifting, and for the above example I could simply have passed in 80+(80 * (20/100)) and it would return the result 96. But separate input values make more sense as numbers would change, as it would be a bit limited if they didn’t.
The ‘calculation’ also works for string values too, so if I entered the variables VAR1 =’value1′ and VAR2=’value2′ then the results of @a+@b would be ‘value1value2’, although if I tried to do the calculation above then it would return an error.
Moving forward if after the pandemic ends the UK is in such a state financially that they need to increase the VAT rate to 25% then the expression input value could be the same but VAR2 would increase to 25. This would parse to 80 + (80 * (25 /100)) and the result would be 100. Although if you have written your automation to take the VAT rate from an entity record then you are still sorted.
What if the chancellor takes over and decides to make things more complicated, and decides to make it a VAT rate of 21% + £2 on every transaction. Well unless you over thought your VAT rate plugin initially and had access to a time machine or a crystal ball then a rewrite is going to be needed. But not if you are using an expression all you have to do is change the expression to be (@a+(@a * (@b/100)))+2 or (@a+(@a * (@b/100)))+@c if you starting using a third variable and make VAR3=2. This is a bit of a simplistic example and you are probably still thinking hmmm I can just write this into my plugin and it won’t be a problem, which is true, but the functionality could be expanded greatly with the addition of some more complex custom code rather than simply using a C# DataTable function.
This is the test rig in a Dynamics365 Dialog and the results in this case would be:
The dialog means that I can easily play with expressions and values to get results, but I wouldn’t use it ever in a production environment.
The proof of concept really only handles basic mathematical operations like addition, subtraction,multiplication,division and modulus, but adding in a custom parser would allow a lot more functions to be evaluation, even trigonometric operations or basic CRUD on the Dynamics365 database. All this could be modified after the plugin creation by a simple change of expression allowing for almost limitless flexibility.
The code is here for the proof of concept plugin.
public class ThoughtExperimentPlugin : CodeActivity
{
protected override void Execute(CodeActivityContext ActivityContext)
{
IWorkflowContext context = ActivityContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = ActivityContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService _service = serviceFactory.CreateOrganizationService(context.InitiatingUserId);
ITracingService _tracing = ActivityContext.GetExtension<ITracingService>();
string var1 = VAR1.Get(ActivityContext);
string var2 = VAR2.Get(ActivityContext);
string var3 = VAR3.Get(ActivityContext);
string expression = EXPRESSION.Get(ActivityContext);
string result = Eval(expression, new string[] { var1, var2, var3 });
RESULT.Set(ActivityContext, result);
}
public string Eval(string expression, string[] vars)
{
double dummyValue = 0;
string variableName = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
bool isNumeric = false;
for (int x = 0; x < vars.Length; x++)
{
if (double.TryParse(vars[x], out dummyValue)) isNumeric = true;
}
for (int x = 0; x < vars.Length; x++)
{
if (isNumeric && string.IsNullOrWhiteSpace(vars[x])) { vars[x] = "0"; }
else
if (!isNumeric) { vars[x] = "'" + vars[x] + "'"; }
expression = new Regex("@" + variableName[x], RegexOptions.IgnoreCase).Replace(expression, vars[x]);
}
DataTable table = new DataTable();
return Convert.ToString(table.Compute(expression, String.Empty));
}
[Input("Variable1")]
public InArgument<string> VAR1 { get; set; }
[Input("Variable2")]
public InArgument<string> VAR2 { get; set; }
[Input("Variable3")]
public InArgument<string> VAR3 { get; set; }
[Input("Expression to Resolve")]
public InArgument<string> EXPRESSION { get; set; }
[Output("Result")]
public OutArgument<string> RESULT { get; set; }
}
The code takes 3 variables as an input, and an expression and returns the result.
The Eval function performs two passes through the variables, the first checks to see if we are dealing with numbers or strings, as strings will be expected to have ” around them, numbers won’t and blank values in that case will be handled as 0. The code also has the facility for 26 variables A-Z as the data is passed in as a string[].
The final expression to be evaluated is built using REGEX before being passed to the Compute function.
In a later version this Eval function would be replaced with custom code to handle an expanded feature set instead of simple math operators, so the expression could look something like this to perform a series of tasks
Calculate @VAT = (@a + (@a * (@b/100))+@c
Update field @d
Create Entity #foo { 'foovalue','morefoo',@VAT }
Post Message @address, JSON('value',@VAT)
This is probably total overkill and is too much processing for what would be a simple plugin, it is only an idea.
Also it would probably not be a workflow plugin, it would have to be hosted in Azure as it would be big, and registered against various entities and actions and take the expressions and field values from a record in an entity created specifically for the purpose, with records relating to each action.
Although it does seem like too much work for something that probably has no practical value except as an idea.
Idle thoughts, this is what happens during a pandemic.