A step-by-step guide to programmatically move BPF stages backward in Dataverse using a C# plugin
Introduction
In Microsoft Dynamics 365 and Dataverse, Business Process Flows (BPFs) guide users through predefined stages, typically moving forward. But what if you need to move backward? For example, from stage 2 to stage 1 based on a field value like "No"? The BPF designer doesn’t support backward movement natively, but a server-side plugin can make it happen. In this post, we’ll walk through how to create a plugin that moves a BPF stage backward when a condition is met, complete with code and setup instructions.
The Requirement
Imagine you’re working with an account entity and a BPF called "Account Process Flow." At stage 2, a choice field (new_decisionfield) determines the next step:
- If the value is "Yes," move to stage 3.
- If the value is "No," move back to stage 1.
Since connectors in Power Automate and the BPF designer only support forward movement, we’ll use a plugin to update the activestageid field of the BPF instance.
Step-by-Step Guide
Step 1: Gather Required Information
Before coding, collect these details:
- BPF Stage IDs: Find the GUIDs for stage 1 and stage 3 in the
processstagestable using XrmToolBox or FetchXML. Example:- Stage 1:
12345678-1234-1234-1234-1234567890ab - Stage 3:
98765432-4321-4321-4321-9876543210ba
- Stage 1:
- BPF Instance Table: Identify the logical name (e.g.,
accountprocessflow). - Field Name: Confirm the logical name of the choice field (e.g.,
new_decisionfield) and its option set values (e.g., Yes = 100000000, No = 100000001).
Step 2: Set Up the Development Environment
You’ll need:
- Visual Studio (2019 or 2022).
- Microsoft Power Platform CLI or Dynamics 365 SDK for
Microsoft.Xrm.Sdk. - Plugin Registration Tool (via XrmToolBox or Power Platform CLI).
Create a Class Library (.NET Framework 4.6.2 or 4.7.2) and add the Microsoft.CrmSdk.CoreAssemblies NuGet package:
Install-Package Microsoft.CrmSdk.CoreAssemblies
Step 3: Write the Plugin
The plugin triggers on the Update message for the account entity, checks the new_decisionfield value, and updates the BPF’s activestageid. Here’s the code:
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
namespace BPFStageMover
{
public class MoveBPFStage : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
try
{
// Check if the event is an update on the account entity
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity target = (Entity)context.InputParameters["Target"];
// Check if decision_field is in the target entity
if (!target.Contains("new_decisionfield"))
{
tracingService.Trace("decision_field not updated, exiting plugin.");
return;
}
// Get the decision field value (OptionSetValue)
OptionSetValue decisionValue = target.GetAttributeValue("new_decisionfield");
if (decisionValue == null)
{
tracingService.Trace("decision_field is null, exiting plugin.");
return;
}
// Define stage GUIDs (replace with actual stage IDs from processstages table)
Guid stage1Id = new Guid("12345678-1234-1234-1234-1234567890ab"); // Stage 1 GUID
Guid stage3Id = new Guid("98765432-4321-4321-4321-9876543210ba"); // Stage 3 GUID
// Get the BPF instance for the account
Guid accountId = context.PrimaryEntityId;
QueryExpression query = new QueryExpression("accountprocessflow")
{
ColumnSet = new ColumnSet("activestageid"),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression("regardingobjectid", ConditionOperator.Equal, accountId)
}
}
};
EntityCollection bpfInstances = service.RetrieveMultiple(query);
if (bpfInstances.Entities.Count == 0)
{
tracingService.Trace("No BPF instance found for account {0}.", accountId);
return;
}
Entity bpfInstance = bpfInstances.Entities[0];
Guid currentStageId = bpfInstance.GetAttributeValue("activestageid")?.Id ?? Guid.Empty;
// Only proceed if the current stage is stage 2 (optional: add stage 2 GUID check)
// Update activestageid based on decision_field value
Guid newStageId = decisionValue.Value == 100000000 ? stage3Id : stage1Id; // 100000000 = Yes, 100000001 = No
if (currentStageId != newStageId)
{
Entity bpfUpdate = new Entity("accountprocessflow")
{
Id = bpfInstance.Id,
["activestageid"] = new EntityReference("processstage", newStageId)
};
service.Update(bpfUpdate);
tracingService.Trace("BPF stage updated to {0} for account {1}.", newStageId, accountId);
}
else
{
tracingService.Trace("No stage change needed.");
}
}
}
catch (Exception ex)
{
tracingService.Trace("Error in MoveBPFStage plugin: {0}", ex.Message);
throw new InvalidPluginExecutionException($"Plugin error: {ex.Message}");
}
}
}
}
Step 4: Build and Register the Plugin
- Build: Compile the solution in Visual Studio to generate the DLL (e.g.,
BPFStageMover.dll). - Register: Use the Plugin Registration Tool to:
- Upload the DLL and select the
MoveBPFStageclass. - Register a step:
- Message:
Update - Primary Entity:
account - Filtering Attributes:
new_decisionfield - Stage: PostOperation, Synchronous
- Message:
- Upload the DLL and select the
Step 5: Test the Plugin
Update the new_decisionfield on an account record and verify the BPF stage moves to stage 3 (Yes) or stage 1 (No). Check trace logs in Dataverse if issues arise.
Plugin Trace Log table.
Key Notes
- Replace GUIDs: Update the stage GUIDs in the code with actual values from your
processstagestable. - BPF Table: Use the correct logical name for your BPF instance table (e.g.,
accountprocessflow). - Option Set Values: Adjust
100000000(Yes) and100000001(No) to match your field’s values. - Security: Ensure the plugin runs under a user with permissions to update BPF instances.
Conclusion
By using a plugin to update the activestageid, you can overcome the BPF designer’s forward-only limitation, enabling dynamic stage movement in Dynamics 365. This solution is robust, scalable, and perfect for scenarios requiring flexible process control. Try it out, and let us know in the comments if you have questions or need further customization!
No comments:
Post a Comment