c# elsa 3.5.2 程序化工作流常用功能及自定义中间件
nuget
Elsa 3.5.3
依赖注入
builder.Services.AddElsa();
基础流程创建
//需要注入对象
IWorkflowRunner workflowRunner;
//顺序工作流
var workflow = new Sequence();
workflow.Activities = new List<IActivity>();
for (int i = 0; i < 3; i++)
{
workflow.Activities.Add(new WriteLine("test"));
}
var result = await workflowRunner.RunAsync(workflow);
//流程
var nameVariable = new Variable<string>();
// Define the activities to put in the flowchart:
var writeLine1 = new WriteLine("Please tell me your name:");
//var writeLine2 = new ReadLine(nameVariable);
var writeLine2 = new WriteLine(nameVariable);
var writeLine3 = new WriteLine(context => $"Nice to meet you, {nameVariable.Get(context)}!");
var cus = new TestActivity();
// Define a flowchart workflow:
var workflow = new Flowchart
{
// Register the name variable.
Variables = { nameVariable },
// Add the activities.
Activities =
{
writeLine1,
writeLine2,
writeLine3,
cus
},
// Setup the connections between activities.
Connections =
{
new Connection(writeLine1, writeLine2),
new Connection(writeLine2, writeLine3),
new Connection(writeLine3, cus)
}
};
var result = await workflowRunner.RunAsync(workflow);
自定义活动
注册自定义活动
builder.Services.AddElsa(options => options.AddActivity<TestActivity>());
自定义活动
通过context.CompleteActivityAsync标记当前活动已经完成,可以直接执行后续的活动
public class TestActivity : Activity //, IActivityWithResult
{
//public Output Result { get; set; } = new();
//[Input] public Input<decimal> Amount { get; set; } = default!;
//[Output] public Output<string> TransactionId { get; set; } = default!;
/// <summary>
/// 判断活动是否可以执行
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected override ValueTask<bool> CanExecuteAsync(ActivityExecutionContext context)
{
//Console.WriteLine("CanExecuteAsync!");
return ValueTask.FromResult(true);
//return base.CanExecuteAsync(context);
}
/// <summary>
/// 活动中执行的具体逻辑
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
{
Console.WriteLine("活动已执行!");
return context.CompleteActivityAsync();
}
}
变量定义、获取、修改
在定义工作流时定义变量或者在活动节点内部定义
工作流定义变量
通过Variables属性定义工作流的变量,也可以在后续节点中进行定义
var nameVariable = new Variable<bool>("boolid", true);
var testStrVariable = new Variable<string>("testStr", "testStr");
Input<bool> x = new(false);
//顺序工作流
var workflow = new Sequence();
workflow.Variables = new List<Variable>()
{
nameVariable,
testStrVariable
};
活动内对变量进行操作
主要是通过上下文对象的几个变量操作的方法进行处理
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
{
Console.WriteLine("活动已执行!");
//旧版本写法
//var nameVariable = new Variable<bool>("boolid", false);
////nameVariable.Value = true;
//var x = nameVariable.Get(context);
//var testVariable = new Variable<string>("testStr", string.Empty);
//var strValue = testVariable.Get(context);
var boolValue = context.GetVariable<bool>("boolid");
var strValue = context.GetVariable<string>("testStr");
//设置变量值
context.SetVariable("testStr", "act-test");
//添加一个新变量
context.SetVariable("newVar", "newVar");
//获取修改后的变量
strValue = context.GetVariable<string>("testStr");
//var testMetadata = context.GetMetadata<string>("testMetadata");
return context.CompleteActivityAsync();
}
获取内部依赖注入
var service = context.GetRequiredService<FlowService>();
入参
传入入参
通过RunWorkflowOptions的Input属性传递入参
var option = new RunWorkflowOptions();
option.Input = new Dictionary<string, object>();
option.Input.Add("inputStr1", "inputStr1");
var result = await workflowRunner.RunAsync(workflow, option);
读取入参
在活动节点内部通过上下文的WorkflowInput属性获取
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
{
var inputStr = context.WorkflowInput["inputStr1"];
return context.CompleteActivityAsync();
}
出参
活动内部写入出参
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
{
context.WorkflowExecutionContext.Output.Add("outputStr1", "outputstr");
context.WorkflowExecutionContext.Output.Add("outputStr2", "outputstr2");
return context.CompleteActivityAsync();
}
获取出参结果
var workflow = ElsaUtil.GetVaraibleTest();
workflow.Name = "Test";
workflow.Id = "testFlowID";
var option = new RunWorkflowOptions();
var result = await workflowRunner.RunAsync(workflow, option);
//获取结果
var output = result.WorkflowState.Output;
IWorkflowRuntime
说明
IWorkflowRuntime提供了更多高级属性和功能,workflowRunner只提供了基础的功能。
如果希望由触发器触发、消息队列触发工作流时可以用这种方式。
可以参考官方说明IWorkflowRunner vs IWorkflowRuntime vs IWorkflowDispatcher
IWorkflowRunner
- 为工作流程逻辑编写单元测试
- 执行不需要持久化的简单、短暂的工作流程
- 完全在进程中运行工作流程,无需外部依赖
- 你需要即时、同步执行
IWorkflowRuntime
- 构建需要工作流持久性和状态管理的应用程序
- 暂停后你需要恢复工作流程(书签、延迟)。
- 你需要用于工作流程作的高级客户端 API
- 大多数生产场景中,执行要求标准
基础使用
- 创建测试流程
public class TestFlow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
builder.Root = new WriteLine("Hello flow!");
}
}
- 注册流程
builder.Services.AddElsa(elsa =>
{
elsa.AddWorkflow<TestFlow>();
}
);
- 注入IWorkflowRuntime
根据配置的注入方式在使用的地方注入
IWorkflowRuntime workflowRuntime;
- 调用
var client = await workflowRuntime.CreateClientAsync();
var result = await client.CreateAndRunInstanceAsync(new CreateAndRunWorkflowInstanceRequest
{
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId("TestFlow"),
Input = new Dictionary<string, object>
{
["message"] = "Hello from the library!",
["userId"] = 123
},
CorrelationId = "optional-correlation-id",
TriggerActivityId = "testFlowID"
});
CodeActivity
前面通过继承Activity可以实现自定义活动,但是需要调用return context.CompleteActivityAsync();通过引擎当前活动已经执行结束可以继续下一个活动。
也可以通过CodeActivity来实现,CodeActivity继承了Activity,可以用来处理简单的有明确输入输出的活动。
Activity可以用来处理复杂的场景,控制暂停和恢复。CodeActivity通常一次执行完成。
支持泛型参数CodeActivity
public class TestCodeActivity : CodeActivity
{
/// <summary>
///
/// </summary>
public Input<string> TestName { get; set; } = default!;
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected override ValueTask<bool> CanExecuteAsync(ActivityExecutionContext context)
{
return ValueTask.FromResult(true);
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
{
Console.WriteLine($"活动已执行!{TestName}");
return ValueTask.CompletedTask;
}
}
获取CodeActivity返回值
- 定义返回值类型(CodeActivity
)
public class TestCodeGenericActivity : CodeActivity<string>
{
protected override ValueTask<bool> CanExecuteAsync(ActivityExecutionContext context)
{
return ValueTask.FromResult(true);
}
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
{
context.SetResult("ExecuteAsync-Result");
return ValueTask.CompletedTask;
}
}
- 调用
public static IActivity TestResult()
{
var testStrVariable = new Variable<string>("testStr", "testStr");
var workflow = new Sequence();
workflow.Variables = new List<Variable>()
{
testStrVariable
};
workflow.Activities = new List<IActivity>();
var resultActivity = new TestCodeGenericActivity()
{
Result = new(testStrVariable)
};
workflow.Activities.Add(resultActivity);
workflow.Activities.Add(new WriteLine(context => $"问候语是:{testStrVariable.Get(context)}"));
return workflow;
}
书签
可以实现先暂停任务,等待外部触发逻辑后继续执行
- 创建书签
书签就是在活动中通过context.CreateBookmark();创建书签,并在流程的返回状态中获取书签id。
后续可以通过书签id继续执行流程。
public class BookmarkTest : Activity
{
protected override void Execute(ActivityExecutionContext context)
{
// 创建一个书签。创建的书签将存储在工作流状态中。
context.CreateBookmark();
// 此活动在事件发生之前不会完成。
}
}
- 注册
elsa.AddActivity<BookmarkTest>();
- 调用
var workflow = new Workflow
{
Root = new Sequence
{
Activities =
{
new WriteLine("工作流开始..."),
new BookmarkTest(), // 这将会阻塞后续执行,直到 MyEvent 的书签被恢复。
new WriteLine("事件发生!")
}
}
};
workflow.Name = "Test";
workflow.Id = "testFlowID";
var result = await workflowRunner.RunAsync(workflow);
- 继续执行
调用完成后将书签id、实例id、WorkflowState保存后继续执行。
如果需要更实用的示例,可以看后续的持久化的示例
var instanceID = result.WorkflowExecutionContext.Id;
var workflowState = result.WorkflowState;
var bookmark = workflowState.Bookmarks.Single(); // 获取由 MyEvent 活动创建的书签。
var options = new RunWorkflowOptions();
options.BookmarkId = bookmark.Id;
options.WorkflowInstanceId = instanceID;
// 恢复工作流。
//await workflowRunner.RunAsync(workflow, options);
//内存缓存时可以不传递state,持久化时需要传递否则无法执行标签后续的活动
//await workflowRunner.RunAsync(workflow, options);
await workflowRunner.RunAsync(workflow, workflowState, options);
流程保存成json后加载执行
注入序列化器
IActivitySerializer activitySerializer;
加载并执行到书签后继续执行
var workflowRaw = ElsaUtil.TestBookmark();
workflowRaw.Name = "Test";
workflowRaw.Id = "testFlowID";
var json = activitySerializer.Serialize(workflowRaw);
var workflow = activitySerializer.Deserialize<Workflow>(json);
var result = await workflowRunner.RunAsync(workflow);
var instanceID = result.WorkflowExecutionContext.Id;
var workflowState = result.WorkflowState;
var bookmark = workflowState.Bookmarks.Single();
var options = new RunWorkflowOptions();
options.BookmarkId = bookmark.Id;
options.WorkflowInstanceId = instanceID;
// 恢复工作流。
await workflowRunner.RunAsync(workflow, result.WorkflowState, options);
持久化
使用pg,需要安装两个nuget包【Elsa.EntityFrameworkCore】、【Elsa.EntityFrameworkCore.PostgreSql】
- 不显示ef执行log
builder.Services.AddLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
logging.AddDebug();
// 过滤掉 EF Core 的 SQL 日志
logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning);
logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Connection", LogLevel.Warning);
logging.AddFilter("Microsoft.EntityFrameworkCore.Infrastructure", LogLevel.Warning);
logging.AddFilter("Microsoft.EntityFrameworkCore.Query", LogLevel.Warning);
});
- 注入ef的配置
builder.Services.AddElsa(elsa =>
{
var connectionString = "Host=127.0.0.1;Port=5432;Database=dbName;Username=dev;Password=testPassword;SearchPath=public";
// 配置管理层以使用EF Core。
elsa.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef =>
ef.UsePostgreSql(connectionString)));
elsa.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef =>
ef.UsePostgreSql(connectionString)));
}
);
- 创建流程
var workflow = new Workflow
{
Root = new Sequence
{
Activities =
{
new WriteLine("工作流开始..."),
new BookmarkTest(), // 这将会阻塞后续执行,直到 MyEvent 的书签被恢复。
new WriteLine("事件发生!")
}
}
};
var result = await workflowRunner.RunAsync(workflow);
var bookmarkID = result.WorkflowState.Bookmarks.First().Id;
var instanceID = result.WorkflowExecutionContext.Id;
- 注入实例存储的对象
private readonly IWorkflowInstanceStore _workflowInstanceStore;
- 搜索旧的流程并执行
var workflow = new Workflow
{
Root = new Sequence
{
Activities =
{
new WriteLine("工作流开始..."),
new BookmarkTest(), // 这将会阻塞后续执行,直到 MyEvent 的书签被恢复。
new WriteLine("事件发生!")
}
}
};
//查找流程实例获取状态属性
var filter = new Elsa.Workflows.Management.Filters.WorkflowInstanceFilter();
filter.Id = instanceID;
var workflowInstance = await _workflowInstanceStore.FindAsync(filter);
// 恢复工作流,需要WorkflowState才能正确执行
await workflowRunner.RunAsync(workflow, workflowInstance.WorkflowState, options);
- 备注
如果需要使用FlowChart时恢复流程,可以使用Workflow将flowChart包在内部即可
var workflow = new Flowchart
{
Activities =
{
new WriteLine("工作流开始..."),
new BookmarkTest(), // 这将会阻塞后续执行,直到 MyEvent 的书签被恢复。
new WriteLine("事件发生!")
}
};
var wf = new Workflow
{
Root = workflow
};
var result = await workflowRunner.RunAsync(wf);
var instanceID = result.WorkflowExecutionContext.Id;
var options = new RunWorkflowOptions();
options.WorkflowInstanceId = instanceID;
var filter = new Elsa.Workflows.Management.Filters.WorkflowInstanceFilter();
filter.Id = instanceID;
var workflowInstance = await _workflowInstanceStore.FindAsync(filter);
options.BookmarkId = workflowInstance.WorkflowState.Bookmarks.First().Id;
options.WorkflowInstanceId = instanceID;
await workflowRunner.RunAsync(wf, workflowInstance.WorkflowState, options);
自定义中间件
中间件
public class WorkflowCompletionMiddleware : IWorkflowExecutionMiddleware
{
private readonly WorkflowMiddlewareDelegate _next;
public WorkflowCompletionMiddleware(WorkflowMiddlewareDelegate next)
{
this._next = next;
}
public async ValueTask InvokeAsync(WorkflowExecutionContext context)
{
// 调用下一个中间件
await _next(context);
//记录流程执行结果
if (context.Status == WorkflowStatus.Finished)
{
var vari = context.Workflow.Variables.ToList();
var result = context.Variables.ToList();
//var testStr = context.Variables.GetPropertyValue<string>("testStr");
//通过容器获取依赖注入的类型
//var sugar = context.GetService<ISqlSugarClient>();
}
}
}
注册中间件
//在原有在AddElsa中通过UseWorkflows调用WithWorkflowExecutionPipeline注册
builder.Services.AddElsa(elsa =>
{
elsa.UseWorkflows(workflows =>
{
workflows.WithWorkflowExecutionPipeline(pipeline =>
{
//pipeline.UseMiddleware<WorkflowCompletionMiddleware>();
//pipeline.UseDefaultPipeline();
//重写UseDefaultPipeline
pipeline.Reset()
.UseMiddleware<WorkflowCompletionMiddleware>()
.UseWorkflowHeartbeat()
.UseEngineExceptionHandling()
.UsePersistentVariables()
.UseExceptionHandling()
.UseDefaultActivityScheduler();
});
});
}
);
[参考]
联系我:ivesbao@163.com

浙公网安备 33010602011771号