elsa HTTP 工作流
让我们来看一个能接收 HTTP
请求、发送 HTTP
请求并将输出写入 HTTP
响应对象的工作流。
我们将依赖于自带示例控制器WeatherForecastController的 webapi
项目模板。
我们的工作流将处理传入的 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();
})
}
}
};
}
}
CanStartWorkflow
请注意,我们设置了 HttpEndpoint
活动的 CanStartWorkflow
属性为 true
。这是向工作流运行时发出的一个信号,表示要从此活动中提取触发器。
如果不设置此属性,工作流将不会自动响应入站 HTTP 请求而触发。
为了将工作流注册到工作流运行时,请返回 Program.cs
并更新 Elsa
配置代码,添加以下行:
elsa.AddWorkflow<WeatherForecastWorkflow>();
重启应用,这次导航到 https://localhost:5001/workflows/weatherforecast
。
结果应该类似于这样:
从设计器创建工作流
除了在代码中创建工作流之外,还可以使用 Elsa Studio
,这是一个允许您创建和管理工作流的 Web
应用程序。
要设置一个托管 Elsa Studio
的 ASP.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
文件:
发布该工作流,然后导航到 https://localhost:5001/workflows/weatherforecast-from-designer
。
你应该会看到和之前一样的结果。
你可以在此处找到本指南最终的源代码:这里。
结论
在这篇指南中,我们学习了如何创建能够处理入站 HTTP
请求、发送 HTTP
请求以及向 HTTP
响应对象写入输出的工作流。