使用 Elsa 工作流 ForEach 循环 Activity
Using a Elsa Workflow ForEach Loop Activity
我的工作流程是根据这样的信号触发的:
public async Task<IActionResult> StartApprovalProcess([FromBody] long requestId)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Get data object
var payload = await _mainService.GetBudgetReleaseRequestPayload(requestId);
var input = new Variables();
input.SetVariable("Payload", payload);
// Signal the workflow to start
await _workflowInvoker.TriggerSignalAsync("StartApprovalPhase", input);
return Ok("BRR registered");
}
这是我的有效载荷 class:
public class BudgetReleaseRequestApprovalPhasePayloadModel
{
public BudgetReleaseRequestApprovalPhasePayloadModel(BudgetReleaseRequestApprovalPhasePayloadDto model)
{
Id = model.Id;
Description = model.Description;
Amount = model.Amount;
RequesterId = model.RequesterId;
SubmissionDate = model.SubmissionDate;
CostCenterName = model.CostCenterName;
ExpenseTypeName = model.ExpenseTypeName;
RequestTypeName = model.RequestTypeName;
AccountCode = model.AccountCode;
AccountName = model.AccountName;
BpsReferenceNumber = model.BpsReferenceNumber;
ApproversList = new List<BudgetReleaseRequestApproverViewModel>();
foreach (var budgetReleaseRequestApprover in model.ApproversList)
{
ApproversList.Add(new BudgetReleaseRequestApproverViewModel(budgetReleaseRequestApprover));
}
}
public long Id { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public string RequesterId { get; set; }
public DateTime SubmissionDate { get; set; }
public string CostCenterName { get; set; }
public string ExpenseTypeName { get; set; }
public string RequestTypeName { get; set; }
public string AccountCode { get; set; }
public string AccountName { get; set; }
public string BpsReferenceNumber { get; set; }
public string AmountFormatted => $"{Amount:N2} AED";
public string DateFormatted => $"{SubmissionDate:dd-MMM-yyyy}";
public string CostCenterAndType => $"{CostCenterName}/{ExpenseTypeName}";
public string AccountDetail => $"{AccountCode} - {AccountName}";
public int ApproversCount => ApproversList.Count;
public IList<BudgetReleaseRequestApproverViewModel> ApproversList { get; set; }
}
这里是作为集合的class:
public class BudgetReleaseRequestApproverViewModel
{
public BudgetReleaseRequestApproverViewModel(BudgetReleaseRequestApprover model)
{
RequestId = model.RequestId;
RequestApproverId = model.RequestApproverId;
ApproverId = model.ApproverId;
RequesterId = model.RequesterId;
ApproverSequence = model.ApproverSequence;
ActionId = model.ActionId;
RequestActionId = model.RequestActionId;
}
public long RequestId { get; set; }
public byte RequestApproverId { get; set; }
public string ApproverId { get; set; }
public string RequesterId { get; set; }
public byte ApproverSequence { get; set; }
public Guid? ActionId { get; set; }
public byte? RequestActionId { get; set; }
}
我遵循了主要指南 (https://sipkeschoorstra.medium.com/building-workflow-driven-net-core-applications-with-elsa-139523aa4c50),并且知道我们需要实现一个处理程序,以便在这两个模型的工作流中使用 Liquid Expressions:
public class LiquidConfigurationHandler : INotificationHandler<EvaluatingLiquidExpression>
{
public Task Handle(EvaluatingLiquidExpression notification, CancellationToken cancellationToken)
{
var context = notification.TemplateContext;
context.MemberAccessStrategy.Register<BudgetReleaseRequestApprovalPhasePayloadModel>();
context.MemberAccessStrategy.Register<BudgetReleaseRequestApproverViewModel>();
return Task.CompletedTask;
}
}
这是我的测试工作流程:
{
"activities": [{
"id": "abc63216-76e7-42b2-ab7b-5cdb6bbc3ed9",
"type": "Signaled",
"left": 122,
"top": 365,
"state": {
"signal": {
"expression": "StartApprovalPhase",
"syntax": "Literal"
},
"name": "",
"title": "Signal: Start Approval Phase",
"description": "Trigger the workflow when this signal is received."
},
"blocking": false,
"executed": false,
"faulted": false
}, {
"id": "ac7669d6-b7e6-4139-825e-5f2b9c1dbdb8",
"type": "SendEmail",
"left": 553,
"top": 379,
"state": {
"from": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"to": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"subject": {
"expression": "Workflow Testing",
"syntax": "Literal"
},
"body": {
"expression": "<p>BRR #{{ Input.Payload.Id }}</p>\r\n<p>Name: {{ Input.Payload.Description }}</p>\r\n<p>Amount: {{ Input.Payload.AmountFormatted }}</p>\r\n<p>Date: {{ Input.Payload.DateFormatted }}</p>\r\n<br />\r\n<p>Approvers: {{ Input.Payload.ApproversCount }}</p>",
"syntax": "Liquid"
},
"name": "",
"title": "Email: Test",
"description": ""
},
"blocking": false,
"executed": false,
"faulted": false
}, {
"id": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"type": "ForEach",
"left": 867,
"top": 474,
"state": {
"collectionExpression": {
"expression": "{{ Input.Payload.ApproversList }}",
"syntax": "Liquid"
},
"iteratorName": "",
"name": "",
"title": "",
"description": ""
},
"blocking": false,
"executed": false,
"faulted": false
}, {
"id": "7966b931-f683-4b81-aad4-ad0f6c628191",
"type": "SendEmail",
"left": 1042,
"top": 675,
"state": {
"from": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"to": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"subject": {
"expression": "Looping #",
"syntax": "Literal"
},
"body": {
"expression": "Loop Details",
"syntax": "Literal"
},
"name": "",
"title": "",
"description": ""
},
"blocking": false,
"executed": false,
"faulted": false
}, {
"id": "5f246eda-271d-46ed-8efe-df0f26d542be",
"type": "SendEmail",
"left": 1163,
"top": 325,
"state": {
"name": "",
"from": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"to": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"subject": {
"expression": "Loop Over",
"syntax": "Literal"
},
"body": {
"expression": "Loop Finished",
"syntax": "Literal"
},
"title": "",
"description": ""
},
"blocking": false,
"executed": false,
"faulted": false
}
],
"connections": [{
"sourceActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"destinationActivityId": "5f246eda-271d-46ed-8efe-df0f26d542be",
"outcome": "Done"
}, {
"sourceActivityId": "abc63216-76e7-42b2-ab7b-5cdb6bbc3ed9",
"destinationActivityId": "ac7669d6-b7e6-4139-825e-5f2b9c1dbdb8",
"outcome": "Done"
}, {
"sourceActivityId": "ac7669d6-b7e6-4139-825e-5f2b9c1dbdb8",
"destinationActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"outcome": "Done"
}, {
"sourceActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"destinationActivityId": "7966b931-f683-4b81-aad4-ad0f6c628191",
"outcome": "Iterate"
}, {
"sourceActivityId": "7966b931-f683-4b81-aad4-ad0f6c628191",
"destinationActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"outcome": "Done"
}
]
}
视觉:
这是我的结果:
- 信号:有效
- 第一封电子邮件:有效:
ForEach 失败,我在调试中发现了这个:
失败:Elsa.Expressions.WorkflowExpressionEvaluator[0]
计算 JavaScript 表达式“{{ Input.Payload.ApproversList }}”时出错。消息:未定义输入
ReferenceError:未定义输入
失败:Elsa.Services.ActivityInvoker[0]
调用工作流 de8e12d4645e4480abccbbe562b48448 的 activity 2efcffa9-8e18-45cf-aac8-fcfdc8846df8 时出错
Elsa.Exceptions.WorkflowException:计算 JavaScript 表达式“{{ Input.Payload.ApproversList }}”时出错。消息:未定义输入
---> ReferenceError: 输入未定义
--- 内部异常堆栈跟踪结束 ---
在 Elsa.Expressions.WorkflowExpressionEvaluator.EvaluateAsync(IWorkflowExpression 表达式,Type 类型,WorkflowExecutionContext workflowExecutionContext,CancellationToken cancellationToken)
在 Elsa.Extensions.WorkflowExpressionEvaluatorExtensions.EvaluateAsync[T](IWorkflowExpressionEvaluator 求值器,IWorkflowExpression1 expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) at Elsa.Activities.ControlFlow.Activities.ForEach.OnExecuteAsync(WorkflowExecutionContext context, CancellationToken cancellationToken) at Elsa.Services.ActivityInvoker.InvokeAsync(WorkflowExecutionContext workflowContext, IActivity activity, Func
2 invokeAction)
失败:Elsa.Services.WorkflowInvoker[0]
DbUpdateException 从 Elsa.WorkflowEventHandlers.PersistenceWorkflowEventHandler 抛出的 IWorkflowEventHandler
Microsoft.EntityFrameworkCore.DbUpdateException: 更新条目时出错。有关详细信息,请参阅内部异常。
---> Newtonsoft.Json.JsonSerializationException:检测到类型为 'Jint.Engine' 的 属性 'Engine' 的自引用循环。路径 'Exception.InnerException.Error.Engine.Global'.
我需要迭代一个 BudgetReleaseRequestApproverViewModel
,发送电子邮件,等待操作,重复,但我无法弄清楚循环。
此答案基于我在 GitHub issue 上提供的评论,该评论重复了 OP 的问题。为了完整起见,我提供以下内容。
尝试对 ForEach activity 使用 input
函数(确保选择的语法是 JavaScript):
input('PayLoad').ApproverList
这将获得名为 "PayLoad"
的输入。
我不知道 Liquid 是否应该工作。从用户体验的角度来看,我们应该要么确保它可以,要么甚至不允许该选项。
我的工作流程是根据这样的信号触发的:
public async Task<IActionResult> StartApprovalProcess([FromBody] long requestId)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Get data object
var payload = await _mainService.GetBudgetReleaseRequestPayload(requestId);
var input = new Variables();
input.SetVariable("Payload", payload);
// Signal the workflow to start
await _workflowInvoker.TriggerSignalAsync("StartApprovalPhase", input);
return Ok("BRR registered");
}
这是我的有效载荷 class:
public class BudgetReleaseRequestApprovalPhasePayloadModel
{
public BudgetReleaseRequestApprovalPhasePayloadModel(BudgetReleaseRequestApprovalPhasePayloadDto model)
{
Id = model.Id;
Description = model.Description;
Amount = model.Amount;
RequesterId = model.RequesterId;
SubmissionDate = model.SubmissionDate;
CostCenterName = model.CostCenterName;
ExpenseTypeName = model.ExpenseTypeName;
RequestTypeName = model.RequestTypeName;
AccountCode = model.AccountCode;
AccountName = model.AccountName;
BpsReferenceNumber = model.BpsReferenceNumber;
ApproversList = new List<BudgetReleaseRequestApproverViewModel>();
foreach (var budgetReleaseRequestApprover in model.ApproversList)
{
ApproversList.Add(new BudgetReleaseRequestApproverViewModel(budgetReleaseRequestApprover));
}
}
public long Id { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public string RequesterId { get; set; }
public DateTime SubmissionDate { get; set; }
public string CostCenterName { get; set; }
public string ExpenseTypeName { get; set; }
public string RequestTypeName { get; set; }
public string AccountCode { get; set; }
public string AccountName { get; set; }
public string BpsReferenceNumber { get; set; }
public string AmountFormatted => $"{Amount:N2} AED";
public string DateFormatted => $"{SubmissionDate:dd-MMM-yyyy}";
public string CostCenterAndType => $"{CostCenterName}/{ExpenseTypeName}";
public string AccountDetail => $"{AccountCode} - {AccountName}";
public int ApproversCount => ApproversList.Count;
public IList<BudgetReleaseRequestApproverViewModel> ApproversList { get; set; }
}
这里是作为集合的class:
public class BudgetReleaseRequestApproverViewModel
{
public BudgetReleaseRequestApproverViewModel(BudgetReleaseRequestApprover model)
{
RequestId = model.RequestId;
RequestApproverId = model.RequestApproverId;
ApproverId = model.ApproverId;
RequesterId = model.RequesterId;
ApproverSequence = model.ApproverSequence;
ActionId = model.ActionId;
RequestActionId = model.RequestActionId;
}
public long RequestId { get; set; }
public byte RequestApproverId { get; set; }
public string ApproverId { get; set; }
public string RequesterId { get; set; }
public byte ApproverSequence { get; set; }
public Guid? ActionId { get; set; }
public byte? RequestActionId { get; set; }
}
我遵循了主要指南 (https://sipkeschoorstra.medium.com/building-workflow-driven-net-core-applications-with-elsa-139523aa4c50),并且知道我们需要实现一个处理程序,以便在这两个模型的工作流中使用 Liquid Expressions:
public class LiquidConfigurationHandler : INotificationHandler<EvaluatingLiquidExpression>
{
public Task Handle(EvaluatingLiquidExpression notification, CancellationToken cancellationToken)
{
var context = notification.TemplateContext;
context.MemberAccessStrategy.Register<BudgetReleaseRequestApprovalPhasePayloadModel>();
context.MemberAccessStrategy.Register<BudgetReleaseRequestApproverViewModel>();
return Task.CompletedTask;
}
}
这是我的测试工作流程:
{
"activities": [{
"id": "abc63216-76e7-42b2-ab7b-5cdb6bbc3ed9",
"type": "Signaled",
"left": 122,
"top": 365,
"state": {
"signal": {
"expression": "StartApprovalPhase",
"syntax": "Literal"
},
"name": "",
"title": "Signal: Start Approval Phase",
"description": "Trigger the workflow when this signal is received."
},
"blocking": false,
"executed": false,
"faulted": false
}, {
"id": "ac7669d6-b7e6-4139-825e-5f2b9c1dbdb8",
"type": "SendEmail",
"left": 553,
"top": 379,
"state": {
"from": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"to": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"subject": {
"expression": "Workflow Testing",
"syntax": "Literal"
},
"body": {
"expression": "<p>BRR #{{ Input.Payload.Id }}</p>\r\n<p>Name: {{ Input.Payload.Description }}</p>\r\n<p>Amount: {{ Input.Payload.AmountFormatted }}</p>\r\n<p>Date: {{ Input.Payload.DateFormatted }}</p>\r\n<br />\r\n<p>Approvers: {{ Input.Payload.ApproversCount }}</p>",
"syntax": "Liquid"
},
"name": "",
"title": "Email: Test",
"description": ""
},
"blocking": false,
"executed": false,
"faulted": false
}, {
"id": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"type": "ForEach",
"left": 867,
"top": 474,
"state": {
"collectionExpression": {
"expression": "{{ Input.Payload.ApproversList }}",
"syntax": "Liquid"
},
"iteratorName": "",
"name": "",
"title": "",
"description": ""
},
"blocking": false,
"executed": false,
"faulted": false
}, {
"id": "7966b931-f683-4b81-aad4-ad0f6c628191",
"type": "SendEmail",
"left": 1042,
"top": 675,
"state": {
"from": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"to": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"subject": {
"expression": "Looping #",
"syntax": "Literal"
},
"body": {
"expression": "Loop Details",
"syntax": "Literal"
},
"name": "",
"title": "",
"description": ""
},
"blocking": false,
"executed": false,
"faulted": false
}, {
"id": "5f246eda-271d-46ed-8efe-df0f26d542be",
"type": "SendEmail",
"left": 1163,
"top": 325,
"state": {
"name": "",
"from": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"to": {
"expression": "my.email@acme.co",
"syntax": "Literal"
},
"subject": {
"expression": "Loop Over",
"syntax": "Literal"
},
"body": {
"expression": "Loop Finished",
"syntax": "Literal"
},
"title": "",
"description": ""
},
"blocking": false,
"executed": false,
"faulted": false
}
],
"connections": [{
"sourceActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"destinationActivityId": "5f246eda-271d-46ed-8efe-df0f26d542be",
"outcome": "Done"
}, {
"sourceActivityId": "abc63216-76e7-42b2-ab7b-5cdb6bbc3ed9",
"destinationActivityId": "ac7669d6-b7e6-4139-825e-5f2b9c1dbdb8",
"outcome": "Done"
}, {
"sourceActivityId": "ac7669d6-b7e6-4139-825e-5f2b9c1dbdb8",
"destinationActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"outcome": "Done"
}, {
"sourceActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"destinationActivityId": "7966b931-f683-4b81-aad4-ad0f6c628191",
"outcome": "Iterate"
}, {
"sourceActivityId": "7966b931-f683-4b81-aad4-ad0f6c628191",
"destinationActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
"outcome": "Done"
}
]
}
视觉:
这是我的结果:
- 信号:有效
- 第一封电子邮件:有效:
ForEach 失败,我在调试中发现了这个:
失败:Elsa.Expressions.WorkflowExpressionEvaluator[0] 计算 JavaScript 表达式“{{ Input.Payload.ApproversList }}”时出错。消息:未定义输入 ReferenceError:未定义输入 失败:Elsa.Services.ActivityInvoker[0] 调用工作流 de8e12d4645e4480abccbbe562b48448 的 activity 2efcffa9-8e18-45cf-aac8-fcfdc8846df8 时出错 Elsa.Exceptions.WorkflowException:计算 JavaScript 表达式“{{ Input.Payload.ApproversList }}”时出错。消息:未定义输入 ---> ReferenceError: 输入未定义 --- 内部异常堆栈跟踪结束 --- 在 Elsa.Expressions.WorkflowExpressionEvaluator.EvaluateAsync(IWorkflowExpression 表达式,Type 类型,WorkflowExecutionContext workflowExecutionContext,CancellationToken cancellationToken) 在 Elsa.Extensions.WorkflowExpressionEvaluatorExtensions.EvaluateAsync[T](IWorkflowExpressionEvaluator 求值器,IWorkflowExpression
1 expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) at Elsa.Activities.ControlFlow.Activities.ForEach.OnExecuteAsync(WorkflowExecutionContext context, CancellationToken cancellationToken) at Elsa.Services.ActivityInvoker.InvokeAsync(WorkflowExecutionContext workflowContext, IActivity activity, Func
2 invokeAction)失败:Elsa.Services.WorkflowInvoker[0] DbUpdateException 从 Elsa.WorkflowEventHandlers.PersistenceWorkflowEventHandler 抛出的 IWorkflowEventHandler Microsoft.EntityFrameworkCore.DbUpdateException: 更新条目时出错。有关详细信息,请参阅内部异常。 ---> Newtonsoft.Json.JsonSerializationException:检测到类型为 'Jint.Engine' 的 属性 'Engine' 的自引用循环。路径 'Exception.InnerException.Error.Engine.Global'.
我需要迭代一个 BudgetReleaseRequestApproverViewModel
,发送电子邮件,等待操作,重复,但我无法弄清楚循环。
此答案基于我在 GitHub issue 上提供的评论,该评论重复了 OP 的问题。为了完整起见,我提供以下内容。
尝试对 ForEach activity 使用 input
函数(确保选择的语法是 JavaScript):
input('PayLoad').ApproverList
这将获得名为 "PayLoad"
的输入。
我不知道 Liquid 是否应该工作。从用户体验的角度来看,我们应该要么确保它可以,要么甚至不允许该选项。