Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix plugin vs workflow execution order #242

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions src/XrmMockupShared/Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,6 @@ internal OrganizationResponse Execute(OrganizationRequest request, EntityReferen
}

Entity preImage = null;
Entity postImage = null;

if (settings.TriggerProcesses && entityInfo != null)
{
Expand All @@ -614,9 +613,9 @@ internal OrganizationResponse Execute(OrganizationRequest request, EntityReferen
if (settings.TriggerProcesses && entityInfo != null)
{
// System Pre-validation
pluginManager.TriggerSystem(eventOp, ExecutionStage.PreValidation, entityInfo.Item1, preImage, postImage, pluginContext, this);
pluginManager.TriggerSystem(eventOp, ExecutionStage.PreValidation, entityInfo.Item1, preImage, null, pluginContext, this);
// Pre-validation
pluginManager.Trigger(eventOp, ExecutionStage.PreValidation, entityInfo.Item1, preImage, postImage, pluginContext, this);
pluginManager.TriggerSync(eventOp, ExecutionStage.PreValidation, entityInfo.Item1, preImage, null, pluginContext, this, (_) => true);
}

//perform security checks for the request
Expand All @@ -629,11 +628,12 @@ internal OrganizationResponse Execute(OrganizationRequest request, EntityReferen
pluginContext.SharedVariables.Clear();

// Pre-operation
pluginManager.Trigger(eventOp, ExecutionStage.PreOperation, entityInfo.Item1, preImage, postImage, pluginContext, this);
workflowManager.TriggerSync(eventOp, ExecutionStage.PreOperation, entityInfo.Item1, preImage, postImage, pluginContext, this);
pluginManager.TriggerSync(eventOp, ExecutionStage.PreOperation, entityInfo.Item1, preImage, null, pluginContext, this, (p) => p.GetExecutionOrder() == 0);
workflowManager.TriggerSync(eventOp, ExecutionStage.PreOperation, entityInfo.Item1, preImage, null, pluginContext, this);
pluginManager.TriggerSync(eventOp, ExecutionStage.PreOperation, entityInfo.Item1, preImage, null, pluginContext, this, (p) => p.GetExecutionOrder() != 0);

// System Pre-operation
pluginManager.TriggerSystem(eventOp, ExecutionStage.PreOperation, entityInfo.Item1, preImage, postImage, pluginContext, this);
pluginManager.TriggerSystem(eventOp, ExecutionStage.PreOperation, entityInfo.Item1, preImage, null, pluginContext, this);
}

// Core operation
Expand All @@ -642,8 +642,6 @@ internal OrganizationResponse Execute(OrganizationRequest request, EntityReferen
// Post-operation
if (settings.TriggerProcesses && entityInfo != null)
{
postImage = TryRetrieve(primaryRef);

// In RetrieveMultipleRequests, the OutputParameters bag contains the entity collection
if (request is RetrieveMultipleRequest)
{
Expand All @@ -657,15 +655,21 @@ internal OrganizationResponse Execute(OrganizationRequest request, EntityReferen

if (!string.IsNullOrEmpty(eventOp))
{
var syncPostImage = TryRetrieve(primaryRef);

//copy the createon etc system attributes onto the target so they are available for postoperation processing
CopySystemAttributes(postImage, entityInfo.Item1 as Entity);
CopySystemAttributes(syncPostImage, entityInfo.Item1 as Entity);

pluginManager.TriggerSystem(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, syncPostImage, pluginContext, this);

pluginManager.TriggerSync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, syncPostImage, pluginContext, this, (p) => p.GetExecutionOrder() == 0);
workflowManager.TriggerSync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, syncPostImage, pluginContext, this);
pluginManager.TriggerSync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, syncPostImage, pluginContext, this, (p) => p.GetExecutionOrder() != 0);

pluginManager.TriggerSystem(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, postImage, pluginContext, this);
pluginManager.TriggerSync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, postImage, pluginContext, this);
pluginManager.StageAsync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, postImage, pluginContext, this);
var asyncPostImage = TryRetrieve(primaryRef);

workflowManager.TriggerSync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, postImage, pluginContext, this);
workflowManager.StageAsync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, postImage, pluginContext, this);
pluginManager.StageAsync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, asyncPostImage, pluginContext, this);
workflowManager.StageAsync(eventOp, ExecutionStage.PostOperation, entityInfo.Item1, preImage, asyncPostImage, pluginContext, this);
}

//When last Sync has been executed we trigger the Async jobs.
Expand Down
71 changes: 31 additions & 40 deletions src/XrmMockupShared/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,53 +251,44 @@ private void SortAllLists(Dictionary<string, Dictionary<ExecutionStage, List<Plu
}
}

/// <summary>
/// Trigger all plugin steps which match the given parameters.
/// </summary>
/// <param name="operation"></param>
/// <param name="stage"></param>
/// <param name="entity"></param>
/// <param name="preImage"></param>
/// <param name="postImage"></param>
/// <param name="pluginContext"></param>
/// <param name="core"></param>
public void Trigger(string operation, ExecutionStage stage,
object entity, Entity preImage, Entity postImage, PluginContext pluginContext, Core core)
{
if (!disableRegisteredPlugins && registeredPlugins.ContainsKey(operation) && registeredPlugins[operation].ContainsKey(stage))
registeredPlugins[operation][stage].ForEach(p => p.ExecuteIfMatch(entity, preImage, postImage, pluginContext, core));
if (temporaryPlugins.ContainsKey(operation) && temporaryPlugins[operation].ContainsKey(stage))
temporaryPlugins[operation][stage].ForEach(p => p.ExecuteIfMatch(entity, preImage, postImage, pluginContext, core));
}


//Post operation - Trigger Sync and Async in that order
public void TriggerSync(string operation, ExecutionStage stage,
object entity, Entity preImage, Entity postImage, PluginContext pluginContext, Core core)
object entity, Entity preImage, Entity postImage, PluginContext pluginContext, Core core, Func<PluginTrigger, bool> executionOrderFilter)
{
if (!disableRegisteredPlugins && registeredPlugins.ContainsKey(operation) && registeredPlugins[operation].ContainsKey(stage))
registeredPlugins[operation][stage].Where(p => p.GetExecutionMode() == ExecutionMode.Synchronous)
.OrderBy(p => p.GetExecutionOrder()).ToList().ForEach(p => p.ExecuteIfMatch(entity, preImage, postImage, pluginContext, core));
if (temporaryPlugins.ContainsKey(operation) && temporaryPlugins[operation].ContainsKey(stage))
temporaryPlugins[operation][stage].Where(p => p.GetExecutionMode() == ExecutionMode.Synchronous)
.OrderBy(p => p.GetExecutionOrder()).ToList().ForEach(p => p.ExecuteIfMatch(entity, preImage, postImage, pluginContext, core));
if (!disableRegisteredPlugins && registeredPlugins.TryGetValue(operation, out var operationPlugins) && operationPlugins.TryGetValue(stage, out var stagePlugins))
stagePlugins
.Where(p => p.GetExecutionMode() == ExecutionMode.Synchronous)
.Where(executionOrderFilter)
.OrderBy(p => p.GetExecutionOrder())
.ToList()
.ForEach(p => p.ExecuteIfMatch(entity, preImage, postImage, pluginContext, core));

if (temporaryPlugins.TryGetValue(operation, out var tempOperationPlugins) && tempOperationPlugins.TryGetValue(stage, out var tempStagePlugins))
tempStagePlugins
.Where(p => p.GetExecutionMode() == ExecutionMode.Synchronous)
.Where(executionOrderFilter)
.OrderBy(p => p.GetExecutionOrder())
.ToList()
.ForEach(p => p.ExecuteIfMatch(entity, preImage, postImage, pluginContext, core));
}

public void StageAsync(string operation, ExecutionStage stage,
object entity, Entity preImage, Entity postImage, PluginContext pluginContext, Core core)
{
if (!disableRegisteredPlugins && registeredPlugins.ContainsKey(operation) && registeredPlugins[operation].ContainsKey(stage))
{
var asyncExecutors = registeredPlugins[operation][stage].Where(p => p.GetExecutionMode() == ExecutionMode.Asynchronous)
.OrderBy(p => p.GetExecutionOrder()).ToList().Select(p => p.ToPluginExecution(entity, preImage, postImage, pluginContext, core));
asyncExecutors.ToList().ForEach(x => pendingAsyncPlugins.Enqueue(x));
}
if (temporaryPlugins.ContainsKey(operation) && temporaryPlugins[operation].ContainsKey(stage))
{
var asyncExecutors = temporaryPlugins[operation][stage].Where(p => p.GetExecutionMode() == ExecutionMode.Asynchronous)
.OrderBy(p => p.GetExecutionOrder()).ToList().Select(p => p.ToPluginExecution(entity, preImage, postImage, pluginContext, core));
asyncExecutors.ToList().ForEach(x => pendingAsyncPlugins.Enqueue(x));
}
if (!disableRegisteredPlugins && registeredPlugins.TryGetValue(operation, out var operationPlugins) && operationPlugins.TryGetValue(stage, out var stagePlugins))
stagePlugins
.Where(p => p.GetExecutionMode() == ExecutionMode.Asynchronous)
.OrderBy(p => p.GetExecutionOrder())
.Select(p => p.ToPluginExecution(entity, preImage, postImage, pluginContext, core))
.ToList()
.ForEach(pendingAsyncPlugins.Enqueue);

if (temporaryPlugins.TryGetValue(operation, out var tempOperationPlugins) && tempOperationPlugins.TryGetValue(stage, out var tempStagePlugins))
tempStagePlugins
.Where(p => p.GetExecutionMode() == ExecutionMode.Asynchronous)
.OrderBy(p => p.GetExecutionOrder())
.Select(p => p.ToPluginExecution(entity, preImage, postImage, pluginContext, core))
.ToList()
.ForEach(pendingAsyncPlugins.Enqueue);
}

public void TriggerAsyncWaitingJobs()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="$(MSBuildThisFileDirectory)AccountBothImagePlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FaxPreOperationPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RetrievePlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SyncAsyncTest\TestPlugin0.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UpdateIdInCreatePlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ContactPostPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DirectPlugins\ContactIPluginDirectPostOp.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ protected void Sync1NameUpdate(LocalPluginContext localContext)

var accountUpd = new Account(account.Id)
{
Name = account.Name + "Sync1"
Name = account.Name + "pSync1"
};
service.Update(accountUpd);
}
Expand Down
38 changes: 38 additions & 0 deletions tests/SharedPluginsAndCodeactivites/SyncAsyncTest/TestPlugin0.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using DG.XrmFramework.BusinessDomain.ServiceContext;
using System;

namespace DG.Some.Namespace.Test
{
public class TestPlugin0 : TestPlugin
{
public TestPlugin0()
: base(typeof(TestPlugin0))
{
RegisterPluginStep<Account>(
EventOperation.Update,
ExecutionStage.PostOperation,
Sync0NameUpdate)
.AddImage(ImageType.PostImage, x => x.Name)
.AddFilteredAttributes(x => x.EMailAddress1)
.SetExecutionOrder(0);
}

protected void Sync0NameUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}

var service = localContext.OrganizationService;

var account = GetPostImage<Account>(localContext, "PostImage");

var accountUpd = new Account(account.Id)
{
Name = account.Name + "Sync0"
};
service.Update(accountUpd);
}
}
}
1 change: 1 addition & 0 deletions tests/SharedTests/SharedTests.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="$(MSBuildThisFileDirectory)TestClone.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestCWAAccountOptional.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestDefaultBusinessUnitTeamsMembers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestExecutionOrder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestPriorityAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestSendEmail.cs" />
<Compile Include="..\SharedTests\TestZipSnapshot.cs" />
Expand Down
112 changes: 112 additions & 0 deletions tests/SharedTests/TestExecutionOrder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using DG.Some.Namespace.Test;
using DG.XrmFramework.BusinessDomain.ServiceContext;
using System.IO;
using Xunit;

namespace DG.XrmMockupTest
{
public class TestExecutionOrder : UnitTestBase
{
public TestExecutionOrder(XrmMockupFixture fixture) : base(fixture) { crm.DisableRegisteredPlugins(true); }

[Fact]
public void TestSyncWorkflowThenSyncPlugin()
{
crm.RegisterAdditionalPlugins(Tools.XrmMockup.PluginRegistrationScope.Temporary, typeof(Test1Plugin1)); // Appends: pSync1
crm.AddWorkflow(Path.Combine("../../..", "Metadata", "Workflows", "TestSyncAsyncWorkflows", "Test2", "Test2WorkflowSync1.xml")); // Appends: Sync1

var account = new Account()
{
Name = "Test",
};
account.Id = orgAdminService.Create(account);

account.EMailAddress1 = "[email protected]";
orgAdminService.Update(account);

var retrievedAccount = Account.Retrieve(orgAdminService, account.Id, x => x.Name);

Assert.Equal(account.Name + "pSync1", retrievedAccount.Name);
}

[Fact]
public void TestAsyncPluginThenAsyncWorkflow()
{
crm.RegisterAdditionalPlugins(Tools.XrmMockup.PluginRegistrationScope.Temporary, typeof(Test2Plugin1)); // Appends: ASync1
crm.AddWorkflow(Path.Combine("../../..", "Metadata", "Workflows", "TestSyncAsyncWorkflows", "Test1", "Test1WorkflowASync2.xml")); // Appends: ASync2

var account = new Account()
{
Name = "Test",
};
account.Id = orgAdminService.Create(account);

account.EMailAddress1 = "[email protected]";
orgAdminService.Update(account);

var retrievedAccount = Account.Retrieve(orgAdminService, account.Id, x => x.Name);

Assert.Equal(account.Name + "ASync1ASync2", retrievedAccount.Name);
}

[Fact]
public void TestSyncWorkflowThenAsyncPlugin()
{
crm.RegisterAdditionalPlugins(Tools.XrmMockup.PluginRegistrationScope.Temporary, typeof(Test2Plugin1)); // Appends: ASync1
crm.AddWorkflow(Path.Combine("../../..", "Metadata", "Workflows", "TestSyncAsyncWorkflows", "Test2", "Test2WorkflowSync1.xml")); // Appends: Sync1

var account = new Account()
{
Name = "Test",
};
account.Id = orgAdminService.Create(account);

account.EMailAddress1 = "[email protected]";
orgAdminService.Update(account);

var retrievedAccount = Account.Retrieve(orgAdminService, account.Id, x => x.Name);

Assert.Equal(account.Name + "Sync1ASync1", retrievedAccount.Name);
}

[Fact]
public void TestSyncPluginThenAsyncWorkflow()
{
crm.RegisterAdditionalPlugins(Tools.XrmMockup.PluginRegistrationScope.Temporary, typeof(Test1Plugin1)); // Appends: pSync1
crm.AddWorkflow(Path.Combine("../../..", "Metadata", "Workflows", "TestSyncAsyncWorkflows", "Test1", "Test1WorkflowASync2.xml")); // Appends: ASync2

var account = new Account()
{
Name = "Test",
};
account.Id = orgAdminService.Create(account);

account.EMailAddress1 = "[email protected]";
orgAdminService.Update(account);

var retrievedAccount = Account.Retrieve(orgAdminService, account.Id, x => x.Name);

Assert.Equal(account.Name + "pSync1ASync2", retrievedAccount.Name);
}

[Fact]
public void TestSyncPluginThenSyncWorkflow()
{
crm.RegisterAdditionalPlugins(Tools.XrmMockup.PluginRegistrationScope.Temporary, typeof(TestPlugin0)); // Appends: Sync0
crm.AddWorkflow(Path.Combine("../../..", "Metadata", "Workflows", "TestSyncAsyncWorkflows", "Test2", "Test2WorkflowSync1.xml")); // Appends: Sync1

var account = new Account()
{
Name = "Test",
};
account.Id = orgAdminService.Create(account);

account.EMailAddress1 = "[email protected]";
orgAdminService.Update(account);

var retrievedAccount = Account.Retrieve(orgAdminService, account.Id, x => x.Name);

Assert.Equal(account.Name + "Sync1", retrievedAccount.Name);
}
}
}
Loading