Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected behavior when implementing custom WorkflowCore.DSL.DefinitionLoader #1092

Open
jstimbertlhg opened this issue Sep 8, 2022 · 1 comment

Comments

@jstimbertlhg
Copy link

jstimbertlhg commented Sep 8, 2022

Describe the bug
We are attempting to load assemblies of steps from a location other than appbin and execute them using WorkflowCore.DSL via json. to accomplish this we implemented a custom DefinitionLoader.

We started with the off the shelf DefinitionLoader and made the following modifications:

  • Changed the "FindType" method on line 352 to the following:
private Type FindType(string name)
        {
            //Name passed in needs work
            var nameArray = name.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
            var nameToFind = nameArray[0].Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries)[1];

            WorkflowCorePluginManager pluginManager = new WorkflowCorePluginManager();
            return pluginManager.FindTypeByName("Steps", nameToFind); 
        }
  • FindTypeByName method
Assembly pluginAssembly = LoadPlugin(pluginAssemblyName);
            if (pluginAssembly != null)
            {
                //TypeInfo? typeInfo = pluginAssembly.DefinedTypes.FirstOrDefault(t => t.ImplementedInterfaces.Contains(typeof(IStepBody))
                //                                                                && t.Name.Equals(name));
                TypeInfo? typeInfo = pluginAssembly.DefinedTypes.FirstOrDefault(t => t.Name.Equals(name));
                return typeInfo?.AsType();
            }
  • LoadPlugin method
private static Assembly LoadPlugin(string pluginAssemblyName)
        {
            // Navigate up to the solution root
            string root = Path.Combine(Directory.GetCurrentDirectory(), "workflowcoreplugins");

            string pluginLocation = Path.GetFullPath(Path.Combine(root, $"{pluginAssemblyName}.dll"));
            Console.WriteLine($"Loading steps from assembly: {pluginLocation}");
            PluginLoadContext loadContext = new PluginLoadContext(pluginLocation);
            return loadContext.LoadFromAssemblyPath(pluginLocation);
        }
  • This is our step implmentation
using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace Steps
{
    public class TestStep : StepBody, IStepBody
    {
        public override ExecutionResult Run(IStepExecutionContext context)
        {
            Console.WriteLine("******************** This should work *************************");
            return ExecutionResult.Next();
        }
    }
}
  • This is the json we are passing to the loader
{
  "Id": "NSRLSteps",
  "Version": 1,
  "Steps": [
    {
      "Id": "TestStep",
      "StepType": "NSRLSteps.TestStep, NSRLSteps"
    }
  ]
}

To Reproduce

  1. Create a class library with a single step like below
using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace NSRLSteps
{
    public class TestStep : StepBody, IStepBody
    {
        public override ExecutionResult Run(IStepExecutionContext context)
        {
            Console.WriteLine("******************** This should work *************************");
            return ExecutionResult.Next();
        }
    }
}
  1. Create a console app that references version 3.7.0 WorkflowCore and WorkflowCore.DSL with a hosted service like below
using Elasticsearch.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
using WorkflowCore.Interface;
using WorkflowCore.Services.DefinitionStorage;

namespace Test
{
    public class TestService : BackgroundService
    {
        private readonly IServiceProvider _serviceProvider;

        public TestService(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
        
        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await Task.Run(() =>
            {
                var loader = _serviceProvider.GetRequiredService<IDefinitionLoader>();
                loader.LoadDefinition(@"{
  ""Id"": ""NSRLSteps"",
  ""Version"": 1,
  ""Steps"": [
    {
      ""Id"": ""TestStep"",
      ""StepType"": "Steps.TestStep, Steps""
    }
  ]
}
", Deserializers.Json);
            });
        }
    }
}
  • The issue occurs on line 72 of the DefinitionLoader if (stepType.GetInterfaces().Contains(typeof(IStepBody)))
    • Our test step is not recognized as having implemented the IStepBody interface even though we have inherited from StepBody as instructed.
    • We did find that updating the if to if (stepType.GetInterfaces().Any(t => t.AssemblyQualifiedName == typeof(IStepBody).AssemblyQualifiedName)) does find our step. However we get the following exception on line 72:
System.ArgumentException
  HResult=0x80070057
  Message=GenericArguments[0], 'Steps.TestStep', on 'WorkflowCore.Models.WorkflowStep`1[TStepBody]' violates the constraint of type 'TStepBody'.
  Source=System.Private.CoreLib
  StackTrace:
   at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
   at System.RuntimeType.MakeGenericType(Type[] instantiation)
   at DefinitionLoader.ConvertSteps(ICollection`1 source, Type dataType) in DefinitionLoader.cs:line 72
   at DefinitionLoader.Convert(DefinitionSourceV1 source) in DefinitionLoader.cs:line 38
   at DefinitionLoader.LoadDefinition(String source, Func`2 deserializer) in CDefinitionLoader.cs:line 27
   at TestService.<ExecuteAsync>b__2_0() in TestService.cs:line 29
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__272_0(Object obj)
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
TypeLoadException: GenericArguments[0], 'Steps.TestStep', on 'WorkflowCore.Models.WorkflowStep`1[TStepBody]' violates the constraint of type parameter 'TStepBody'.

Expected behavior

TestStep should be successfully loaded.

Additional context

We would like to know if we are on the right track or if this is not a recommended course of action.

Thank you,
John

@rapmue
Copy link
Contributor

rapmue commented May 4, 2023

We had more or less the same problems. We solved it by adding a custom typeresolver like i proposed in #1166.

Then there is also some kind of assembly resolving etc. involved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants