工作流引擎之Elsa入门系列教程之一 初始化项目并创建第一个工作流
引子
工作流(Workflow)是对工作流程及其各操作步骤之间业务规则的抽象、概括描述。
为了实现某个业务目标,需要多方参与、按预定规则提交数据时,就可以用到工作流。
通过流程引擎,我们按照流程图,编排一系列的步骤,让数据可以按照一定的规则,一定的顺序,提交给一定的负责人进行处理,实现带有时间轴的数据协作。
目前dotnet平台主流工作流引擎有两个:
轻量级嵌入式工作流引擎。它支持多种持久化方式和并发提供程序,以允许多节点群集,可以编码或者使用json、xml编排工作流。
这个引擎功能比较简单,但不适合处理长期工作流(定时任务类型的),随着执行的次数越来越多,处理速度会越来越慢。
Workflow slow when the count of the execution point more and more #1028
PersistedWorkflow ExecutionPointers exponentially increase in workflow loop. #1030
而且它是异步的,通过webapi启动流程后不能实时返回此次流程中step返回的数据,官方更新速度也不太理想,所以不选择此工作流引擎。
Elsa Core 是一个工作流库,可在任何 .NET Core 应用程序中执行工作流。可以使用代码和可视化工作流设计器来定义工作流。(功能更加全面,附带可视化流程设计器与流程监控页面)
本系列文章选择使用Elsa作为流程引擎,准备介绍此流程引擎的使用与扩展,如何与Abp框架一起使用,集成swagger,一步一步实现一个Demo。
快速开始
我们用vs2022创建一个空的ASP.NET Core Web应用,作为工作流核心服务,包含仪表盘与流程API。
一步一步添加依赖与配置,并启动。后续慢慢改造。
初始化项目
创建一个名为ElsaCore.Server的新项目
dotnet new web -n "ElsaCore.Server"
进入项目文件夹中为项目安装包
cd ElsaCore.Server
dotnet add package Elsa
dotnet add package Elsa.Activities.Http
dotnet add package Elsa.Activities.Timers
dotnet add package Elsa.Activities.UserTask
dotnet add package Elsa.Activities.Temporal.Quartz
dotnet add package Elsa.Persistence.EntityFramework.SqlServer
dotnet add package Elsa.Server.Api
dotnet add package Elsa.Designer.Components.Web
dotnet add package Microsoft.EntityFrameworkCore.Tools
添加ef tools用于初始化数据库
Elsa.Activities.Temporal.Quartz可以换成Elsa.Activities.Temporal.Hangfire,后续会讲解集成Hangfire和仪表盘。
上面的Activities是Elsa提供的几个活动实现,Http就是通过webapi接口形式的、Timers提供定时任务功能、UserTask提供了用户审批的功能,后续会详细解释,并且还有好多其他的Activities,我们还可以自己实现一个新的。
修改Program.cs
using Elsa;
using Elsa.Persistence.EntityFramework.Core.Extensions;
using Elsa.Activities.UserTask.Extensions;
using Elsa.Persistence.EntityFramework.SqlServer;
var builder = WebApplication.CreateBuilder(args);
// Elsa services.
var elsaSection = builder.Configuration.GetSection("Elsa");
builder.Services.AddElsa(elsa => elsa
.UseEntityFrameworkPersistence(ef => ef.UseSqlServer(builder.Configuration.GetConnectionString("Default"), typeof(Program)))
.AddConsoleActivities()
.AddJavaScriptActivities()
.AddUserTaskActivities()
.AddHttpActivities(elsaSection.GetSection("Server").Bind)
.AddQuartzTemporalActivities()
.AddWorkflowsFrom<Program>()
)
// Elsa API endpoints.
.AddElsaApiEndpoints()
// For Dashboard.
.AddRazorPages();
var app = builder.Build();
app.UseStaticFiles()// For Dashboard.
.UseHttpActivities()
.UseRouting()
.UseEndpoints(endpoints =>
{
// Elsa API Endpoints are implemented as regular ASP.NET Core API controllers.
endpoints.MapControllers();
// For Dashboard
endpoints.MapFallbackToPage("/_Host");
});
app.Run();
添加appsettings.json配置
BaseUrl的端口号要和launchSettings.json中的一致
"ConnectionStrings": {
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=ElsaServer;Trusted_Connection=True"
},
"Elsa": {
"Server": {
"BaseUrl": "https://localhost:5001"
}
}
修改launchSettings.json
把launchSettings中的iis profiles删除,端口号改为5001
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:5001",
"sslPort": 5001
}
},
"profiles": {
"ElsaCore.Server": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
初始化数据库
首先生成一次项目,然后执行
dotnet ef migrations add init
会自动创建Migrations目录。

然后更新数据库,执行
dotnet ef database update
此时打开SQL Server对象资源管理器可以看到数据库已经初始化完毕。
创建页面
新建目录Pages,创建在该目录下创建一个_Host.cshtml。
@page "/"
@{
var serverUrl = $"{Request.Scheme}://{Request.Host}";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Elsa Workflows</title>
<link rel="icon" type="image/png" sizes="32x32" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-16x16.png">
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.css">
<script src="/_content/Elsa.Designer.Components.Web/monaco-editor/min/vs/loader.js"></script>
<script type="module" src="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.esm.js"></script>
</head>
<body>
<elsa-studio-root server-url="@serverUrl" monaco-lib-path="_content/Elsa.Designer.Components.Web/monaco-editor/min">
<elsa-studio-dashboard></elsa-studio-dashboard>
</elsa-studio-root>
</body>
</html>
启动项目
运行该项目,打开浏览器访问https://localhost:5001/,页面如下所示:

第一个HTTP Endpoint工作流
我们先定义一个简单的工作流,后续会实现启动参数与返回特定格式数据的流程。
定义工作流的方式有两种,使用设计器和代码。设计器定义的好处是可以在运行时动态添加与修改流程,并且是直接在流程图上进行修改,但是只能使用已注册的Activity,如果业务需要自定义Activity,则还是需要先写一些代码。
通过流程设计器定义
新建流程
选择菜单中的Workflow Definitions,进入工作流定义页,点击Create Workflow创建一个新的工作流。

点击Start,然后选择Http里面的HTTP Endpoint创建一个接口用来做为流程的入口。

设置参数并保存
- Path:
/design/hello-world - Methods: GET
接下来设置该接口的返回值。在流程的Done节点下点加号,选择HTTP里面的HTTPResponse,设置参数并保存:
- Content:
<h1>Hello World! </h1><p>这是通过设计器实现的流程</p> - Content Type:
text/html - Status Code:
OK

设置流程名称,点击右上角的设置按钮,设置Name为hello-world-design,Display Name为hello-world by design

点击右下角的publish发布流程。此时返回到Workflow Definitions中可以看到刚刚定义好的流程。

启动流程
因为hello-world-design这个流程是由HTTP Endpoint作为起点,所以我们可以通过接口来启动该流程。
访问hello-world-design可以看到如下效果

此时我们点击Workflow Instances可以看到刚刚执行的工作流实例,点击进入可以看到流程执行的详细过程。


使用代码定义
我们通过代码的方式实现上述流程。
新建流程
新建一个Workflows目录用于存放工作流。
创建一个类名为:HelloWorldWorkflow,并实现IWorkflow接口。具体代码如下:
using Elsa.Builders;
using Elsa.Activities.Http;
namespace ElsaCore.Server.Workflows
{
public class HelloWorldWorkflow : IWorkflow
{
public void Build(IWorkflowBuilder builder)
{
builder.HttpEndpoint(setup =>
{
setup.WithMethod(HttpMethod.Get.Method).WithPath("/code/hello-world");
})
.Then<WriteHttpResponse>(setup =>
{
setup.WithContentType("text/html")
.WithContent("<h1>Hello World! </h1><p>这是通过代码实现的流程</p>")
.WithStatusCode(System.Net.HttpStatusCode.OK);
});
}
}
}
因为我们在Program.cs中配置Elsa的时候使用了AddWorkflowsFrom<Program>(),所以会自动扫描目标类所在的程序集下所有实现IWorkflow接口的工作流自动注册。
否则需要调用AddWorkflow<HelloWorldWorkflow>()手动注册流程。
查看流程
启动项目并点击Workflow Registry可以看到我们刚刚创建的流程

点进去可以看到流程图,但因为是代码实现的所以是只读。

启动流程
访问https://localhost:5001/code/hello-world即可。

小结
本次我们创建了一个新项目,引入了一些Elsa相关的包,完成了工作流服务+图形化工作流仪表盘。创建了一个简单的工作流,但是这样是远远不够的,我们需要更加复杂的工作流,比如自定义参数、不同参数返回不同结果,模拟一些真实的业务场景,慢慢熟悉此框架,应用到真实的业务场景中,将在后续文章中体现,未完待续...
工作流引擎之Elsa入门系列教程之一 初始化项目并创建第一个工作流的更多相关文章
- Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求
上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...
- Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数
上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...
- Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数
上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...
- Angular2入门系列教程4-服务
上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...
- ASP.NET MVC 入门系列教程
ASP.NET MVC 入门系列教程 博客园ASP.NET MVC 技术专题 http://kb.cnblogs.com/zt/mvc/ 一个居于ASP.NET MVC Beta的系列入门文章,有朋友 ...
- Qt快速入门系列教程目录
Qt快速入门系列教程目录
- Android视频录制从不入门到入门系列教程(一)————简介
一.WHY Android SDK提供了MediaRecorder帮助开发者进行视频的录制,不过这个类很鸡肋,实际项目中应该很少用到它,最大的原因我觉得莫过于其输出的视频分辨率太有限了,满足不了项目的 ...
- Android视频录制从不入门到入门系列教程(三)————视频方向
运行Android视频录制从不入门到入门系列教程(二)————显示视频图像中的Demo后,我们应该能发现视频的方向是错误的. 由于Android中,Camera给我们的视频图片的原始方向是下图这个样子 ...
- 数据挖掘入门系列教程(二)之分类问题OneR算法
数据挖掘入门系列教程(二)之分类问题OneR算法 数据挖掘入门系列博客:https://www.cnblogs.com/xiaohuiduan/category/1661541.html 项目地址:G ...
随机推荐
- 常见的JVM 面试题
1.讲一讲JVM的跨平台与跨语言 跨平台 我们写的一个类,在不同的操作系统上(Linux.windows.Mac OS)执行,效果是一样的.这就是JVM的跨平台性. 跨语言 JVM只识别字节码,JVM ...
- Python入门-pip模块管理工具
安装 # 在线安装 pip install <包名> 安装后,该模块文件会在安装python环境目录:lib/packages目录下 # 安装本地安装包 pip install <目 ...
- Kubernetes架构-图解
- 时间篇之centos7修复ntpq: read: Connection refused
关于ntp同步时间, 由于是解决问题,所以理论性内容不多. 关于UTC NTP要提供准确的时间,就必须有准确的时间来源,那可以用格林尼治时间吗?答案是否定的. 因为格林尼治时间是以地球自转为基础的时间 ...
- Golang 源码解读 01、深入解析 strings.Builder、strings.Join
strings.Builder 源码解析. 存在意义. 实现原理. 常用方法. 写入方法. 扩容方法. String() 方法. 禁止复制. 线程不安全. io.Writer 接口. 代码. stri ...
- iOS全埋点解决方案-UITableView和UICollectionView点击事件
前言 在 $AppClick 事件采集中,还有两个比较特殊的控件: UITableView •UICollectionView 这两个控件的点击事件,一般指的是点击 UITableViewCell 和 ...
- 搜索与图论①-深度优先搜索(DFS)
深度优先搜索(DFS) 例题一(指数型枚举) 把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序. 输入格式 一个整数 n. 输出格式 按照从小到大的顺序输出所有方案,每行 1 个. ...
- 2021.08.05 P7095 不离【扶咕咕出题】(贪心)
2021.08.05 P7095 不离[扶咕咕出题](贪心) [P7095 yLOI2020] 不离 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 游戏中人物有两个属性,我 ...
- Collection工具类
Collection工具类: 集合工具类,定义除了存取以外的集合常用方法 方法: public static void reverse(List<?> list) //反转集合中元素的 ...
- Git删除已提交的文件
Git删除已提交的文件 Git删除已提交的文件 定位文件 删除文件 参考链接 昨天通过Git Bash提交代码的时候遇到了由于单个文件大小超过100M,导致代码上传失败的问题.考虑到那个大文件是用于训 ...