ForEach refusing to accept Collection
See original GitHub issueI 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"
}
]
}
On the ForEach activity, I tried {{ Input.Payload.ApproversList }}
and { Input.Payload.ApproversList } but workflow crashes badly:
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:
- Created 3 years ago
- Comments:7 (3 by maintainers)
Top 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 >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Issue resolved
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
isnew 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 }}