elsa-workflows / elsa-workflows/elsa-core

[BUG] `Variable` and `Input` Do Not Work for Custom `IWorkflowProvider` Implementation

Open
#5,898 3 comments 0 reactions 1 assignee Claimed by @sfmskywalker View on GitHub
bug triaged
Dominant language
C#
Stars
7.9k
Forks
1.5k
Avg merge
15h 22m
Merged PRs (30d)
114

Description

## Description
When implementing a custom `IWorkflowProvider`, the JSON representation is serialized and persisted successfully, but upon execution of the workflow, it is not possible to access any workflow Input or to set any Variable value using the known conventions.

For an activity like `WriteLine` which accesses some Input or a Variable, then there will be no error, but the console line output will be blank. If the activity requires the input property then there will be an error that the same is required such as...

```
warn: Elsa.Workflows.Middleware.Activities.ExceptionHandlingMiddleware[0]
An exception was caught from a downstream middleware component
System.Exception: InCmxContext is required.
at Elsa.Extensions.InputExtensions.Get[T](Input`1 input, ActivityExecutionContext context, String inputName)
at Services.Workflow.Workflow.TestActivity.ExecuteAsync(ActivityExecutionContext context)
```

## Steps to Reproduce
Following is an example of an `IWorkflowProvider` implementation that doesn't do anything useful beyond returning a static `MaterializedWorkflow`. Once registered with `services.AddWorkflowDefinitionProvider();` then it will be called on startup of the Elsa.Web project and will successfully serialize and persist the workflow to the `WorkflowDefinitions` table in the Elsa database.

``` c#
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Activities.Flowchart.Activities;
using Elsa.Workflows.Activities.Flowchart.Models;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Management.Materializers;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Models;

namespace Services.Workflow.Workflow;

public class CustomWorkflowProvider : IWorkflowProvider
{
public const string CustomVarName = "customVar";
public const string CustomWorkflowDefId = "CustomWorklowDefinitionId";
public const string CustomWorkflowId = "abcdefg1234567890";

public string Name { get; } = "CmxDesignerJsonMaterializer";

public async ValueTask> GetWorkflowsAsync(
CancellationToken cancellationToken = new CancellationToken())
{
return await Task.FromResult(new List { MaterializeWorkflow() });
}

private MaterializedWorkflow MaterializeWorkflow()
{
var identity = new WorkflowIdentity(CustomWorkflowDefId, 1, CustomWorkflowId);

var workflowMetadata = new WorkflowMetadata("CustomWorkflow", "Custom WF Description", DateTimeOffset.UtcNow);

var varCustomVariable = new Variable(CustomVarName, default)
{
Id = "Workflow1:variable-1", StorageDriverType = typeof(Elsa.Workflows.Services.WorkflowStorageDriver)
};

var elsaWf = new Elsa.Workflows.Activities.Workflow
{
Id = CustomWorkflowId,
Name = CustomWorkflowDefId,
Variables = [varCustomVariable],
Identity = identity,
WorkflowMetadata = workflowMetadata,
Root = MapDesignerToElsaWorkflow(varCustomVariable)
};

return new MaterializedWorkflow(elsaWf, Name, JsonWorkflowMaterializer.MaterializerName);
}

private IActivity MapDesignerToElsaWorkflow(Variable varCustomVar)
{
var setVarAct =
new SetVariable(varCustomVar, context => context.GetInput(CustomVarName) ?? "FAIL");
var writeLine = new WriteLine(ctx => $"CustomVariable ==> {varCustomVar.Get(ctx)}");
var root = new Flowchart
{
Activities = [setVarAct, writeLine],
Connections = [new Connection(setVarAct, writeLine)]
};

return root;
}
}
```

In the above example, the problem arises when you try to access `context.GetInput(CustomVarName)` inside the `SetVariable` activity (see `MapDesignerToElsaWorkflow()` method). Similarly, the output of the subsequent `WriteLine` activity is a blank line in the console output.

The serialized workflow json looks good (to me) and if compared to the same workflow but defined as regular _programmatic_ workflow, using the `Build()` overload, and loaded via the default `ClrWorkflowProvider`, it is **virtually identical**. For that reason, I have included links to two diff reports for exactly this comparison.

Following diff report is for the workflow json as it's saved in the database...
[workflow json diff](https://coffee-johna-7.tiiny.site/)

...and this is for the `data` column on the same workflow...
[workflow data diff](https://rose-malissa-88.tiiny.site/)

We're attempting to use Elsa as a workflow orchestrator, but we have our own UX experience for building complex end client defined workflows, and was suggested by @sfmskywalker that using `IWorkflowProvider` would be a good way to translate our own bespoke JSON workflow definition into something that Elsa can execute. However, I'm a bit worried that the state management doesn't seem to be working, given how foundational that is for workflow execution.

If there's something I'm missing, please advise. I've spent many hours pouring through the source, but there's so many layers of abstraction that I feel like it's going to take quite a while before I can find the issue myself, as a relative noob to Elsa.

## Expected Behavior
Be able to resolve workflow input and variables within individual activities

## Actual Behavior
All references to input and variables are null within an individual activity

## Environment
- Elsa v3.2.0-rc4
- MacOS Sonoma

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.