Power Fx via Custom API

With the release of Dependent Assemblies (preview) we can now use 3rd party libraries directly within plugins without the need for questionable ILMerge hacks. Lets use this to expand on the previous blog, Using Power Fx in Cloud Flows, so that we can more simply use Power Fx within a cloud flow.

Demo

The Dataverse connector provides the action ‘Perform an unbound action’ allowing us to call a Custom API in Dataverse. The custom API can fire a plugin, which using the new dependent assemblies functionality can now parse and evaluate Power Fx and return the result.

Here’s the same example from the previous blog, except using a Dataverse Custom API pgc_PowerFxEvaluate:

Example usage

When run we get the following output. The JSON field contains the results of the Power Fx evaluation, which we can provide to a subsequent ‘Parse JSON’ action.

Example output

Additionally, because we’re using a plugin we are able to produce more than just a JSON object. By using the ‘expando’ type we can add additional fields to the Output response. For some use cases this may eliminate the requirement for a ‘Parse JSON’ action.

Full output

In the example below we’re using the joined Power Fx result directly without needing a ‘Parse JSON’ action:

No Parse JSON needed

In some cases the ‘Output’ may not be usable, so the Custom API has a ‘JSONOnly’ parameter to turn this functionality off.

List Functions

As per the previous blog, we have also have a pgc_PowerFxListFunctions custom API, which merely displays the version and functions available in the Power Fx library.

List Functions flow

The output is a JSON list of functions, and the version of the Power Fx assembly.

List Functions output

Dependent Assemblies

The same logic for evaluating the Power Fx YAML is used from the previous blog. The following dependent assemblies need to be included:

  • Microsoft.PowerFx.Core & Microsoft.PowerFx.Interpreter - to parse and evaluate Power Fx expressions
  • Newtonsoft.Json - to convert between Power Fx objects, JSON and Expando objects
  • YamlDotNet - to parse the YAML
  • Microsoft.CSharp - to use dynamic Expando objects

For plugins created via pac plugin init --skip-signing, including a dependent assembly is as simple as dotnet add package YamlDotNet. Building the plugin in Visual studio or via dotnet build will create a nuget package, found in the bin\Debug or bin\Release folder, which can be imported into a Dataverse environment via Microsoft’s Plugin Registration Tool. The official docs provide good instructions.

We can check that the NuGet package contains our the dependent assemblies by opening it as a zip file (or using NuGet Package Explorer), e.g.

Nuget package contents

Power Fx exception

Initially, when using Power Fx as a dependent assembly within a plugin I found that I would get an error when calling any function in the Power Fx library, e.g.:

Message: The type initializer for 'Microsoft.PowerFx.Core.Types.Enums.EnumStoreBuilder' threw an exception.
System.Resources.MissingSatelliteAssemblyException: The satellite assembly named "Microsoft.PowerFx.Core.resources.dll, PublicKeyToken=31bf3856ad364e35" for fallback culture "en-US" either could not be found or could not be loaded. This is generally a setup problem. Please consider reinstalling or repairing the application.

The reason, as explained in this github issue, is because Power Fx uses satellite DLLs for resources. So, I needed to make the following tweaks to the plugin .csproj file that ensure the Microsoft.PowerFx.Core.resources.dll is included in the generated plugin package nuget.

csproj adjustments

With the plugin package imported into Dataverse a Custom API can be hooked up. Custom APIs can now be created using Microsoft’s Plugin Registration Tool, however I found it easier to use David Rivards Custom API Manager because it allowed easier editing after the initial upload. Here’s the Custom API definition:

Custom API Manager configuration

Note the ‘Output’ response property is of type ‘Entity’, but the entity is not specified (and is therefore expando), this allows us to create a dynamic and nested output.

Note that Custom APIs requests and responses can also use early bound classes, even in combination with expando. I’m using the excellent Early Bound Generator per the instructions in Daryl LaBar’s blog.

Source Code

The source code and solution for this are on my github at filcole/PowerFxViaCustomAPI.

Solutions

There are two solutions:

  • PowerFxCustomAPI - containing the CustomAPIs pgc_PowerFxListFunctions and pgc_PowerFxEvaluate
  • PowerFxCustomAPIExamples - containing five cloud flows demonstrating using the custom APIs - this requires the ‘PowerFxCustomAPI’ to be installed first.

Both solutions are available on my github in the ‘solutions’ folder in managed and unmanaged format.

Summary

What have we learnt?

  • Using a Custom API and plugin to host the Power Fx engine has a major advantage of not requiring a custom connector and associated azure function, and also is fully contained within a Dataverse solution which simplifies ALM.
  • Inconsiderate use of Power Fx has the potential to take a long time to run, and which may hit the platform service protection execution time limits.
  • Expando gives the ability to directly reference Power Fx results in Power Automate cloud flows
  • Expando only supports simple types (strings, integers, doubles, booleans) in an entity, string arrays, or EntityCollections. This means that list of non-string fields have to be returned as an EntityCollection of single field Entities - which is very verbose.
  • Power Automate and expando responses have minimal understanding of types, whereas Power Fx is strongly typed. It would be nice if the strong typing of Power Fx could be used within Power Automate.
  • Editing Power Fx outside of a dedicated editor is still painful!
  • It may be possible to consume the custom API via other means, such as via the Xrm.WebAPI
  • There may be limits on the size of the request and response to a Custom API.
  • Dependent assemblies are generally incredibly easy to use, I had no issue with NewtonSoft or YamlDotNet libraries, and even Power Fx is simple once the issue with satellite DLLs is understood.
  • Both dependent assemblies and the Power Fx libraries are in preview - so production use best avoided!

References