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
  • 大多数生产场景中,执行要求标准

基础使用

  1. 创建测试流程
public class TestFlow : WorkflowBase
{
    protected override void Build(IWorkflowBuilder builder)
    {
        builder.Root = new WriteLine("Hello flow!");
    }
}
  1. 注册流程
builder.Services.AddElsa(elsa =>
    {
        elsa.AddWorkflow<TestFlow>();
    }
);
  1. 注入IWorkflowRuntime
    根据配置的注入方式在使用的地方注入
IWorkflowRuntime workflowRuntime;
  1. 调用
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返回值

  1. 定义返回值类型(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;
    }
}
  1. 调用
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;
}

书签

可以实现先暂停任务,等待外部触发逻辑后继续执行

  1. 创建书签
    书签就是在活动中通过context.CreateBookmark();创建书签,并在流程的返回状态中获取书签id。
    后续可以通过书签id继续执行流程。
public class BookmarkTest : Activity
{
    protected override void Execute(ActivityExecutionContext context)
    {
        // 创建一个书签。创建的书签将存储在工作流状态中。
        context.CreateBookmark();

        // 此活动在事件发生之前不会完成。
    }
}
  1. 注册
elsa.AddActivity<BookmarkTest>();
  1. 调用
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);
  1. 继续执行
    调用完成后将书签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】

  1. 不显示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);
});
  1. 注入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)));
    }
);
  1. 创建流程
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;
  1. 注入实例存储的对象
private readonly IWorkflowInstanceStore _workflowInstanceStore;
  1. 搜索旧的流程并执行
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);
  1. 备注
    如果需要使用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();

        });
    });
}
);

[参考]

深入Elsa Workflows核心模块:工作流执行引擎

posted @ 2026-01-21 16:59  Hey,Coder!  阅读(32)  评论(0)    收藏  举报