Low-code platforms have become increasingly popular in recent years, offering a more accessible approach to software development. Among these, the Power Platform by Microsoft stands out for its versatility and ease of use.
However, as with any technology, there’s a fine line between use and overuse. The allure of low code solutions can sometimes lead to their application in scenarios where traditional coding would be more suitable or efficient, and continued use can often bring systems to a halt, damaging user engagement.
Within this article, I aim to complete a deep dive into Microsoft’s Power Fx low-code plugins, understanding their inner workings in order to better understand any considerations that need to be made when creating solutions.
Table of Contents
What is Power Fx?
Power Fx is a Microsoft developed low-code language that is being rolled out across the Power Platform, from Canvas Apps to Power Automate Cloud Flows, and more recently, in the form of a preview feature, low-code plugins.
An overview of Power Fx in Microsoft’s words can be seen here, and for more information on Power Fx for low-code plugins, please see this article by Microsoft. If you’d like to check out the formula and syntax reference for the Power Fx language, Microsoft have published a formula reference guide.
How to manage low-code plugins in the Power Platform?
You are able to create and edit Power Fx low-code plugins through the Dataverse Accelerator App deployed to your instance of Power Platform. This model drive application is provided by Microsoft and should be owned by “SYSTEM” and shared with all System Customizers or System Administrators.
Given that Power Fx low-code plugins are a preview feature at this point in time, I’m certain that other methods of managing and creating low-code plugins will become available, such as using the solution management interface, however, at present, it’s only possible to create a low code plugin via the “Dataverse Accelerator App” published with your Power Platform instance.
If you attempt to open the configuration for the Power Fx Expression via a Solution you will be faced with the following error. Hopefully Microsoft resolve this, as most developers and consultants will work from a solution and not a custom Model Driven App.
Creating a low-code plugin
Once you’ve loaded up the Dataverse Accelerator Model Driven Application, you will be faced with the ability to create Instant Plugins or Automated Plugins through the user interface.
Instant Plugins
Instant plugins are reusable Power Fx expressions that can be centrally managed via a single, reusable action, they can be triggered through Model Driven Apps, Cloud Flows or even via the Dataverse API.
These types of low-code plugins take in parameters much like Custom Actions, and can provide outputs without the need to respond to events within the Power Platform like transitional plugins.
Automated Plugins
Automated plugins are more like the coded-plugins that we have used in the past, these plugins will execute automatically in response to data being created, updated or deleted within Dataverse.
Solution management
Like all other solution aware components, Power Fx low-code plugins do get added to a solution that you have selected during the creation process (You can click “Advanced” to specify a solution). However, you are free to manually add the components to an existing or new solution if you don’t change this during creation.
Two new solution components are added to the solution on creation of a Automated Plugin, these are as seen below;
As seen above, we can’t dive into either of these solution components at the moment, as we get an error when attempting to open the FxExpression object, and the plugin-step does not have a GUI available via the web UI.
However, we are able to open up the plugin step via the Plugin Registration Tool. The plugin step is registered under the “Microsoft.PowerFx.PlexPlugins” assembly, under a plugin called “Microsoft.PowerFx.Evaluator”.
As we can see in the configuration above, there is no Unsecure or Secure configuration defined, which I guess would be expected, as that would be stored within the FxExpression element within the solution, however, there is no link between the FxExpression and the Plugin Step that we can see via the Plugin Registration Tool.
To understand how the FxExpression and Plugin Step relate, I next extracted an unmanaged solution containing my Power Fx low-code plugin from Dataverse and using the Microsoft Solution Packager tool, extracted the solution down into individual files.
SdkMessageProcessingStep
{93521dc7-0e8e-ee11-8178-7c1e520403e0}.xml
<?xml version="1.0" encoding="utf-8"?>
<SdkMessageProcessingStep Name="Duplicate Contacts" SdkMessageProcessingStepId="{93521dc7-0e8e-ee11-8178-7c1e520403e0}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SdkMessageId>9ebdbb1b-ea3e-db11-86a7-000a3a5473e8</SdkMessageId>
<PluginTypeName>Microsoft.PowerFx.Evaluator, Microsoft.PowerFx.PlexPlugins, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</PluginTypeName>
<PluginTypeId>462b3e8f-beb4-4981-9b87-a60c2162a9f7</PluginTypeId>
<fxexpressionid>
<uniquename>igl_DuplicateContacts</uniquename>
</fxexpressionid>
<PrimaryEntity>contact</PrimaryEntity>
<AsyncAutoDelete>0</AsyncAutoDelete>
<FilteringAttributes></FilteringAttributes>
<InvocationSource>1</InvocationSource>
<Mode>0</Mode>
<Rank>1</Rank>
<EventHandlerTypeCode>4602</EventHandlerTypeCode>
<Stage>20</Stage>
<IsCustomizable>1</IsCustomizable>
<IsHidden>0</IsHidden>
<SupportedDeployment>0</SupportedDeployment>
<IntroducedVersion>1.0.0.0</IntroducedVersion>
<SdkMessageProcessingStepImages />
</SdkMessageProcessingStep>
So while we could not see the FxExpression via the Plugin Registration tool, a new XML Element called fxexpressionid has been inserted into the SdkMessageProcessingStep output, which completes the link from the Plugin Step to the Expression it should call.
FxExpressions
A series of files in a folder called igl_DuplicateContact have been created, and as expected, these track the dependencies to the Contact entity, the actual expression itself and finally the configuration for the plugin as well as it’s managed state.
dependencies.json
{
"RequiredEntityAttributeMap": {
"contact": [
"lastname",
"firstname"
]
},
"RequiredEntityReferences": null
}
expression.yaml
If( !IsBlank(LookUp([@contact],lastname=ThisRecord.lastname && firstname=ThisRecord.firstname)),
Error("You have existing contacts with the same first name and last name")
)
fxexpression.json
{
"fxexpression": {
"@uniquename": "igl_DuplicateContacts",
"category": "0",
"entitylogicalname": "contact",
"iscustomizable": "1",
"messagename": "Create",
"name": "Duplicate Contacts",
"statecode": "0",
"statuscode": "1"
}
}
How does the plugin work?
In order to understand how low-code plugins work behind the scene, we are going to need to start to dive into actual code. Using the Assembly Recovery Tool via XrmToolbox, we can extract the Microsoft.PowerFx.PlexPlugins assembly, and decompile using a tool like dotPeek.
The first thing we notice when viewing the decompiled code is a dependency on the following open source library produced by Microsoft: Microsoft.PowerFx.Core. This library has open source code on GitHub, and you can download the profile to view the full source code as required.
Other than relying on this dependency for the heavy lifting, the PlexPlugins assembly also includes the expected IPlugin implementation for low-code plugins and some helper classes, as seen below;
Within the IPlugin implementation, a function called “GetCompiledExpression” is also present, which retrieves compiled expression column from the fxexpression entity within Dataverse. This can be seen below;
Before we take a look at what is stored within the compiledexpression attribute, lets identify how the plugin extracts the GUID of the rule, as the step only contains the unique name of the FxExpression. After decompiling the code, it appears Microsoft are leaning on a new Service from the Service Provider (IPowerFxService) to extract the FxExpression Id from the plugin configuration, as seen below;
Now lets take a look at this entity in more detail by retrieving the data relating to our Power Fx expression. We can use the FetchXML builder by Jonas Rapp with XrmToolbox to extract information from this table. The following information is returned from the compiledexpression column.
eyJfdmVyc2lvbiI6MiwiX2V4cHIiOiJJZiggIUlzQmxhbmsoTG9va1VwKFtAY29udGFjdF0sbGFzdG5hbWU9VGhpc1JlY29yZC5sYXN0bmFtZSAmJiBmaXJzdG5hbWU9VGhpc1JlY29yZC5maXJzdG5hbWUpKSxcclxuICAgIEVycm9yKFwiWW91IGhhdmUgZXhpc3RpbmcgY29udGFjdHMgd2l0aCB0aGUgc2FtZSBmaXJzdCBuYW1lIGFuZCBsYXN0IG5hbWVcIilcclxuKSIsIl9kZXNjciI6eyJMb2dpY2FsTmFtZSI6ImNvbnRhY3QiLCJNZXNzYWdlIjoiQ3JlYXRlIiwiVGFibGVzIjpbImNvbnRhY3QiXX0sIl9jb29raWUiOiJ2MSJ9
This has returned a Base64 encoded string, which appears to be a version of the expression and associated metadata within a JSON object, decoding the Base64 encoded string (using a tool like base64decode) returns the following:
{
"_version": 2,
"_expr": "If( !IsBlank(LookUp([@contact],lastname=ThisRecord.lastname && firstname=ThisRecord.firstname)),\r\n Error(\"You have existing contacts with the same first name and last name\")\r\n)",
"_descr": {
"LogicalName": "contact",
"Message": "Create",
"Tables": ["contact"]
},
"_cookie": "v1"
}
Once the plugin has access to the compiled version of the FxExpression, it populates a concurrent cache, which is exposed as a dictionary of string to string within the Plugin implementation. When a message is processed, a hash of the FxExpression is stored linked to the compiledexpression so that it does not need to be retrieved from Dataverse a second or third time.
After the plugin has extracted all the information needed, and cached the expression for future runs, the request is offloaded to the Microsoft.PowerFx.Core library.
PowerFx.Core: where the magic is!
Lucky for us, we no longer need to decompile code, as the Microsoft.PowerFx.Core library is totally open source, and accessible via GitHub! While I won’t attempt to document the entire code base Microsoft have provided, I’m going to attempt to document the fundamentals of the solution Microsoft have provided.
For the following example, I’m going to use the following PowerFx command, which results in an output of 5.
Sum(1) + Sum(1) + Sum(1) + Sum(1) + Sum(1)
When the code is being evaluated, it’s converted to types and unnecessary characters are stripped out, this results in a conversion to the following expression:
AddDecimals:w(AddDecimals:w(AddDecimals:w(AddDecimals:w(Sum:w(1:w), Sum:w(1:w)), Sum:w(1:w)), Sum:w(1:w)), Sum:w(1:w))
In the above, each “+” has been stripped out with a new function called “AddDecimals”, which returns a decimal (“:w”). The Sum function has also been extended to support the return type, and the number attribute has been defined with its type as decimal as well.
Given that the AddDecimals function takes two parameters and returns a decimal, this has been layered so that there is N-1 functions for each attribute. In the example above, the last “1” is being added to “4”, the “4” is being calculated by adding “1” to “3”… and so on.
Each function within PowerFx correlates to a class or a type of node within the Core library, and these can be find via the GitHub repo, for example, you can find the class for the functions above:
- AddDecimals (A type of BinaryOpNode)
- SumFunction (An actual defined function, inheriting from StatisticalFunction)
The actual magic, where the PowerFx library parses these text based functions happens in the TexlParser class, there are some aptly named functions that handle the recursive nature of parsing the converted expression. The “Parse” function returns a root TexlNode object, with based on the way the platform converts the expression, there is always a root node (in our example, “AddDecimals”, and a recursive function within each node parses other nodes.
The final output is wrapped up in a result object, which return 5!
Anything to watch out for?
A few things, yes…
Performance
While basic operations on the same function are relatively performant, Power Fx expressions that utilise a Dataverse connection to interact with other entities are not. Operations such as LookUp, CountIf, SumIf, etc. utilising a Dataverse connection with a condition inefficiently retrieve records and loop over the results, rather than letting Dataverse query out the ineligible records.
Power Fx low-code functions also complete another submission back to Dataverse to update or create data where configured, instead of using the target object to avoid the extra call, this could be concern for anyone on licenses where users are limited to the number of API operations per day.
Dataverse Lookups & Counts limited to 1000 records
Functions that interact with Dataverse data are limited to only retrieving the first 1,000 records and do not support paging! Therefore, it’s not a suitable use case to use Power Fx plugins to validate, count or sum child data from a parent – one of the most common actions required in simple plugins.
Synchronous only
Functions working with PowerPlatform connectors can be very slow – and these plugins run synchronously, meaning the user needs to wait for the plugin to complete before they can continue their work.
Bugs
There are some bugs… However, thats to be expected with any preview software. The worst bug I’ve seen so far is due to how Microsoft have handled the implementation of the PlexPlugins implementation, where they have used global variables to store the ID of the rule to execute, breaking one of their most fundamental rules of plugin development: Develop IPlugin implementations as stateless and randomly causing some “triggers” to run the wrong function.
I’m certain the bugs will be resolved as Power Fx low-code plugins become closer to released, and I will update this article with additional information as time goes on.
… in conclusion?
Power Fx low-code plugins are a huge step forward for the Power Platform, and as consultants and developers start to use this powerful new technology, it’s going to be interesting to see the use cases where they become a staple part of a developers toolkit.
Overall, I don’t see Power Fx plugins replacing actual plugins, instead, I see them as a tool to replace simple real-time workflows.