Changing Business Process Flow Stage in C# Plugin

Tim Hutchison picture Tim Hutchison · Jul 19, 2018 · Viewed 7.6k times · Source

I am following this article to change my Business Process Flow stage within a c# plugin. I am able to move the stage forward to the next stage, but I am receiving an error when I try to move back to a previous stage. The error below is what I receive in the UI from Dynamics. When I debug the plugin, I receive a FaultException<OrganizationServiceFault> exception that doesn't contain any information. Why am I receiving an error and how can I modify my code to successfully go back to a previous stage in my Business Process Flow?

Error

Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: An unexpected error occurred.
Detail: <OrganizationServiceFault xmlns="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <ActivityId>5df51362-b7c1-4817-a8d0-de2d63b15c17</ActivityId>
  <ErrorCode>-2147220970</ErrorCode>
  <ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
  <Message>An unexpected error occurred.</Message>
  <Timestamp>2018-07-19T18:55:42.6625925Z</Timestamp>
  <ExceptionSource i:nil="true" />
  <InnerFault>
    <ActivityId>5df51362-b7c1-4817-a8d0-de2d63b15c17</ActivityId>
    <ErrorCode>-2147220970</ErrorCode>
    <ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
    <Message>System.NullReferenceException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #0D309052</Message>
    <Timestamp>2018-07-19T18:55:42.6625925Z</Timestamp>
    <ExceptionSource i:nil="true" />
    <InnerFault i:nil="true" />
    <OriginalException i:nil="true" />
    <TraceText i:nil="true" />
  </InnerFault>
  <OriginalException i:nil="true" />
  <TraceText i:nil="true" />
</OrganizationServiceFault>

Plugin

if (localContext == null)
{
    throw new ArgumentNullException("localContext");
}

IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;

Client client = (Client)service.Retrieve(
    Client.LogicalName,
    new Guid("75FE165F-848B-E811-80F3-005056B33317"),
    new ColumnSet(new String[]{
        Client.Properties.ClientId
    })
);

client.ChangeStage(service);

Change Stage

public void ChangeStage(IOrganizationService service)
{
    // Get Process Instances
    RetrieveProcessInstancesRequest processInstanceRequest = new RetrieveProcessInstancesRequest
    {
        EntityId = this.Id,
        EntityLogicalName = this.LogicalName
    };

    RetrieveProcessInstancesResponse processInstanceResponse = (RetrieveProcessInstancesResponse)service.Execute(processInstanceRequest);

    // Declare variables to store values returned in response
    int processCount = processInstanceResponse.Processes.Entities.Count;
    Entity activeProcessInstance = processInstanceResponse.Processes.Entities[0]; // First record is the active process instance
    Guid activeProcessInstanceID = activeProcessInstance.Id; // Id of the active process instance, which will be used later to retrieve the active path of the process instance

    // Retrieve the active stage ID of in the active process instance
    Guid activeStageID = new Guid(activeProcessInstance.Attributes["processstageid"].ToString());

    // Retrieve the process stages in the active path of the current process instance
    RetrieveActivePathRequest pathReq = new RetrieveActivePathRequest
    {
        ProcessInstanceId = activeProcessInstanceID
    };
    RetrieveActivePathResponse pathResp = (RetrieveActivePathResponse)service.Execute(pathReq);

    string activeStageName = "";
    int activeStagePosition = -1;

    Console.WriteLine("\nRetrieved stages in the active path of the process instance:");
    for (int i = 0; i < pathResp.ProcessStages.Entities.Count; i++)
    {
        // Retrieve the active stage name and active stage position based on the activeStageId for the process instance
        if (pathResp.ProcessStages.Entities[i].Attributes["processstageid"].ToString() == activeStageID.ToString())
        {
            activeStageName = pathResp.ProcessStages.Entities[i].Attributes["stagename"].ToString();
            activeStagePosition = i;
        }
    }

    // Retrieve the stage ID of the next stage that you want to set as active
    activeStageID = (Guid)pathResp.ProcessStages.Entities[activeStagePosition - 1].Attributes["processstageid"];

    // Retrieve the process instance record to update its active stage
    ColumnSet cols1 = new ColumnSet();
    cols1.AddColumn("activestageid");
    Entity retrievedProcessInstance = service.Retrieve("ccseq_bpf_clientsetup", activeProcessInstanceID, cols1);

    // Set the next stage as the active stage
    retrievedProcessInstance["activestageid"] = new EntityReference(ProcessStage.LogicalName, activeStageID);
    service.Update(retrievedProcessInstance);
}

Update

I found this article that explains how to update the Stage using the Web API. When I try this method, I get the error:

An undeclared property 'activestageid' which only has property annotations in the payload but no property value was found in the payload. In OData, only declared navigation properties and declared named streams can be represented as properties without values.

I've tried a few varieties of 'activestageid' without success (ActiveStageId, _activestageid_value).


Update 2

Based on Arun's feedback, I tried the below Web API calls without success. The ID inside the brackets in the url (ccseq_bpf_clientsetups(###)) I pulled from the BusinessProcessFlowInstanceId on the ccseq_bpf_clientsetups table. The processstages ID I pulled from the ProcessStageId in the ProcessStageBase table

// Attempt 1
PATCH /COHEN/api/data/v8.2/ccseq_bpf_clientsetups(bc892aec-2594-e811-80f4-005056b33317) HTTP/1.1
{ "[email protected]": "/processstages(70018854-db7c-4612-915b-2ad7870a8574)"}

// Attempt 2
PATCH /COHEN/api/data/v8.2/ccseq_bpf_clientsetups(bc892aec-2594-e811-80f4-005056b33317) HTTP/1.1
{ "[email protected]": "/processstages(70018854-db7c-4612-915b-2ad7870a8574)"}

// Attempt 3
PATCH /COHEN/api/data/v8.2/ccseq_bpf_clientsetups(bc892aec-2594-e811-80f4-005056b33317) HTTP/1.1
{ "[email protected]": "/processstages(70018854-db7c-4612-915b-2ad7870a8574)"}

Update 3

I downloaded jLattimer's CRM Rest Builder and tried running the JavaScript his tool generated. The code was identical to what I had written previously and unfortunately did not work. At this point I'm fairly confident that changing stages is not supported in v8.2 of the Web API.

Answer

jasonscript picture jasonscript · Jul 30, 2018

I've got some code that attempts to move the Business Process Flow stage forward, as a custom workflow step (rather than a plugin). I've posted it below.

The difference that I see are:

  • I'm moving forwards (not backwards)
  • I'm probably not following best practice :)
  • I'm not retrieving the active path, I'm just getting all available stages for the process
  • I'm also setting the TraversedPath property

Code:

var activeInstancesRequest = new RetrieveProcessInstancesRequest
{
    EntityId          = TargetEntity.Id,
    EntityLogicalName = TargetEntity.LogicalName
};
var activeInstancesResponse = (RetrieveProcessInstancesResponse)base.OrgService.Execute(activeInstancesRequest);
var process = activeInstancesResponse.Processes.Entities.Select(x => x.ToEntity<BusinessProcessFlowInstance>()).ToList();
var stages = base.XrmContext.ProcessStageSet
    .Where(s => s.ProcessId.Id == process.FirstOrDefault().ProcessId.Id)
    .Select(s => new ProcessStage
    {
        ProcessStageId = s.ProcessStageId,
        StageName = s.StageName
    })
    .ToList();

var targetStage = stages.Where(stage => stage.StageName == targetStageName).FirstOrDefault();
if (targetStage != null)
{
    crmWorkflowContext.Trace($"BPF contains target stage (\"{targetStageName}\"). Attempting to update BPF");

    // Setting the Traversed Path is necessary for the Business Process Flow to show the active Stage
    // If this is not updated then although the new Stage is set as current, the previous Stage remains actively selected
    var traversedPath = $"{bpf.TraversedPath},{targetStage.ProcessStageId.Value}";
    var update = new BusinessProcessFlowInstance()
    {
        BusinessProcessFlowInstanceId = bpf.BusinessProcessFlowInstanceId,
        ProcessStageId                = targetStage.ProcessStageId,
        TraversedPath                 = traversedPath   
    };

    xrmContext.Attach(update);
    xrmContext.UpdateObject(update);
}