在C#语言中动态加载和执行工作流活动

目录

介绍

背景

代码和解决方案结构摘要

Project Activites.Runtime

IActivity

ActivityBase

Reflector

RuntimeService

Project ActivityExample

GetCustomerActivity

Project Console.Test

Program

使用代码

Project Activites.Runtime

IActivity.cs

ActivityBase.cs

Reflector.cs

RuntimeService.cs

Project ActivityExample.csproj

GetCustomerActivity.cs

Project Console.Test

Program.cs

App.Config


介绍

在通过工作流处理我们应用程序的业务逻辑时,会执行来自不同业务部门和服务的逻辑,这也容易受到持续扩展和修改的影响。为了促进工作流服务的开发、增长和维护,拥有一个集中式系统作为代理并允许调用任何活动非常方便,而无需在开发生命周期中添加的每个业务单元都涉及耦合、重新编码和重新编译。

以下解决方案通过反射动态加载DLL解决了这个问题,同时统一了活动执行周期中的实现、运行、状态和错误处理。

背景

为了应用这些技术,程序员应该:

  1. 了解工作流基本概念:活动、执行流程、参数和实例
  2. 熟悉面向对象编程的基本概念:接口和继承
  3. 了解有关C#中反射的基础知识:激活器、调用、方法和属性

代码和解决方案结构摘要

解决方案的结构分为三个C#项目,其中包含以下文件:

Project Activites.Runtime

IActivity

必须实现所有活动类的接口

ActivityBase

所有活动类都必须从中继承的父类

Reflector

具有所有必需的反射方法的类,可在运行时查找、加载和执行活动

RuntimeService

具有运行任何活动的方法的工作流活动服务类

Project ActivityExample

GetCustomerActivity

用于实现活动示例的活动类

Project Console.Test

Program

控制台中动态加载和运行GetCustomer活动的执行示例

使用代码

完整代码解释

Project Activites.Runtime

IActivity.cs

//Interface IActivity requires:
//Workflow Guid, Input parameters, state, Response and ErrorDetail for every activity

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

//Parent class for all activities Implements:
//IActivity Properties
//A constructor with default started state
//RunActivity method to execute the activity and encapsule state and error control
//Virtual RunActivityImplementation method, 
//which must be overridden by every child class

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

 //Attribute to be used to flag Activity
 //to be found dynamically and to be executed at runtime
 public class WorkflowActivitesAttribute : Attribute
 {
    public string ActivityMethodId;
 }

 //Class that defines a repository for assemblies loaded in memory
 public class LoadAssembly
 {
    public string Key { get; set; }
    public DateTime LastModification { get; set; }
    public System.Reflection.Assembly Assembly { get; set; }
 }

 //Reflection class that:
 //Dynamically loads assemblies (.DLLs) from a set path
 //From loaded assemblies, finds all IActivity classes
 //From all IActivity classes, finds specific method that matches 
 //activityMethodId parameter
 public class Reflector
 {
   //set Path from AppConfig
   private static string Path = 
   System.Configuration.ConfigurationManager.AppSettings
          ["WorkflowDLLsClientsFolderPath"]; 

   private static object _LockAssembliesList = new object();

   private static List<LoadAssembly> LoadAssemblies = new List<LoadAssembly>();

   //From loaded assemblies, finds all classes that implement type(IActivity)
   public static List<Type> GetClassesFromInterface(Type type)
   {
       var types = GetAssemblies()
                   .SelectMany(s => s.GetTypes())
                   .Where(p => type.IsAssignableFrom(p) &&
                               p.IsClass)
                   .ToList();

       return types;
   }

   //From all Loaded IActivity classes, 
   //returns specific method name that matches activityMethodId parameter
   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;
   }

   //Load assemblies from Path and manage a repository 
   //to load every assembly only once until there are new versions
   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

// Core to execute Activities from any Service
// Programmer has to integrate a call to this method in its workflow service

public class RuntimeService
{
   //Given a workflow processId, an activityMethodId, and inputParameters for activity
   //This method uses reflector to:
   //Dynamically load DLLs located at Path folder (check out at Reflector class)
   //Get all classes that implements interface IActivity
   //Create an instance for every class by using Activator
   //Find the class that implements the logic to run.
   //By matching activityMethodId attribute and parameter
   //Invoke method RunActivity from ActivityBase
   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;

//Example for an Activity compiled in a separated project from our Workflow service
//Placing it at Path folder(check out at reflector class),
//it will be dynamically loaded and executed.
public class GetCustomerActivity : ActivityBase, IActivity
{
    [WorkflowActivites(ActivityMethodId = "GetCustomer")]
    public override List<KeyValuePair<string, object>> RunActivityImplementation()
    {
        //Get InputParameters (List<KeyValuePair<string, object>>)

        //Execute business logic code
        //Use any Client/Server communication architecture: Web API, WCF, ..

        //Return result in a List<KeyValuePair<string, object>>
        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; 

 //An example that uses RunTimeService to dynamically load ActivityExample DLL
 //and run implementation for GetCustomerActivity class

 class Program
 {
     static void Main(string[] args)
     {
         //Set a new workflow Id
         var workflowGuid = System.Guid.NewGuid();

         //Set input parameters to call GetCustomer Activity
         var inputParameters = new List<KeyValuePair<string, object>>()
         {
             new KeyValuePair<string, object>("Customer",1)
         };
         //Run method "GetCustomer" from Activity class 
         //"GetCustomerActivity" through RuntimeService
         //Activity class "GetCustomerActivity" is loaded at runtime by service
         ActivityBase result = new RuntimeService().RunActivity
                               (workflowGuid, "GetCustomer", inputParameters);
         //Check result state and response

         System.Console.WriteLine("Success...");
         System.Console.ReadLine();
     }
}

App.Config

<!-- Set path folder to place, find and dynamically run the DLLs for the activities -->
<appSettings>
   <add key="WorkflowDLLsClientsFolderPath" value="[DLL_FOLDER_PATH]" />
</appSettings>

https://www.codeproject.com/Tips/5333178/Dynamic-Load-and-Execution-of-Workflow-Activities

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值