使用 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"
        }
    ]
}

视觉:

这是我的结果:

我需要迭代一个 BudgetReleaseRequestApproverViewModel,发送电子邮件,等待操作,重复,但我无法弄清楚循环。

此答案基于我在 GitHub issue 上提供的评论,该评论重复了 OP 的问题。为了完整起见,我提供以下内容。

尝试对 ForEach activity 使用 input 函数(确保选择的语法是 JavaScript):

input('PayLoad').ApproverList

这将获得名为 "PayLoad" 的输入。

我不知道 Liquid 是否应该工作。从用户体验的角度来看,我们应该要么确保它可以,要么甚至不允许该选项。