Elsa工作流需显式配置服务注册、活动发现、持久化及表达式引擎,缺一不可;HttpEndpoint依赖中间件顺序和路径匹配,自定义Activity必须标注Input/Output特性并调用CompleteAsync()。

Elsa 工作流在 C# 中不是“装个 NuGet 就能跑”的轻量库,它依赖服务注册、活动发现、持久化配置和表达式引擎初始化 —— 缺一不可。直接写 new SendEmail() 不会触发执行,必须通过 ElsaServer 托管并注册为可发现活动。
如何用 C# 代码定义并注册一个可运行的工作流
纯代码定义工作流(WorkflowBase 子类)只是“蓝图”,不注册就进不了引擎调度队列。
- 必须在
Program.cs中调用builder.Services.AddElsa(...)并启用对应模块,比如AddElsaHttpActivities()和AddElsaEmailActivities() -
SendEmail活动依赖 SMTP 配置,漏掉services.Configure<smtpoptions>(...)</smtpoptions>会导致运行时报InvalidOperationException: No service of type 'SmtpClient' registered - 工作流类需标记
[PublicWorkflow]或显式调用elsaBuilder.AddWorkflow<MyWorkflow>(),否则设计器里看不到、API 也查不到 - 若使用内存存储(默认),重启后工作流实例丢失;生产环境务必配置
AddElsaEntityFrameworkStores()并指定数据库提供程序
为什么 HttpEndpoint 收不到请求?常见配置断点
HttpEndpoint 不是 ASP.NET Core 的 MapPost,它依赖中间件链和路由匹配器,且受 CanStartWorkflow 和路径解析规则约束。
- 确保已调用
app.UseHttpActivities()—— 这个中间件负责拦截/elsa/api/workflows/trigger和挂载的 HTTP 路径,漏掉就完全无响应 -
Path = new("/send-email")是相对路径,实际访问地址是http://localhost:5000/send-email(非/elsa/...),注意端口和反向代理是否透传 -
SupportedMethods必须包含客户端请求方法,比如用curl -X GET测试但只配了Post,会返回 405 - 开发时若启用了 HTTPS 重定向,而
HttpEndpoint又没配RequireHttps = false,可能被中间件提前拦截
自定义 Activity 必须实现的三个关键点
继承 Activity 类只是开始,Elsa 在执行时会反射调用特定方法并注入上下文,跳过任一环节都会静默失败或抛 NullReferenceException。
- 属性必须用
Input/Output特性标注,例如[Input] public Input<string> Body { get; set; }</string>,否则绑定不到表达式值 - 重写
ExecuteAsync(ActivityExecutionContext context),并在其中调用context.CompleteAsync()或context.CancelAsync(),不调用则流程卡死 - 若需异步 IO(如调用 HttpClient),必须用
await,且不能在同步方法中调用GetAwaiter().GetResult(),否则引发线程死锁(尤其在 Blazor Server 场景)
调试工作流执行失败最有效的三件事
日志里只有 Workflow instance {id} completed 不代表成功 —— 它可能因异常被吞掉或跳过活动。
- 启用详细日志:
builder.Logging.AddFilter("Elsa", LogLevel.Debug),重点关注Elsa.Workflows.Runtime和Elsa.Activities.Http命名空间 - 检查
ActivityExecutionContext.Inputs和.Outputs字典内容,确认表达式是否求值为空(比如{{ input.Body }}在 POST JSON 时未启用JsonInputConverter就会是 null) - 用管理 API 直查实例状态:
GET /elsa/api/workflow-instances/{id},看ExecutionLog数组里每一步的Status和Exception字段
Elsa 的“自动”背后全是显式配置,从活动注册、表达式解析到存储适配器,每个环节都可能成为黑盒。最容易被忽略的是:HTTP 活动的路径匹配和中间件顺序,以及自定义 Activity 中 CompleteAsync() 的调用时机 —— 这两者出问题,不会报错,只会让工作流看起来“没反应”。


















