前言

WorkFlowCore是一个针对.NetCore的轻量级的工作流引擎,提供了FluentAPI、多任务、持久化以及并行处理的功能,适合于小型工作流、责任链的需求开发。支持工作流长期运行,提供了各种持久化方式。

本篇开发环境为.Net7,此处不演示Jsonyaml配置,详细文档请查看官方文档项目源码地址

一、安装与基础使用

通过以下命令安装

Install-Package WorkflowCore

然后注入WorkFlowCore

builder.Services.AddWorkflow();

WorkFlowCore主要分为两部分:步骤工作流

 步骤

 多个步骤组成一个工作流,每个步骤都可以有输入并产生输出,这些输出可以传递回其所在的工作流。通过创建继承抽象类StepBody或StepBodyAsync的类,并且实现Run或RunAsync方法来定义步骤,很明显它们的区别是是否异步

public class FirstStepBody: StepBody
{
public override ExecutionResult Run(IStepExecutionContext context)
{
Console.WriteLine("Hello world!First");
return ExecutionResult.Next();
}
}

工作流

通过继承IWorkflow接口定义一个工作流,接口只有IdVersionBuild方法(内部可以执行多个步骤),工作流主机使用这些信息来标识工作流

public class MyWorkflow :IWorkflow
{
public string Id => "HelloWorld";
public int Version => 1;
public void Build(IWorkflowBuilder<object> builder)
{
builder
.StartWith<FirstStepBody>()
.Then<FirstStepBody>();
}
}

工作流如果想使用必须在工作流主机中通过RegisterWorkflow()方法注册,并且通过Start()方法启动主机,当然也可以通过Stop()方法停止工作流。执行工作流需要使用StartWorkflow()方法,参数为工作流类的Id,如下

 [ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IWorkflowHost _workflowHost;
public WeatherForecastController(IWorkflowHost workflowHost)
{
_workflowHost = workflowHost;
}
[HttpGet(Name = "get")]
public ContentResult Get()
{
if (!_workflowHost.Registry.IsRegistered("HelloWorld",1))
{
_workflowHost.RegisterWorkflow<MyWorkflow>();
}
_workflowHost.Start();
_workflowHost.StartWorkflow("HelloWorld");
//host.Stop();
return Content("ok");
}
}

当然也可以在构建web服务的时候统一注册,然后就可以直接执行啦

var host = app.Services.GetService<IWorkflowHost>();
host.RegisterWorkflow<MyWorkflow>();
host.Start();

二、在步骤之间传递参数

每个步骤都是一个黑盒,因此它们支持输入和输出。这些输入和输出可以映射到一个数据类,该数据类定义与每个工作流实例相关的自定义数据。

以下示例显示了如何定义步骤的输入和输出,然后显示了如何使用内部数据的类型化类定义工作流,以及如何将输入和输出映射到自定义数据类的属性。

//步骤包含属性,并且计算
public class FirstStepBody: StepBody
{
public int Input1 { get; set; }
public int Input2 { get; set; }
public int Output { get; set; }
public override ExecutionResult Run(IStepExecutionContext context)
{
Output = Input1 + Input2;
Console.WriteLine(Output);
return ExecutionResult.Next();
}
}
//工作流包含输入输出的赋值
public class MyWorkflow :IWorkflow<MyDataClass>
{
public string Id => "HelloWorld";
public int Version => 1;
public void Build(IWorkflowBuilder<MyDataClass> builder)
{
builder
.StartWith<FirstStepBody>()
.Input(step => step.Input1,data => data.Value1)
.Input(step => step.Input2, data => 100)
.Output(data => data.Answer, step => step.Output)
.Then<FirstStepBody>()
.Input(step => step.Input1, data => data.Value1)
.Input(step => step.Input2, data => data.Answer)
.Output(data => data.Answer, step => step.Output);
}
}
//工作流的属性类
public class MyDataClass
{
public int Value1 { get; set; }
public int Value2 { get; set; }
public int Answer { get; set; }
}
//执行工作流传入参数
MyDataClass myDataClass = new MyDataClass();
myDataClass.Value1 = 100;
myDataClass.Value2 = 200;
//不传入myDataClass则每次执行都是新的数据对象
_workflowHost.StartWorkflow("HelloWorld", myDataClass);

从上述例子可以看到工作流可以定义一个初始的类作为参数传入,每个步骤可以有自己的属性字段去接收参数(可以是工作流类的字段,也可以是固定值),可以用Input方法传入,Output方法输出赋值。如果在工作流执行时不传入参数每次执行都是新的对象的默认值,比如在StartWorkflow方法中不传myDataClass,运行结果是100100,否则是200300

三、外部事件

工作流可以使用WaitFor方法进行等待,通过外部触发此事件,将事件产生的数据传递给工作流,并且让工作流继续执行下面的步骤。示例如下:

public class MyWorkflow :IWorkflow<MyDataClass>
{
//省略。。。。
public void Build(IWorkflowBuilder<MyDataClass> builder)
{
builder
.StartWith<FirstStepBody>()
.Input(step => step.Input1,data => data.Value1)
.Input(step => step.Input2, data => 100)
.Output(data => data.Answer, step => step.Output)
.WaitFor("MyEvent",key => "EventKey")
.Output(data => data.Answer,step => step.EventData)
.Then<FirstStepBody>()
.Input(step => step.Input1, data => data.Value1)
.Input(step => step.Input2, data => data.Answer)
.Output(data => data.Answer, step => step.Output);
}
}
//。。。
[HttpGet(Name = "get")]
public ContentResult Get()
{
MyDataClass myDataClass = new MyDataClass();
myDataClass.Value1 = 100;
myDataClass.Value2 = 200;
_workflowHost.StartWorkflow("HelloWorld", myDataClass);
return Content("ok");
}
  [HttpPost(Name = "event")]
  public ContentResult PublishEvent()
  {
    _workflowHost.PublishEvent("MyEvent", "EventKey", 200);
    return Content("ok");
  }

使用WaitFor方法可以使工作流等待监听指定事件的执行,有两个入参事件名称事件关键字。通过工作流主机去触发PublishEvent执行指定的事件,有三个入参触发事件名称触发事件关键字和事件参数

需要执行事件,工作流才会继续下一步,如下动图演示:

可以为等待事件设置有效时间,在有效时间之前执行事件是不会继续下一步流程的,只有当大于有效时间之后执行事件才会继续下一步步骤。如下代码设置,为工作流执行时间一天后执行事件才会继续执行,否则就等待不动。

WaitFor("MyEvent",key => "EventKey", data => DateTime.Now.AddDays(1))

四、活动

活动被定义为在工作流中可以被等待的外部工作队列中的步骤。

在本例中,工作流将等待活动activity-1,直到活动完成才继续工作流。它还将data.Value1的值传递给活动,然后将活动的结果映射到data.Value2

然后我们创建一个worker来处理活动项的队列。它使用GetPendingActivity方法来获取工作流正在等待的活动和数据。

    //.....
builder
.StartWith<FirstStepBody>()
.Input(step => step.Input1,data => data.Value1)
.Input(step => step.Input2, data => 100)
.Output(data => data.Answer, step => step.Output)
.Activity("activity-1", (data) => data.Value1)
.Output(data => data.Value2, step => step.Result)
.Then<FirstStepBody>()
.Input(step => step.Input1, data => data.Value1)
.Input(step => step.Input2, data => data.Answer)
.Output(data => data.Answer, step => step.Output);
//....
[HttpPost(Name = "active")]
   public ContentResult PublishEvent()
   {
    var activity = _workflowHost.GetPendingActivity("activity-1", "worker1", TimeSpan.FromMinutes(1)).Result;
    if (activity != null)
    {
      Console.WriteLine(activity.Parameters);
      _workflowHost.SubmitActivitySuccess(activity.Token, 100);
    }
    return Content("ok");
   }

活动可以看作一个等待的步骤可以传入参数和输出参数,和事件的区别是事件不能输入参数而是单纯的等待。

五、错误处理

每个步骤都可以配置自己的错误处理行为,可以在以后重试、挂起工作流或终止工作流。

    public void Build(IWorkflowBuilder<object> builder)
{
builder
.StartWith<HelloWorld>()
.OnError(WorkflowErrorHandling.Retry,TimeSpan.FromMinutes(10))
.Then<GoodbyeWorld>();
}

六、流程控制

工作流的流程控制包括分支、循环等各种操作

决策分支

在工作流中定义多个独立分支,并根据表达式值选择满足条件的分支执行。

使用IWorkflowBuilderCreateBranch方法定义分支。然后我们可以使用branch方法选择一个分支。

选择表达式将与通过branch方法列出的分支相匹配,匹配的分支将安排执行。匹配多个分支将导致并行分支运行。

如果data.Value1的值为1,则此工作流将选择branch1,如果为2,则选择branch2

  var branch1 = builder.CreateBranch()
.StartWith<PrintMessage>()
.Input(step => step.Message, data => "hi from 1")
.Then<PrintMessage>()
.Input(step => step.Message, data => "bye from 1");   var branch2 = builder.CreateBranch()
.StartWith<PrintMessage>()
.Input(step => step.Message, data => "hi from 2")
.Then<PrintMessage>()
.Input(step => step.Message, data => "bye from 2");
  builder
.StartWith<HelloWorld>()
.Decide(data => data.Value1)
.Branch((data, outcome) => data.Value1 == "one", branch1)
.Branch((data, outcome) => data.Value1 == "two", branch2);

并行ForEach

使用ForEach方法启动并行for循环

  public class ForEachWorkflow : IWorkflow
  {
  public string Id => "Foreach";
  public int Version => 1;
  public void Build(IWorkflowBuilder<object> builder)
  {
  builder
  .StartWith<SayHello>()
  .ForEach(data => new List<int>() { 1, 2, 3, 4 })
  .Do(x => x
  .StartWith<DisplayContext>()
  .Input(step => step.Message, (data, context) => context.Item)
  .Then<DoSomething>())
  .Then<SayGoodbye>();
  }
  }

While循环

使用While方法启动while循环

  public class WhileWorkflow : IWorkflow<MyData>
  {
  public string Id => "While";
  public int Version => 1;
  public void Build(IWorkflowBuilder<MyData> builder)
  {
  builder
  .StartWith<SayHello>()
  .While(data => data.Counter < 3)
  .Do(x => x
  .StartWith<DoSomething>()
  .Then<IncrementStep>()
  .Input(step => step.Value1, data => data.Counter)
  .Output(data => data.Counter, step => step.Value2))
  .Then<SayGoodbye>();
  }
  }

If判断

使用If方法执行if判断

  public class IfWorkflow : IWorkflow<MyData>
  {
  public void Build(IWorkflowBuilder<MyData> builder)
  {
  builder
  .StartWith<SayHello>()
  .If(data => data.Counter < 3).Do(then => then
   .StartWith<PrintMessage>()
  .Input(step => step.Message, data => "Value is less than 3")
  )
  .If(data => data.Counter < 5).Do(then => then
  .StartWith<PrintMessage>()
  .Input(step => step.Message, data => "Value is less than 5")
  )
  .Then<SayGoodbye>();
  }
  }

并行

使用Parallel方法并行执行任务

  public class ParallelWorkflow : IWorkflow<MyData>
  {
  public string Id => "parallel-sample";
  public int Version => 1;
  public void Build(IWorkflowBuilder<MyData> builder)
  {
  builder
  .StartWith<SayHello>()
  .Parallel()
  .Do(then =>
  then.StartWith<Task1dot1>()
  .Then<Task1dot2>()
  .Do(then =>
  then.StartWith<Task2dot1>()
  .Then<Task2dot2>()
  .Join()
  .Then<SayGoodbye>();
}
}

Schedule

使用Schedule方法在工作流中注册在指定时间后执行的异步方法

builder
.StartWith(context => Console.WriteLine("Hello"))
.Schedule(data => TimeSpan.FromSeconds(5)).Do(schedule => schedule
.StartWith(context => Console.WriteLine("Doing scheduled tasks"))
)
.Then(context => Console.WriteLine("Doing normal tasks"));

Recur

使用Recure方法在工作流中设置一组重复的后台步骤,直到满足特定条件为止

builder
.StartWith(context => Console.WriteLine("Hello"))
.Recur(data => TimeSpan.FromSeconds(5), data => data.Counter > 5).Do(recur => recur
.StartWith(context => Console.WriteLine("Doing recurring task"))
)
.Then(context => Console.WriteLine("Carry on"));

七、Saga transaction

saga允许在saga transaction中封装一系列步骤,并为每一个步骤提供补偿步骤,使用CompensateWith方法在对应的步骤后面添加补偿步骤,补偿步骤将会在步骤抛出异常的时候触发。

如下示例,步骤Task2如果抛出一个异常,那么补偿步骤UndoTask2UndoTask1将被触发。

builder
.StartWith(context => Console.WriteLine("Begin"))
.Saga(saga => saga
.StartWith<Task1>()
.CompensateWith<UndoTask1>()
.Then<Task2>()
.CompensateWith<UndoTask2>()
.Then<Task3>()
.CompensateWith<UndoTask3>()
)
.CompensateWith<CleanUp>()
.Then(context => Console.WriteLine("End"));

也可以指定重试策略,在指定时间间隔后重试。

builder
.StartWith(context => Console.WriteLine("Begin"))
.Saga(saga => saga
.StartWith<Task1>()
.CompensateWith<UndoTask1>()
.Then<Task2>()
.CompensateWith<UndoTask2>()
.Then<Task3>()
.CompensateWith<UndoTask3>()
)
.OnError(Models.WorkflowErrorHandling.Retry, TimeSpan.FromSeconds(5))
.Then(context => Console.WriteLine("End"));

八、持久化

可以使用RedisMongdbSqlserver等持久化,具体可以看文档,此处使用Redis,先安装nuget

Install-Package WorkflowCore.Providers.Redis

然后注入就可以了

builder.Services.AddWorkflow(cfg =>
{
cfg.UseRedisPersistence("localhost:6379", "app-name");
cfg.UseRedisLocking("localhost:6379");
cfg.UseRedisQueues("localhost:6379", "app-name");
cfg.UseRedisEventHub("localhost:6379", "channel-name");
//cfg.UseMongoDB(@"mongodb://mongo:27017", "workflow");
//cfg.UseElasticsearch(new ConnectionSettings(new Uri("http://elastic:9200")), "workflows");
});

运行打开可以看到

.Net Core工作流WorkFlowCore的更多相关文章

  1. .NET Core工作流引擎(RoadFlow)多语言版发布

    经过两个月的辛苦努力.NET Core工作流引擎(RoadFlow)多语言版发布了,在原来只有一种简体中文语言的基础上增加了繁体中文和英文两种语言,还可以通过扩展增加任意语言包.至此RoadFlow工 ...

  2. 开源轻量级工作流WorkflowCore介绍

    在.Net Framework环境下,我们使用Windows Workflow Foundation(WF)作为项目的工作流引擎,可是.Net Core已经不支持WF了,需要为基于.Net Core的 ...

  3. RoadFlow ASP.NET Core工作流引擎IIS部署

    RoadFlow最新版本采用ASP.NET CORE2.1开发,部署步骤和.NET CORE部署一样,具体可参数ASP.NET CORE的部署方式. 1. 获取代码 首先从RoadFlow官网下载最新 ...

  4. RoadFlow ASP.NET Core工作流配置文件说明

    工作流配置文件及说明如下: { "Logging": { "LogLevel": { "Default": "Warning&qu ...

  5. roadflow asp.net core版工作流引擎更新发布

    ROADFLOW CORE (.NET CORE工作流引擎)更新说明 1.RoadFlow全新工作流平台采用.NET CORE 2.1重构,结构更简单,逻辑梳理更清析,性能有了很大的提升. 2.表单设 ...

  6. Orchard Core 使用工作流处理审批和创建内容项

    译自:http://www.ideliverable.com/blog/orchard-core-workflows-walkthrough-content-approval 转载请注明出处, 原文地 ...

  7. babel 的介绍及其配置

    vue/cli -- babel Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其 ...

  8. 一个适合于.NET Core的超轻量级工作流引擎:Workflow-Core

    一.关于Workflow-Core 近期工作上有一个工作流的开发需求,自己基于面向对象和职责链模式捣鼓了一套小框架,后来在github上发现一个轻量级的工作流引擎轮子:Workflow-Core,看完 ...

  9. 开源工作流引擎 Workflow Core 的研究和使用教程

    目录 开源工作流引擎 Workflow Core 的研究和使用教程 一,工作流对象和使用前说明 二,IStepBuilder 节点 三,工作流节点的逻辑和操作 容器操作 普通节点 事件 条件体和循环体 ...

  10. ABP+WorkflowCore+jsplumb实现工作流

    前言 ABP目前已经是很成熟的开发框架了,它提供了很多我们日常开发所必须的功能,并且很方便扩展,让我们能更专注于业务的开发.但是ABP官方并没有给我们实现工作流. 在.net core环境下的开源工作 ...

随机推荐

  1. 新安装的eclipse没有新建java project----解决方法:安装插件

    问题描述:最近新安装的一个eclipse版本,建立新工程的时候发现没有java project选项,如下: 百度了一些资料:https://blog.csdn.net/sinat_41752599/a ...

  2. 解决SpringBoot前后端集成项目导出jar包运行访问页面资源报错“Whitelabel Error Page”问题

    一.SpringBoot前后端集成项目导出jar包后运行访问页面资源报错"Whitelabel Error Page"问题 二.解决方案 1.将Controller层移入com.x ...

  3. Javaheima13

    Java Stream流 salary 薪水 bonus 奖金 top performer 优秀员工 punish 处罚 1员工信息至少包含了(名称.性别.工资.奖金.处罚记录) 2开发一部有4个员工 ...

  4. 探测域名解析依赖关系(运行问题解决No module named 'DNS')

    探测域名解析依赖关系 最近很懒,今天晚上才开始这个任务,然后发现我原来能跑起来的程序跑不起来了. 一直报错 ModuleNotFoundError: No module named 'DNS' 这个应 ...

  5. 剑指offer----1.二维数组查找

    题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. ...

  6. JavaScript 函数的方法

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. JSTL常用便签choose和JSTL常用便签foreach

    2. choose :相当于 java 代码的 switch 语句 1.使用 choose 标签声明    相当于switch 2.使用 when 标签做判断   相当于case 3,使用 other ...

  8. 运行npm报错:无法加载文件 D:\nodejs\node_global\webpack.ps1,因为在此系统上禁止运行脚本

    npm报错 在 windows终端输入 vue init webpack app, 创建一个名为 app 的 Vue 项目时报错如下: 无法加载文件 D:\nodejs\node_global\web ...

  9. 2019.11.14 typeScript环境搭建

    当前环境为windows系统,在VSCode下搭建typeScript环境.在mac系统下同window系统一样,只是安装好ts环境后可能会报tsc命令不能使用的错误,这个时候需要找到tsc命令所在的 ...

  10. python的setattr可以直接给类设置属性!!!!而不是非要是对象

    dynamic.py import unittest class MakeTestCases: testcases = None def make_test_func(self, case=None) ...