elsa core—3.elsa 服務

中陽裡士發表於2021-09-03

在本快速入門中,我們將介紹一個用於設定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": []
            }
        ]
    }
]:

 

相關文章