elsa HTTP 工作流

让我们来看一个能接收 HTTP 请求、发送 HTTP 请求并将输出写入 HTTP 响应对象的工作流。

我们将依赖于自带示例控制器WeatherForecastControllerwebapi 项目模板。

我们的工作流将处理传入的 HTTP 请求,通过 HTTP 调用调用天气预报 API,并将响应写回 HTTP 响应中。

因此,我们将学习如何使用以下 HTTP 活动:

  • HttpEndpoint(HTTP 端点)
  • SendHttpRequest(发送 HTTP 请求)
  • WriteHttpResponse(写入 HTTP 响应)

项目设置

要创建项目,请运行以下命令:

dotnet new webapi -n WorkflowApp.Web --no-openapi -f net8.0

运行项目

运行以下命令以进入创建的项目目录并运行项目:

cd WorkflowApp.Web
dotnet run --urls=https://localhost:5001

要调用天气预报控制器,请导航到 https://localhost:5001/weatherforecast ,这应该产生如下所示的输出(为了清晰起见已简化):

[
  {
    "date": "2023-01-20",
    "temperatureC": 54,
    "temperatureF": 129,
    "summary": "炎热"
  },
  {
    "date": "2023-01-21",
    "temperatureC": 48,
    "temperatureF": 118,
    "summary": "温和"
  }
]

Elsa 集成

接下来,我们安装并配置 Elsa

安装以下包:

dotnet add package Elsa
dotnet add package Elsa.EntityFrameworkCore.Sqlite
dotnet add package Elsa.Http
dotnet add package Elsa.Identity
dotnet add package Elsa.Liquid
dotnet add package Elsa.Workflows.Api

Program.cs

更新 Program.cs 文件,添加以下代码:

using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.Extensions;
using WorkflowApp.Web.Workflows;

var builder = WebApplication.CreateBuilder(args);

// 向容器中添加服务。
builder.Services.AddControllers();
builder.Services.AddRazorPages();

// 添加Elsa服务。
builder.Services.AddElsa(elsa =>
{
    elsa.UseIdentity(identity =>
    {
        identity.UseAdminUserProvider();
        identity.TokenOptions = tokenOptions => tokenOptions.SigningKey = "my-long-256-bit-secret-token-signing-key";
    });
    elsa.UseDefaultAuthentication();
    elsa.UseWorkflowManagement(management => management.UseEntityFrameworkCore());
    elsa.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore());
    elsa.UseJavaScript();
    elsa.UseLiquid();
    elsa.UseWorkflowsApi();
    elsa.UseHttp(http => http.ConfigureHttpOptions = options =>
    {
        options.BaseUrl = new Uri("https://localhost:5001");
        options.BasePath = "/workflows";
    });
    elsa.AddWorkflow<WeatherForecastWorkflow>();
});

var app = builder.Build();

// 配置HTTP请求管道。
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseWorkflowsApi();
app.UseWorkflows();
app.MapControllers();
app.MapRazorPages();

// 创建一个示例天气预报API。
var summaries = new[]
{
    "冰冻", "寒冷", "凉爽", "舒适", "温暖", "温和", "宜人", "热", "酷热", "灼热"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        )).ToArray();
    return forecast;
});

// 运行应用程序。
app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

路径前缀

默认情况下,HTTP 工作流路径前缀为 /workflows 。这是为了优化,防止每一个传入的 HTTP 请求都通过伴随的中间件组件来调用工作流。

你可以将前缀更改为其他内容。例如,以下代码将前缀更改为 "/wf"

elsa.UseHttp(http => http.ConfigureHttpOptions = options => options.BasePath = "/wf");

要完全移除前缀,提供一个空字符串即可:

elsa.UseHttp(http => http.ConfigureHttpOptions = options => options.BasePath = "");

从前述提及,这会导致所有入站 HTTP 请求与工作流匹配,可能潜在地降低应用的整体性能,因此请谨慎使用。

从代码创建工作流

我们将要创建的工作流能够执行以下操作:

  • 处理入站 HTTP 请求
  • WeatherForecast API 端点发送 HTTP 请求。
  • 将天气预报结果写回到 HTTP 响应中。

让我们看看如何通过代码创建这个工作流。

创建一个名为 Workflows 的新文件夹,并添加以下类:

Workflows/WeatherForecastWorkflow.cs

using System.Net;
using System.Net.Mime;
using System.Text;
using Elsa.Http;
using Elsa.Workflows.Core;
using Elsa.Workflows.Core.Activities;
using Elsa.Workflows.Core.Contracts;

namespace WorkflowApp.Web.Workflows;

public class WeatherForecastWorkflow : WorkflowBase
{
    protected override void Build(IWorkflowBuilder builder)
    {
        var serverAddress = new Uri(Environment.GetEnvironmentVariable("ASPNETCORE_URLS")!.Split(';')[0]);
        var weatherForecastApiUrl = new Uri(serverAddress, "weatherforecast");
        var weatherForecastResponseVariable = builder.WithVariable<ICollection<WeatherForecast>>();

        builder.Root = new Sequence
        {
            Activities =
            {
                // 将此工作流作为一个HTTP端点公开。
                new HttpEndpoint
                {
                    Path = new("/weatherforecast"),
                    SupportedMethods = new(new[] { HttpMethods.Get }),
                    CanStartWorkflow = true
                },

                // 调用另一个API端点。可以是远程服务器,但这里我们调用的是同一个应用中托管的API。
                new SendHttpRequest
                {
                    Url = new(weatherForecastApiUrl),
                    Method = new(HttpMethods.Get),
                    ParsedContent = new(weatherForecastResponseVariable)
                },

                // 写回天气预报。
                new WriteHttpResponse
                {
                    ContentType = new(MediaTypeNames.Text.Html),
                    StatusCode = new(HttpStatusCode.OK),
                    Content = new(context =>
                    {
                        var weatherForecasts = weatherForecastResponseVariable.Get(context)!;
                        var sb = new StringBuilder();

                        sb.AppendLine(
                            """
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
    <div class="px-4 sm:px-6 lg:px-8">
    <div class="mt-8 flex flex-col">
    <div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
      <div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
        <div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
          <table class="min-w-full divide-y divide-gray-300">
            <thead class="bg-gray-50">
              <tr>
                <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">日期</th>
                <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">温度 (C/F)</th>
                <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">概要</th>
              </tr>
            </thead>
            <tbody class="divide-y divide-gray-200 bg-white">
""");
                        foreach (var weatherForecast in weatherForecasts)
                        {
                            sb.AppendLine("<tr>");
                            sb.AppendLine($"""<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">{weatherForecast.Date}</td>""");
                            sb.AppendLine($"""<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{weatherForecast.TemperatureC}/{weatherForecast.TemperatureF}</td>""");
                            sb.AppendLine($"""<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{weatherForecast.Summary}</td>""");
                            sb.AppendLine("</tr>");
                        }

                        sb.AppendLine(
"""
            </tbody>
        </table>
    </div>
    </div>
    </div>
    </div>
</body>
</html>
""");
                        return sb.ToString();
                    })
                }
            }
        };
    }
}

为了将工作流注册到工作流运行时,请返回 Program.cs 并更新 Elsa 配置代码,添加以下行:

elsa.AddWorkflow<WeatherForecastWorkflow>();

重启应用,这次导航到 https://localhost:5001/workflows/weatherforecast

结果应该类似于这样:

天气预报响应

从设计器创建工作流

除了在代码中创建工作流之外,还可以使用 Elsa Studio,这是一个允许您创建和管理工作流的 Web 应用程序。 要设置一个托管 Elsa StudioASP.NET 应用,请遵循 此处的说明,或者按照 此处所述直接从 Docker 容器运行设计器。

将 Elsa Studio 连接到 Elsa 服务器

确保您的 Elsa Studio 应用配置为指向本指南中创建的 Elsa 服务器 URL。例如:https://localhost:5001/elsa/api。

Liquid

Elsa Studio 中设计工作流时,您可以使用 Liquid 表达式语言动态生成值。这对于编写 HTML 响应特别有用。因为我们想在 HTML 响应中使用 WeatherForecast 模型,所以我们需要将其注册到 Liquid 引擎中。

当我们将 WeatherForecast 注册为工作流变量类型时,Elsa 会为我们完成这项工作。

为此,请更新 Program.cs 中的工作流管理设置,添加以下行:

management.AddVariableType<WeatherForecast>(category: "天气");

Elsa Studio 正在运行时,创建一个新的工作流并导入以下 JSON 文件:

weatherforecast-workflow.json

发布该工作流,然后导航到 https://localhost:5001/workflows/weatherforecast-from-designer

你应该会看到和之前一样的结果。

你可以在此处找到本指南最终的源代码:这里

结论

在这篇指南中,我们学习了如何创建能够处理入站 HTTP 请求、发送 HTTP 请求以及向 HTTP 响应对象写入输出的工作流。

在本文档中