In the execution context of a Workflow, activities from different business units need to be executed. This solution simplifies and unifies it by using reflection and object oriented-programming.
Introduction
When handling business logic of our applications through workflows, logic from different business units and services is executed, which is also susceptible to continuous extensions and modifications. To facilitate the development, growth and maintenance of a workflow service, it is convenient to have a centralized system that acts as a proxy and allows calling any activity without the need that each business unit added in the development life cycle involves coupling, recoding, and recompilation.
The following solution solves this problem through the dynamic loading of DLLs via reflection, while unifying the implementation, running, state and error handling in the execution cycle of activities.
Background
In order to apply these techniques, a programmer should:
- know workflow basic concepts: Activities, execution flow, parameters and Instances
- be familiarized with the basic concepts of object oriented-programming: Interfaces, and inheritance
- know basics about reflection in C#: Activator, Invoke, methods and attributes
Summary of the Code and Solution Structure
The structure of the solution is separated in three C# projects with the following files:
Project Activites.Runtime
IActivity
Interface that has to implement all activity classes
ActivityBase
Parent class from which all activity classes have to inherit
Reflector
Class with all needed reflection methods to find, load and execute activities at runtime
RuntimeService
Workflow activities service class with a method to run any activity
Project ActivityExample
GetCustomerActivity
Activity class to implement an example for an activity
Project Console.Test
Program
An execution example in console to dynamically load and run the GetCustomer
Activity
Using the Code
Full code explained
Project Activites.Runtime
IActivity.cs
public enum EState { NO_STARTED, STARTED, FINISHED, ERROR, NOT_FOUND }
public interface IActivity
{
EState State { get; set; }
List<KeyValuePair<string, object>> Response { get; set; }
}
ActivityBase.cs
public class ActivityBase : IActivity
{
public Guid WorkflowId { get; set; }
public EState State { get; set; }
public List<KeyValuePair<string, object>> Response { get; set; }
public List<KeyValuePair<string, object>> InputParameters { get; set; }
public string ErrorDetail { get; set; }
public ActivityBase() { }
public ActivityBase (Guid workflowId,
List<KeyValuePair<string, object>> inputParameters,
EState state = EState.STARTED)
{
WorkflowId = workflowId;
InputParameters = inputParameters;
State = state;
}
public IActivity RunActivity(Guid workflowId,
List<KeyValuePair<string, object>> parameters)
{
var result = new ActivityBase(workflowId, parameters);
this.WorkflowId =workflowId;
this.InputParameters = parameters;
try
{
result.Response = RunActivityImplementation();
result.State = EState.FINISHED;
}
catch (Exception ex)
{
result.ErrorDetail = ex.Message;
result.State = EState.ERROR;
}
return result;
}
public virtual List<KeyValuePair<string, object>> RunActivityImplementation()
{
throw new NotImplementedException();
}
}
Reflector.cs
public class WorkflowActivitesAttribute : Attribute
{
public string ActivityMethodId;
}
public class LoadAssembly
{
public string Key { get; set; }
public DateTime LastModification { get; set; }
public System.Reflection.Assembly Assembly { get; set; }
}
public class Reflector
{
private static string Path =
System.Configuration.ConfigurationManager.AppSettings
["WorkflowDLLsClientsFolderPath"];
private static object _LockAssembliesList = new object();
private static List<LoadAssembly> LoadAssemblies = new List<LoadAssembly>();
public static List<Type> GetClassesFromInterface(Type type)
{
var types = GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p) &&
p.IsClass)
.ToList();
return types;
}
public static string GetWorkflowActivityMethodName
(Type type, string activityMethodId)
{
string result = null;
System.Reflection.MethodInfo[] methods = type.GetMethods();
foreach (System.Reflection.MethodInfo m in methods)
{
object[] attrs = m.GetCustomAttributes(false);
foreach (object attr in attrs)
{
var a = attr as WorkflowActivitesAttribute;
if (a != null && a.ActivityMethodId == activityMethodId)
{
return m.Name;
}
}
}
return result;
}
private static System.Reflection.Assembly[] GetAssembliesFromPath()
{
lock (_LockAssembliesList)
{
var resultList = new List<System.Reflection.Assembly>();
System.Reflection.Assembly assembly;
foreach (string dll in System.IO.Directory.GetFiles(Path, "*.dll"))
{
DateTime modification = System.IO.File.GetLastWriteTime(dll);
var loadedAssembly = LoadAssemblies.FirstOrDefault(a => a.Key == dll);
if (loadedAssembly != null &&
loadedAssembly.LastModification < modification)
{
LoadAssemblies.RemoveAll(a => a.Key == dll);
loadedAssembly = null;
}
assembly = loadedAssembly?.Assembly;
if (assembly == null)
{
assembly = System.Reflection.Assembly.LoadFile(dll);
LoadAssemblies
.Add(new LoadAssembly
{
Key = dll,
LastModification = modification,
Assembly = assembly
});
}
resultList.Add(assembly);
}
var result = resultList.ToArray();
return result;
}
}
}
RuntimeService.cs
public class RuntimeService
{
public ActivityBase RunActivity(Guid processId,
string activityMethodId,
List<KeyValuePair<string, object>> inputParameters)
{
var types = Reflector.GetClassesFromInterface(typeof(IActivity));
foreach (Type t in types)
{
var obj = Activator.CreateInstance(t) as IActivity;
string methodName =
Reflector.GetWorkflowActivityMethodName(t, activityMethodId);
if (methodName != null)
{
System.Reflection.MethodInfo methodInfo = t.GetMethod("RunActivity");
var parameters = new object[] { processId, inputParameters };
try
{
var result = (ActivityBase)methodInfo.Invoke(obj, parameters);
return result;
}
catch (Exception ex)
{
return new ActivityBase(processId, inputParameters, EState.ERROR)
{
ErrorDetail = ex.Message
};
}
}
}
return new ActivityBase { State = EState.NOT_FOUND };
}
}
Project ActivityExample.csproj
GetCustomerActivity.cs
using Activities.Runtime;
public class GetCustomerActivity : ActivityBase, IActivity
{
[WorkflowActivites(ActivityMethodId = "GetCustomer")]
public override List<KeyValuePair<string, object>> RunActivityImplementation()
{
var result = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("customers",
new { Lists = new List<dynamic>{ new { id = 1 } } })
};
return result;
}
}
Project Console.Test
Program.cs
using Activities.Runtime;
class Program
{
static void Main(string[] args)
{
var workflowGuid = System.Guid.NewGuid();
var inputParameters = new List<KeyValuePair<string, object>>()
{
new KeyValuePair<string, object>("Customer",1)
};
ActivityBase result = new RuntimeService().RunActivity
(workflowGuid, "GetCustomer", inputParameters);
System.Console.WriteLine("Success...");
System.Console.ReadLine();
}
}
App.Config
<appSettings>
<add key="WorkflowDLLsClientsFolderPath" value="[DLL_FOLDER_PATH]" />
</appSettings>
History
- 14th July, 2022: Initial version