Skip to content
17 changes: 16 additions & 1 deletion src/WorkflowCore.DSL/Services/DefinitionLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ private void AttachInputs(StepSourceV1 source, Type dataType, Type stepType, Wor
var dataParameter = Expression.Parameter(dataType, "data");
var contextParameter = Expression.Parameter(typeof(IStepExecutionContext), "context");
var environmentVarsParameter = Expression.Parameter(typeof(IDictionary), "environment");
var stepProperty = stepType.GetProperty(input.Key);
var stepProperty = stepType.GetProperty(input.Key, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

if (stepProperty == null)
{
Expand All @@ -243,6 +243,21 @@ private void AttachInputs(StepSourceV1 source, Type dataType, Type stepType, Wor
continue;
}

// Handle primitive values (bool, int, etc.) directly
if (input.Value != null && (input.Value.GetType().IsPrimitive || input.Value is bool || input.Value is int || input.Value is long || input.Value is double || input.Value is decimal))
{
var primitiveValue = input.Value;
void primitiveAction(IStepBody pStep, object pData)
{
if (stepProperty.PropertyType.IsAssignableFrom(primitiveValue.GetType()))
stepProperty.SetValue(pStep, primitiveValue);
else
stepProperty.SetValue(pStep, System.Convert.ChangeType(primitiveValue, stepProperty.PropertyType));
}
step.Inputs.Add(new ActionParameter<IStepBody, object>(primitiveAction));
continue;
}

throw new ArgumentException($"Unknown type for input {input.Key} on {source.Id}");
}
}
Expand Down
2 changes: 2 additions & 0 deletions test/WorkflowCore.TestAssets/DataTypes/CounterBoard.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace WorkflowCore.TestAssets.DataTypes
{
Expand All @@ -17,5 +18,6 @@ public class CounterBoard
public bool Flag1 { get; set; }
public bool Flag2 { get; set; }
public bool Flag3 { get; set; }
public List<string> DataList { get; set; } = new List<string> { "item1", "item2", "item3" };
}
}
12 changes: 12 additions & 0 deletions test/WorkflowCore.TestAssets/Steps/IterateListStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using WorkflowCore.Interface;
using WorkflowCore.Models;
using WorkflowCore.Primitives;

namespace WorkflowCore.TestAssets.Steps
{
public class IterateListStep : Foreach
{
// This class inherits from Foreach and should be able to use
// the RunParallel property from the base class in YAML definitions
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using FakeItEasy;
using WorkflowCore.Interface;
using WorkflowCore.Services.DefinitionStorage;
using Xunit;

namespace WorkflowCore.UnitTests.Services.DefinitionStorage
{
/// <summary>
/// Integration test to verify the fix for inherited property binding in YAML definitions.
/// This test specifically reproduces the issue mentioned in GitHub issue #1375.
/// </summary>
public class YamlInheritedPropertyIntegrationTest
{
[Fact(DisplayName = "Should bind inherited properties like RunParallel from base Foreach class")]
public void ShouldBindInheritedPropertiesInYamlDefinition()
{
// Arrange
var registry = A.Fake<IWorkflowRegistry>();
var loader = new DefinitionLoader(registry, new TypeResolver());

// This YAML definition uses a custom step (IterateListStep) that inherits from Foreach
// and tries to set the RunParallel property which is defined in the base Foreach class
var yamlWithInheritedProperty = @"
Id: TestInheritedPropertyWorkflow
Version: 1
Description: Test workflow for inherited property binding
DataType: WorkflowCore.TestAssets.DataTypes.CounterBoard, WorkflowCore.TestAssets
Steps:
- Id: IterateList
StepType: WorkflowCore.TestAssets.Steps.IterateListStep, WorkflowCore.TestAssets
Inputs:
Collection: ""data.DataList""
RunParallel: false
";

// Act & Assert
// Before the fix, this would throw: "Unknown property for input RunParallel on IterateList"
// After the fix, this should succeed
var exception = Record.Exception(() =>
loader.LoadDefinition(yamlWithInheritedProperty, Deserializers.Yaml));

// Verify no exception was thrown
Assert.Null(exception);
}

[Fact(DisplayName = "Should still throw exception for truly unknown properties")]
public void ShouldStillThrowForUnknownProperties()
{
// Arrange
var registry = A.Fake<IWorkflowRegistry>();
var loader = new DefinitionLoader(registry, new TypeResolver());

var yamlWithUnknownProperty = @"
Id: TestInheritedPropertyWorkflow
Version: 1
Description: Test workflow for inherited property binding
DataType: WorkflowCore.TestAssets.DataTypes.CounterBoard, WorkflowCore.TestAssets
Steps:
- Id: IterateList
StepType: WorkflowCore.TestAssets.Steps.IterateListStep, WorkflowCore.TestAssets
Inputs:
Collection: ""data.DataList""
NonExistentProperty: false
";

// Act & Assert
// This should still throw an exception for truly unknown properties
var exception = Assert.Throws<ArgumentException>(() =>
loader.LoadDefinition(yamlWithUnknownProperty, Deserializers.Yaml));

Assert.Contains("Unknown property for input NonExistentProperty", exception.Message);
}
}
}
Loading