0.Elsa源码探索-入门简介
一、Elsa 是什么
Elsa 是一个开源的 .NET 工作流引擎,让你能在任意 .NET 应用中定义和执行工作流。
很多系统上线后,真正拖慢团队的不是不会开发新功能,而是历史代码改不动。业务规则一变就要发版、跨部门协作缺少统一对接规范、问题发生后很难追踪定位。
工作流的意义在于,把这些经常变化的流程抽出来,变成可配置、可观测、可复用的资产。有了工作流,代码执行过程不再是“藏在代码里的隐形逻辑”,而是变成可组合、可观测的显式系统。
除了上述的业务价值外,工作流对框架能力的复用与拓展也非常有帮助,当发送各类消息、调用外部API、处理文件转换这些底层能力被当成工具摆在眼前时,业务开发者就能更好的专注于业务逻辑本身,而不至于到处造轮子。
为什么选 Elsa?首先肯定是因为它由 dotnte 语言编写,同时它功能强大、社区活跃、代码质量优良...在设计思路和完整度上更是遥遥领先!

当然 Elsa 也有一定的局限性,比如:
- 前端使用的 Blazor 技术比较小众、页面风格也不太符合中国人的胃口。
- 对标 n8n、dify、扣子等 AI 工作流时稍显功能不足。
好在这些缺点并不难解决,我们会在后续分享中逐步说明具体解决方案。
二、Elsa 整体架构

三、快速体验
3.1.1 Docker(版本老旧,不推荐)
docker pull elsaworkflows/elsa-server-and-studio-v3:latest
docker run -t -i \
-e ASPNETCORE_ENVIRONMENT=Development \
-e HTTP_PORTS=8080 \
-e HTTP__BASEURL=http://localhost:13000 \
-p 7113:8080 \
elsaworkflows/elsa-server-and-studio-v3:latest
3.1.2 本地运行源代码(版本较新、可读源码,推荐)
-
第一步:拉代码
git clone https://github.com/elsa-workflows/elsa-core.git git clone https://github.com/elsa-workflows/elsa-studio.git -
第二步:启动后端,打开
elsa-core\Elsa.sln,找到并运行启动项目src/apps/Elsa.Server.Web,启动后默认监听https://localhost:5001。数据库使用的是 SQLite 且会自动项目根目录完成初始化,无需任何配置。 -
第三步:启动前端,打开
elsa-studio\Elsa.Studio.sln,找到并运行:src/hosts/Elsa.Studio.Host.Wasm (WASM 模式,在浏览器跑) 或 src/hosts/Elsa.Studio.Host.Server (Server 模式,在服务端渲染)
3.1.3 登录
打开浏览器访问 https://localhost:7113 , 用户名: admin,密码: password
3.1.4 创建第一个工作流
登录后,可按以下步骤体验一个最简单的工作流:
-
点击左侧菜单 Workflow Definitions 点击 Create workflow,输入工作流名称

-
在画布上拖入 HTTP Endpoint 活动,配置路径为
/helloworld,请求方式为GET`

-
再拖入 HTTP Response 活动, Content 内容写 helloworld

-
点击运行按钮后会自动跳转到工作流实例页面,且状态是挂起

-
用浏览器或 curl 访问 https://localhost:5001/workflows/helloworld 页面上会得到
helloworld的响应,并且后台会刷新出执行过程日志。

四、Elsa 设计上的精良之处
4.1 Feature 模块化配置系统
Elsa 把每个功能单元封装为 IFeature,通过 IModule 统一管理依赖和安装,每个 Feature 通过 [DependsOn] 声明依赖,Module.Apply() 自动做拓扑排序,确保按正确顺序安装。不安装的模块不注册,没有任何开销。
services.AddElsa(module => { module.UseWorkflows() // 核心执行引擎 .UseWorkflowManagement(m => m.UseEntityFrameworkCore(ef => ef.UseSqlite())) .UseWorkflowRuntime(r => r.UseEntityFrameworkCore()) .UseDistributedRuntime() // 一行切换分布式模式 .UseJavaScript() // 按需添加 JS 表达式支持 .UseLiquid() // 按需添加 Liquid 模板支持 .UseHttp(); // 按需添加 HTTP 触发器支持 .UseDistributedRuntime() // 一行切换分布式模式 .UseJavaScript() // 按需添加 JS 表达式支持 .UseLiquid() // 按需添加 Liquid 模板支持 .UseHttp(); // 按需添加 HTTP 触发器支持 });
4.2 丰富的预留拓展点
Elsa 的执行过程并不是一个又臭又长的大函数,而是两条可扩展中间件管道:
IWorkflowExecutionPipeline(工作流级) └── DefaultActivitySchedulerMiddleware(调度循环) └── IActivityExecutionPipeline(活动级) └── DefaultActivityInvokerMiddleware(活动执行) └── DefaultActivitySchedulerMiddleware(调度循环) └── IActivityExecutionPipeline(活动级) └── DefaultActivityInvokerMiddleware(活动执行)
类比 ASP.NET Core 的 app.UseXxx() 管道,你可以在任意位置插入自己的中间件:
// 在所有活动执行前后记录耗时 public class TimingMiddleware : IActivityExecutionMiddleware { public async ValueTask InvokeAsync(ActivityExecutionContext ctx, ActivityMiddlewareDelegate next) { var sw = Stopwatch.StartNew(); await next(ctx); Console.WriteLine($"{ctx.Activity.Type} 耗时: {sw.ElapsedMilliseconds}ms"); } }
不需要改任何引擎代码,直接注入中间件就能扩展执行行为。
4.3 灵活的取值逻辑
这是整个项目中最值得学习的设计,活动中所有输入和输出都可以是动态表达式,而不是硬编码值。这意味着在编排的过程中我仅关注静态的逻辑即可,框架会将运行时的实际值放到指定的位置上。
以上文中 demo 中的场景为例,在设置 Http 响应的时候,我们使用的是固定值,而真实场景中一般不会这么简单,此时我们可以有更多选择:
-
使用全局变量

-
使用函数

-
代码说明:
const variable = getVariable('Variable2'); // 使用 Elsa 内置函数获取全局变量 var today = new Date();// 使用原生的 js 对象 // 完全支持 js 习惯的写法 var DD = String(today.getDate()).padStart(2, '0'); var MM = String(today.getMonth() + 1).padStart(2, '0'); var yyyy = today.getFullYear(); hh = String(today.getHours()).padStart(2, '0'); mm = String(today.getMinutes()).padStart(2, '0'); ss = String(today.getSeconds()).padStart(2, '0'); today = yyyy + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss; return variable + today;除了
getVariableElsa 内置还了很多系统函数,可以轻松获取到任何你想获取的值,具体用法将会在后续文章中进行分享。
-
4.4 工作流即代码(Code-first)
Elsa 不强迫你用可视化设计器——你可以完全用 C# 定义工作流:
public class OrderApprovalWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
builder.Root = new Sequence
{
Activities =
{
// 等待 HTTP 请求触发(外部 POST 请求进来时启动)
new HttpEndpoint
{
Path = new("/orders"),
SupportedMethods = new(new[] { HttpMethods.Post }),
CanStartWorkflow = true
},
// 发邮件通知审批人
new SendEmail
{
To = new(new[] { "manager@company.com" }),
Subject = new("新订单待审批"),
Body = new("{{ Input.orderDetails }}")
},
// 等待审批结果(挂起,等下一个 HTTP 请求)
new HttpEndpoint
{
Path = new("/orders/approve"),
SupportedMethods = new(new[] { HttpMethods.Post })
},
// 根据审批结果分支
new If
{
Condition = new("{{ Input.approved }}"),
Then = new Sequence { Activities = { new SendEmail { /* 通知通过 */ } } },
Else = new Sequence { Activities = { new SendEmail { /* 通知拒绝 */ } } }
}
}
};
}
}
4.5 支持多种持久化存储方案
Elsa 的 IWorkflowDefinitionStore、IWorkflowInstanceStore 等都是接口,引擎本身不依赖任何具体数据库,甚至可以实现自己的 Store 接口,对接任意存储...
// 用 SQLite
management.UseEntityFrameworkCore(ef => ef.UseSqlite());
// 换成 PostgreSQL
management.UseEntityFrameworkCore(ef => ef.UsePostgreSql(connectionString));
// 换成 SQL Server
management.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString));
4.6 一行代码启动分布式水平扩展
单节点模式下直接本地执行;需要多节点时:
module.UseDistributedRuntime();// 只需加这一行,本地运行时自动升级为分布式运行时
通过本篇内容您仅需对 Elsa 这个 dotnet 工作流框架建立一个初印象即可,我们会从源码角度逐步展开更多原理和使用细节。

浙公网安备 33010602011771号