首页 > 解决方案 > 使用 Elsa 工作流 ForEach 循环活动

问题描述

我的工作流程是在这样的信号上触发的:

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");
}

这是我的有效负载类:

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; }
}

这是充当集合的类:

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)并且知道我们需要实现一个处理程序才能在其中包含液体表达式这两种模型的工作流程:

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

视觉的: Elsa-工作流程

这是我的结果:

电子邮件样本

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

标签: elsa-workflows

解决方案


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

尝试使用ForEachinput活动的函数(确保所选语法是 JavaScript):

input('PayLoad').ApproverList

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

我不知道Liquid是否应该工作。从用户体验的角度来看,我们应该确保它这样做,或者如果没有,甚至不允许该选项。


推荐阅读