question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ForEach refusing to accept Collection

See original GitHub issue

I have my workflow triggered on a signal like so:

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

Here is my Payload 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; }
}

And here is the class that acts as a collection:

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

}

I followed the main guide (https://sipkeschoorstra.medium.com/building-workflow-driven-net-core-applications-with-elsa-139523aa4c50) and know that we need to implement a handler in order to have Liquid Expressions within workflow for both these models:

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

Here’s my test workflow:

{
    "activities": [{
            "id": "abc63216-76e7-42b2-ab7b-5cdb6bbc3ed9",
            "type": "Signaled",
            "left": 42,
            "top": 0,
            "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": 33,
            "top": 272,
            "state": {
                "from": {
                    "expression": "DA-BRR@DubaiAirports.ae",
                    "syntax": "Literal"
                },
                "to": {
                    "expression": "Hassan.Gulzar@DubaiAirports.ae",
                    "syntax": "Literal"
                },
                "subject": {
                    "expression": "DA BRR#{{ Input.Payload.Id }}: {{ Input.Payload.Description }}",
                    "syntax": "Liquid"
                },
                "body": {
                    "expression": "<p>BRR #{{ Input.Payload.Id }}: {{ 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{{ Input.Payload.Justification | raw }}\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": "CurrentApprover",
                "name": "",
                "title": "",
                "description": ""
            },
            "blocking": false,
            "executed": false,
            "faulted": false
        }, {
            "id": "7966b931-f683-4b81-aad4-ad0f6c628191",
            "type": "SendEmail",
            "left": 1042,
            "top": 675,
            "state": {
                "from": {
                    "expression": "DA-BRR@DubaiAirports.ae",
                    "syntax": "Literal"
                },
                "to": {
                    "expression": "Hassan.Gulzar@DubaiAirports.ae",
                    "syntax": "Literal"
                },
                "subject": {
                    "expression": "DA-BRR: 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": "DA-BRR@DubaiAirports.ae",
                    "syntax": "Literal"
                },
                "to": {
                    "expression": "Hassan.Gulzar@DubaiAirports.ae",
                    "syntax": "Literal"
                },
                "subject": {
                    "expression": "DA-BRR: Loop Over",
                    "syntax": "Literal"
                },
                "body": {
                    "expression": "Loop Finished",
                    "syntax": "Literal"
                },
                "title": "",
                "description": ""
            },
            "blocking": false,
            "executed": false,
            "faulted": false
        }
    ],
    "connections": [{
            "sourceActivityId": "abc63216-76e7-42b2-ab7b-5cdb6bbc3ed9",
            "destinationActivityId": "ac7669d6-b7e6-4139-825e-5f2b9c1dbdb8",
            "outcome": "Done"
        }, {
            "sourceActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
            "destinationActivityId": "5f246eda-271d-46ed-8efe-df0f26d542be",
            "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"
        }, {
            "sourceActivityId": "ac7669d6-b7e6-4139-825e-5f2b9c1dbdb8",
            "destinationActivityId": "2efcffa9-8e18-45cf-aac8-fcfdc8846df8",
            "outcome": "Done"
        }
    ]
}

image

On the ForEach activity, I tried {{ Input.Payload.ApproversList }} and { Input.Payload.ApproversList } but workflow crashes badly:

image

ForEach Fails and I catch this in debug:

fail: Elsa.Expressions.WorkflowExpressionEvaluator[0] Error while evaluating JavaScript expression "{{ Input.Payload.ApproversList }}". Message: Input is not defined ReferenceError: Input is not defined fail: Elsa.Services.ActivityInvoker[0] Error while invoking activity 2efcffa9-8e18-45cf-aac8-fcfdc8846df8 of workflow de8e12d4645e4480abccbbe562b48448 Elsa.Exceptions.WorkflowException: Error while evaluating JavaScript expression "{{ Input.Payload.ApproversList }}". Message: Input is not defined ---> ReferenceError: Input is not defined --- End of inner exception stack trace --- at Elsa.Expressions.WorkflowExpressionEvaluator.EvaluateAsync(IWorkflowExpression expression, Type type, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) at Elsa.Extensions.WorkflowExpressionEvaluatorExtensions.EvaluateAsync[T](IWorkflowExpressionEvaluator evaluator, 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, Func2 invokeAction)

fail: Elsa.Services.WorkflowInvoker[0] IWorkflowEventHandler thrown from Elsa.WorkflowEventHandlers.PersistenceWorkflowEventHandler by DbUpdateException Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Engine' with type 'Jint.Engine'. Path 'Exception.InnerException.Error.Engine.Global'.

I need to iterate a IList<BudgetReleaseRequestApproverViewModel>, send email on each, wait action, repeat but I cannot figure out the Loop.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
DoomerDGR8commented, Mar 3, 2021

Issue resolved

1reaction
sfmskywalkercommented, Mar 3, 2021

I think I know why Elsa might be trying to use JavaScript in the ForEach activity itself. There’s a serialization bug. The default value for its CollectionExpression is new JavaScriptExpression<IList>("[]"). But I’ve seen cases with other activities where it won’t change to e.g. Liquid. This issue has been resolved with Elsa 2.

Although I understand you need to use Liquid for email templates, I’m not sure I see why you would absolutely need liquid for the CollectionExpression. Please do let me know, I might be missing something important here.

The ForEach activity automatically sets a variable on each iteration using the name specified in the IteraterName property. This allows you to access the current value by accessing the variable named "CurrentApprover". E.g. in liquid form:

{{ Variables.CurrentApprover.RequestId }}

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why can't you modify a collection in a for each loop
The Best answer would be that List has some kind of Tracking over its list items and can update its items as you...
Read more >
Why does a foreach loop refuse inline code results but ...
My foreach loop will accept Results from inline and Body from list files on server but when I add a step, whatever it...
Read more >
c# - Collection that can be modified in a foreach
You can't modify a collection in a foreach loop for good reason. However, it is okay to modify a collection using a for...
Read more >
a!forEach() Function - Appian 22.3
When items is null or an empty list, a!forEach() returns an empty list of the same type as items. It does not evaluate...
Read more >
Help! My foreach formula refuses to echo/print.
I'm lost. Spent the last hour trying to write a foreach loop for exercise 7, but my echo won't print. My formula is...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found