在本快速入門中,我們將介紹一個用於設定Elsa Server的最小ASP.NET Core應用程式。我們還將安裝一些更常用的activities(活動),如Timer、Cron和sendmail,以能夠實現簡單的重複工作流。
此應用程式的目的是作為工作流服務。這意味著它將承載和執行工作流,但不會承載dashboard(儀表板) UI。當然,也可以將工作流主機與承載dashboard UI的web應用程式相結合,將在隨後的文章中展示。 |
我們將:
- 建立ASP.NET Core應用程式。
- 使用EF Core和SQLite提供配置永續性。
- 註冊各種activities (活動)以在工作流中使用。
- 使用workflow Builder API建立簡單的工作流。
- 公開Elsa API Endpoints供外部應用程式(包括Elsa Dashboard)使用。
- 使用Postman驗證Elsa API Endpoints。
專案:
建立一個新的空ASP.NET核心專案,名為ElsaQuickstarts.Server.apidemps:
dotnet new web -n "ElsaQuickstarts.Server.ApiEndpoints"
CD 到建立的專案資料夾中:
cd ElsaQuickstarts.Server.ApiEndpoints
cd ElsaQuickstarts.Server.ApiEndpoints
新增以下包:
dotnet add package Elsa
dotnet add package Elsa.Activities.Http
dotnet add package Elsa.Activities.Temporal.Quartz
dotnet add package Elsa.Persistence.EntityFramework.Sqlite
dotnet add package Elsa.Server.Api
Heartbeat(心跳)工作流
僅出於演示目的,我們將建立一個簡單的“心跳”工作流,它會定期將當前時間寫入標準。這將展示以下內容:
- Elsa 支援程式化和動態工作流(例如,使用 Elsa Dashboard 視覺化建立)。
- 如果我們有 Elsa Dashboard 設定,我們可以直觀地檢視這個 Heartbeat 工作流程,即使我們要在這以程式設計方式建立它。
- 我們可以使用 Postman 與 Elsa API endpoints互動並查詢 Heartbeat 工作流生成的工作流例項。
繼續建立一個名為 HeartbeatWorkflow.cs 的新檔案並新增以下程式碼:
using Elsa.Activities.Console;
using Elsa.Activities.Temporal;
using Elsa.Builders;
using NodaTime;
namespace ElsaQuickstarts.Server.ApiEndpoints
{
public class HeartbeatWorkflow : IWorkflow
{
private readonly IClock _clock;
public HeartbeatWorkflow(IClock clock) => _clock = clock;
public void Build(IWorkflowBuilder builder) =>
builder
.Timer(Duration.FromSeconds(10))
.WriteLine(context => $"Heartbeat at {_clock.GetCurrentInstant()}");
}
}
上述工作流有兩個活動。第一個活動 Timer 將導致此工作流每 10 秒執行一次。第二個活動 WriteLine 將當前時間寫入標準輸出。請注意它採用字串委託的過載。這會允許在執行時提供動態的屬性值。類似與在Elsa 1工作流中使用 JavaScript 和 Liquid 表示式。當然,不同之處在於您現在可以在使用 Workflow Builder API 編寫工作流時使用普通的舊 C# 語法。
另請注意,HeartbeatWorkflow 類可以接受建構函式注入的服務,就像向 DI 系統註冊的任何其他型別一樣。
IClock :它是由 NodaTime 提供的抽象,它是 .NET 的替代日期和時間的 API。 |
Startup
接下來,開啟 Startup.cs 並將其內容替換為以下內容:
using Elsa;
using Elsa.Persistence.EntityFramework.Core.Extensions;
using Elsa.Persistence.EntityFramework.Sqlite;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ElsaQuickstarts.Server.ApiEndpoints
{
public class Startup
{
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
private IWebHostEnvironment Environment { get; }
private IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var elsaSection = Configuration.GetSection("Elsa");
// Elsa services.
services
.AddElsa(elsa => elsa
.UseEntityFrameworkPersistence(ef => ef.UseSqlite())
.AddConsoleActivities()
.AddHttpActivities(elsaSection.GetSection("Server").Bind)
.AddQuartzTemporalActivities()
.AddJavaScriptActivities()
.AddWorkflowsFrom<Startup>()
);
// Elsa API endpoints.
services.AddElsaApiEndpoints();
// Allow arbitrary client browser apps to access the API.
// In a production environment, make sure to allow only origins you trust.
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy
.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
.WithExposedHeaders("Content-Disposition"))
);
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app
.UseCors()
.UseHttpActivities()
.UseRouting()
.UseEndpoints(endpoints =>
{
// Elsa API Endpoints are implemented as regular ASP.NET Core API controllers.
endpoints.MapControllers();
});
}
}
}
使用 Entity Framework Core 時,Elsa 將預設使用合併資料庫上下文,並將自動為您執行遷移。如果您不想使用池化資料庫上下文,請改用 UseNonPooledEntityFrameworkPersistence 方法。如果您更喜歡自己執行遷移,請確保在使用 UseEntityFrameworkPersistence 方法時傳遞 autoRunMigrations: false (它是一個預設設定為 true 的可選引數)。 |
請注意,我們正在訪問名為“Elsa”的配置部分。然後我們使用此部分來檢索名為“Http”和“Smtp”的子部分。接下來讓我們用這些部分更新 appsettings.json:
Appsettings.json
開啟 appsettings.json 並新增以下部分:
{
"Elsa": {
"Http": {
"BaseUrl": "https://localhost:5001"
}
}
}
我們設定“BaseUrl”的原因是 HTTP 活動庫提供了一個絕對 URL 提供程式,可供活動和工作流表示式使用。由於這個絕對 URL 提供者可以在實際 HTTP 請求的上下文之外使用(例如,當一個計時器事件發生時),我們不能依賴例如IHttpContextAccessor,因為不會有任何 HTTP 上下文。
執行
執行程式並等待,直到看到以下輸出:
Now listening on: http://localhost:5000
Now listening on: https://localhost:5001
Application started. Press Ctrl+C to shut down.
等待大約 10 秒後,可以看到以下輸出:
info: Elsa.Bookmarks.BookmarkIndexer[0]
Indexed 0 bookmarks in 00:00:00.0077348
Heartbeat at 2021-05-07T19:43:47Z
info: Elsa.Bookmarks.BookmarkIndexer[0]
Indexing bookmarks
Postman
啟動 Postman 或任何其他HTTP 請求的工具。接下來將嘗試一些公開的 API。
列出工作流藍圖
首先,讓我們查詢工作流注冊表:
GET /v1/workflow-registry
Host: localhost:5001
JSON 響應將包含所有已註冊工作流的“summary(摘要)”檢視(目前只有一個):
{
"items": [
{
"id": "HeartbeatWorkflow",
"name": "HeartbeatWorkflow",
"displayName": "HeartbeatWorkflow",
"description": null,
"version": 1,
"tenantId": null,
"isSingleton": false,
"isEnabled": false,
"isPublished": true,
"isLatest": true
}
],
"page": null,
"pageSize": null,
"totalCount": 1
}
獲取單一工作流藍圖
要獲取完整的工作流藍圖定義,請發出以下 HTTP 請求:
GET /v1/workflow-registry/HeartbeatWorkflow
Host: localhost:5001
響應將包括更多詳細資訊,包括activities (活動)和connections(聯絡):
{
"$id": "1",
"version": 1,
"isSingleton": false,
"isEnabled": false,
"isPublished": true,
"isLatest": true,
"variables": {
"$id": "2",
"data": {}
},
"persistenceBehavior": "WorkflowBurst",
"deleteCompletedInstances": false,
"customAttributes": {
"$id": "3",
"data": {}
},
"activities": [
{
"$id": "4",
"id": "activity-1",
"type": "Timer",
"parentId": "HeartbeatWorkflow",
"persistWorkflow": false,
"loadWorkflowContext": false,
"saveWorkflowContext": false,
"properties": {
"$id": "5",
"data": {
"Timeout": "0:00:10"
}
}
},
{
"$id": "6",
"id": "activity-2",
"type": "WriteLine",
"parentId": "HeartbeatWorkflow",
"persistWorkflow": false,
"loadWorkflowContext": false,
"saveWorkflowContext": false,
"properties": {
"$id": "7",
"data": {
"Text": "Heartbeat at 2021-05-07T19:58:22Z"
}
}
}
],
"connections": [
{
"$id": "8",
"sourceActivityId": "activity-1",
"targetActivityId": "activity-2",
"outcome": "Done"
}
],
"id": "HeartbeatWorkflow",
"name": "HeartbeatWorkflow",
"displayName": "HeartbeatWorkflow",
"type": "HeartbeatWorkflow",
"parentId": "HeartbeatWorkflow",
"persistWorkflow": true,
"loadWorkflowContext": false,
"saveWorkflowContext": false,
"properties": {
"$id": "9",
"data": {}
}
}
列出工作流例項
隨著 HeartbeatWorkflow 的執行,每 10 秒將建立一個新的工作流例項。要獲取工作流例項列表,請發出以下請求:
GET /v1/workflow-instances?workflow=HeartbeatWorkflow&page=0&pageSize=2
Host: localhost:5001
響應應類似於以下內容:
{
"items": [
{
"id": "e380d0a7fd4a4b6ba236fbdc0adf0ddb",
"definitionId": "HeartbeatWorkflow",
"tenantId": null,
"version": 1,
"workflowStatus": "Finished",
"correlationId": null,
"contextType": null,
"contextId": null,
"name": null,
"createdAt": "2021-05-07T19:43:46.4198083Z",
"lastExecutedAt": "2021-05-07T19:43:47.4602325Z",
"finishedAt": "2021-05-07T19:43:47.4626325Z",
"cancelledAt": null,
"faultedAt": null
},
{
"id": "418d0b535a89413e9ca2014a3b476b93",
"definitionId": "HeartbeatWorkflow",
"tenantId": null,
"version": 1,
"workflowStatus": "Finished",
"correlationId": null,
"contextType": null,
"contextId": null,
"name": null,
"createdAt": "2021-05-07T19:43:56.175008Z",
"lastExecutedAt": "2021-05-07T19:43:56.3284055Z",
"finishedAt": "2021-05-07T19:43:56.3285439Z",
"cancelledAt": null,
"faultedAt": null
}
],
"page": 0,
"pageSize": 2,
"totalCount": 110
}
列出活動
要獲取可用活動的完整列表,您可以發出以下 HTTP 請求:
GET /v1/activities
Host: localhost:5001
以下是部分響應,讓您瞭解它的樣子
[
{
"type": "ReadLine",
"displayName": "Read Line",
"description": "Read text from standard in.",
"category": "Console",
"traits": 1,
"outcomes": [
"Done"
],
"properties": []
},
{
"type": "WriteLine",
"displayName": "Write Line",
"description": "Write text to standard out.",
"category": "Console",
"traits": 1,
"outcomes": [
"Done"
],
"properties": [
{
"name": "Text",
"type": "System.String",
"uiHint": "single-line",
"label": "Text",
"hint": "The text to write.",
"supportedSyntaxes": [
"JavaScript",
"Liquid"
]
}
]
},
{
"type": "HttpEndpoint",
"displayName": "HTTP Endpoint",
"description": "Handle an incoming HTTP request.",
"category": "HTTP",
"traits": 2,
"outcomes": [
"Done"
],
"properties": [
{
"name": "Path",
"type": "Microsoft.AspNetCore.Http.PathString",
"uiHint": "single-line",
"label": "Path",
"hint": "The relative path that triggers this activity.",
"supportedSyntaxes": [
"JavaScript",
"Liquid"
]
},
{
"name": "Methods",
"type": "System.Collections.Generic.HashSet`1[System.String]",
"uiHint": "check-list",
"label": "Methods",
"hint": "The HTTP methods that trigger this activity.",
"options": [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"OPTIONS",
"HEAD"
],
"defaultValue": [
"GET"
],
"defaultSyntax": "Json",
"supportedSyntaxes": [
"Json",
"JavaScript",
"Liquid"
]
},
{
"name": "ReadContent",
"type": "System.Boolean",
"uiHint": "checkbox",
"label": "Read Content",
"hint": "A value indicating whether the HTTP request content body should be read and stored as part of the HTTP request model. The stored format depends on the content-type header.",
"supportedSyntaxes": [
"Literal",
"JavaScript",
"Liquid"
]
},
{
"name": "TargetType",
"type": "System.Type",
"uiHint": "single-line",
"label": "Target Type",
"category": "Advanced",
"supportedSyntaxes": []
}
]
}
]: