跳到主要内容

GST.Rpa.Web — Blazor 控制台

項目內容
適用對象GST 內部開發人員
前置知識C# / .NET 8 / Blazor Server / SignalR
閱讀時間20 分鐘
最後更新2026-04-04
版本1.0.0

1. 模組職責

GST.Rpa.Web 是 RPA 系統的 Web-based 控制台,以 Blazor Server 建構,提供:

  • Monaco Editor — 嵌入式 C# 腳本編輯器,支援 breakpoint glyph、error marker 及當前行高亮
  • DOT Graph 視覺化 — 透過 D3-graphviz 即時渲染 workflow 狀態機圖形,標記 current / visited 狀態
  • 即時狀態同步 — 經由 SignalR 接收 Engine 的 state change、log、screen capture 及 variable snapshot
  • 偵錯控制 — Pause / Resume / StepOver / Continue / Abort 及 Breakpoint 管理

專案結構:

GST.Rpa.Web/
├── Interop/
│ ├── MonacoInterop.cs ← Monaco Editor JS 互動
│ └── GraphvizInterop.cs ← D3-graphviz DOT 渲染
├── Models/
│ ├── ConsoleLogEntry.cs ← 日誌項目
│ ├── TimelineStep.cs ← 工作流程步驟追蹤
│ └── BreakpointInfo.cs ← 中斷點描述
├── Services/
│ ├── IRpaConsoleStateService.cs ← 核心介面
│ ├── SignalRConsoleStateService.cs ← 正式 SignalR 實作
│ ├── MockConsoleStateService.cs ← 開發用 Mock 實作
│ ├── RpaWebOptions.cs ← 設定選項
│ └── ServiceCollectionExtensions.cs ← DI 註冊
└── Program.cs

2. NuGet 引用與相依

套件版本用途
BlazorMonaco3.3.0Monaco Editor Blazor 封裝
Microsoft.AspNetCore.SignalR.Client8.0.11SignalR Client — 連線 RPA Hub

專案同時引用內部模組:

<ProjectReference Include="..\GST.Rpa.Core\GST.Rpa.Core.csproj" />
<ProjectReference Include="..\GST.Rpa.SignalR\GST.Rpa.SignalR.csproj" />
<ProjectReference Include="..\GST.Rpa.Scripting\GST.Rpa.Scripting.csproj" />
  • GST.Rpa.Core — 提供 RpaEngineState enum 等共用型別
  • GST.Rpa.SignalR — SignalR Hub 定義
  • GST.Rpa.Scripting — C# 腳本引擎

3. 核心 API

3.1 IRpaConsoleStateService

控制台的核心抽象,實作 IAsyncDisposable。所有 Blazor 元件透過此介面取得即時狀態與發送控制指令。

public interface IRpaConsoleStateService : IAsyncDisposable
{
// ── State Properties ──
string? CurrentStateName { get; }
string? DotGraph { get; }
byte[]? ScreenFrame { get; }
RpaEngineState EngineState { get; }
IReadOnlyList<ConsoleLogEntry> LogEntries { get; }
IReadOnlyList<TimelineStep> TimelineSteps { get; }
IReadOnlyDictionary<string, object?> Variables { get; }
IReadOnlySet<int> Breakpoints { get; }

// ── Event ──
event Action? OnChange;

// ── Control Methods ──
Task ExecuteScriptAsync(string code, CancellationToken cancellationToken = default);
Task PauseAsync(CancellationToken cancellationToken = default);
Task ResumeAsync(CancellationToken cancellationToken = default);
Task AbortAsync(CancellationToken cancellationToken = default);
Task SetBreakpointAsync(int lineNumber, CancellationToken cancellationToken = default);
Task RemoveBreakpointAsync(int lineNumber, CancellationToken cancellationToken = default);
Task StepOverAsync(CancellationToken cancellationToken = default);
Task ContinueAsync(CancellationToken cancellationToken = default);
Task ConnectAsync(CancellationToken cancellationToken = default);
}
屬性型別說明
CurrentStateNamestring?目前 State Machine 狀態名稱
DotGraphstring?當前 workflow 的 DOT 語言圖形
ScreenFramebyte[]?最新螢幕截圖 raw bytes
EngineStateRpaEngineState引擎執行狀態(Idle / Running / Paused / Error / Completed)
LogEntriesIReadOnlyList<ConsoleLogEntry>執行期間產生的日誌(最多 500 筆,超過後移除最早的)
TimelineStepsIReadOnlyList<TimelineStep>workflow 各步驟及執行狀態
VariablesIReadOnlyDictionary<string, object?>腳本變數快照
BreakpointsIReadOnlySet<int>已設定的中斷點行號集合

3.2 SignalRConsoleStateService

正式環境的 IRpaConsoleStateService 實作,透過 SignalR HubConnection 與 RPA Engine 即時通訊。

public class SignalRConsoleStateService : IRpaConsoleStateService
{
public SignalRConsoleStateService(RpaWebOptions options, NavigationManager navigation)
// ...
}

連線建立:

ConnectAsync() 使用 RpaWebOptions.SignalRHubUrl 建立 Hub 連線,並啟用 WithAutomaticReconnect()

Hub 事件監聽:

Hub 事件參數處理邏輯
StateChangedpreviousState, currentState, engineState更新 CurrentStateNameEngineState
ScreenUpdatedframe (byte[])更新 ScreenFrame
LogReceivedlevel, message, durationMs新增 ConsoleLogEntry,超過 500 筆移除最舊
DotGraphUpdateddot (string)更新 DotGraph
VariablesUpdatedvars (Dictionary)清空後替換整組變數

控制方法呼叫對應:

方法Hub Invoke
ExecuteScriptAsync(code)"Execute"
PauseAsync()"Pause"
ResumeAsync()"Resume"
AbortAsync()"Abort"
SetBreakpointAsync(line)"SetBreakpoint"
RemoveBreakpointAsync(line)"RemoveBreakpoint"
StepOverAsync()"StepOver"
ContinueAsync()"Continue"

所有控制方法在呼叫前皆檢查 _hub?.State == HubConnectionState.Connected

3.3 MockConsoleStateService

開發與測試用的 Mock 實作,從 wwwroot/mock/{MockScenario}.json 載入預錄情境並以 Timer 按步驟重播。

public class MockConsoleStateService : IRpaConsoleStateService
{
public MockConsoleStateService(RpaWebOptions options)
// ...
}

Mock Scenario 格式:

internal record MockScenario(
string Name,
string DotGraph,
List<MockScenarioStep> Steps);

internal record MockScenarioStep(
string StateName,
int DelayMs,
string? ScreenshotBase64,
MockLogEntry? Log,
Dictionary<string, object?>? Variables);

internal record MockLogEntry(
string Message,
string Level,
int? DurationMs);
  • ExecuteScriptAsync() 載入 scenario 後自動逐步播放
  • PauseAsync() / ResumeAsync() 控制 Timer 暫停與恢復
  • StepOverAsync() 手動前進一步後立即暫停
  • ContinueAsync() 等同 ResumeAsync()
  • ConnectAsync() 為 no-op(無需實際連線)
  • 內部以 lock (_syncRoot) 保護 log / timeline / variables 的 thread safety

3.4 RpaWebOptions

public class RpaWebOptions
{
public bool UseMockEngine { get; set; } = true;
public string MockScenario { get; set; } = "demo-scenario";
public string SignalRHubUrl { get; set; } = "/rpahub";
}
屬性預設值說明
UseMockEnginetruetrue 時注入 MockConsoleStateServicefalse 時注入 SignalRConsoleStateService
MockScenario"demo-scenario"Mock 情境檔名(不含 .json),從 wwwroot/mock/ 載入
SignalRHubUrl"/rpahub"SignalR Hub 端點路徑

3.5 操作流程時序

以「使用者按下 Execute 按鈕」為例,從 Browser 點擊到 UI 即時更新的完整路徑:

時序的三個層次:

  • 同步控制路徑Browser → State → Hub → Worker 是一次性的 RPC;InvokeAsync 完成才算指令送達 Worker
  • 異步事件路徑:執行期間 Worker 持續 SendAsync 推送狀態 / 截圖 / 日誌,State 端的 handler 更新本地狀態並觸發 OnChange
  • UI 渲染路徑:所有訂閱 OnChange 的元件(Editor、Graph、Log、Timeline、Variables)一起 re-render,達成多面板同步

4. JS Interop

4.1 MonacoInterop

封裝 monaco-interop.js,透過 IJSRuntime 呼叫 JavaScript 函式。

public sealed class MonacoInterop(IJSRuntime js)
{
Task SetBreakpointDecorationsAsync(
IJSObjectReference editorInstance, int[] lineNumbers);

Task SetErrorMarkersAsync(
string modelUri, IEnumerable<ScriptErrorMarker> errors);

Task HighlightCurrentLineAsync(
IJSObjectReference editorInstance, int lineNumber);
}
方法JS 函式用途
SetBreakpointDecorationsAsyncmonacoInterop.setBreakpointDecorations在指定行號顯示 breakpoint glyph
SetErrorMarkersAsyncmonacoInterop.setErrorMarkers設定 inline 錯誤標記(紅色波浪線)
HighlightCurrentLineAsyncmonacoInterop.highlightCurrentLine高亮目前執行暫停的行

輔助型別:

public record ScriptErrorMarker(int Line, int Column, string Message);

4.2 GraphvizInterop

封裝 graphviz-interop.js,透過 D3-graphviz 將 DOT 字串渲染為 SVG。實作 IAsyncDisposable 以釋放 JavaScript 端資源。

public sealed class GraphvizInterop(IJSRuntime js) : IAsyncDisposable
{
Task RenderDotAsync(string elementId, string dotString);

Task HighlightStatesAsync(
string elementId, string? currentState, IEnumerable<string> visitedStates);

ValueTask DisposeAsync();
}
方法JS 函式用途
RenderDotAsyncgraphvizInterop.renderDot將 DOT 渲染至指定 DOM 元素
HighlightStatesAsyncgraphvizInterop.highlightStates標記 current state 及已走過的 visited states
DisposeAsyncgraphvizInterop.dispose釋放渲染資源

5. 資料模型

ConsoleLogEntry

public record ConsoleLogEntry(
DateTime Timestamp,
string Level,
string Message,
int? DurationMs = null);
欄位說明
Timestamp日誌產生時間
Level嚴重等級:InfoWarningError
Message人類可讀的日誌訊息
DurationMs計時操作的耗時毫秒數(選填)

TimelineStep

public record TimelineStep(
string StateName,
TimelineStepStatus Status,
TimeSpan? Duration = null);

public enum TimelineStepStatus
{
Pending, // 尚未到達
Current, // 正在執行
Completed, // 執行完成
Failed // 執行失敗
}
欄位說明
StateNameState Machine 狀態顯示名稱
Status步驟執行狀態
Duration已完成步驟的實際耗時(選填)

BreakpointInfo

public record BreakpointInfo(int LineNumber, bool IsHit = false);
欄位說明
LineNumber1-based 行號
IsHit引擎目前是否暫停在此中斷點

6. State Container Pattern

IRpaConsoleStateService 同時扮演 State Container 的角色,採用 Blazor 推薦的共享狀態模式:

┌─────────────┐     OnChange event      ┌─────────────┐
│ Component A │ ◄──────────────────────► │ State │
│ (Editor) │ │ Service │
└─────────────┘ │ │
┌─────────────┐ OnChange event │ (IRpaConsole │
│ Component B │ ◄──────────────────────► │ StateService│
│ (Graph) │ │ ) │
└─────────────┘ └──────┬───────┘
┌─────────────┐ OnChange event │
│ Component C │ ◄─────────────────────────────┘
│ (Log Panel) │
└─────────────┘

運作機制:

  1. IRpaConsoleStateServiceScoped 生命週期注入(每個 Blazor circuit 一個實例)
  2. 各元件在 OnInitialized 中訂閱 OnChange event
  3. 當狀態變更時(例如 SignalR 收到 StateChanged),Service 呼叫 NotifyChange()
  4. 所有已訂閱的元件收到通知後呼叫 StateHasChanged() 觸發 re-render

元件端典型用法:

@inject IRpaConsoleStateService Console
@implements IDisposable

@code {
protected override void OnInitialized()
{
Console.OnChange += StateHasChanged;
}

public void Dispose()
{
Console.OnChange -= StateHasChanged;
}
}

此模式確保所有面板(Editor、Graph、Log、Timeline、Variables)保持同步,而不需元件間直接耦合。


7. 使用範例

DI 註冊(Mock 模式)

builder.Services.AddRpaWeb(options =>
{
options.UseMockEngine = true;
options.MockScenario = "DemoErp";
});

DI 註冊(正式模式)

builder.Services.AddRpaWeb(options =>
{
options.UseMockEngine = false;
options.SignalRHubUrl = "/rpahub";
});

ServiceCollectionExtensions 內部邏輯

public static IServiceCollection AddRpaWeb(
this IServiceCollection services,
Action<RpaWebOptions>? configure = null)
{
var options = new RpaWebOptions();
configure?.Invoke(options);
services.AddSingleton(options);

if (options.UseMockEngine)
services.AddScoped<IRpaConsoleStateService, MockConsoleStateService>();
else
services.AddScoped<IRpaConsoleStateService, SignalRConsoleStateService>();

return services;
}

元件注入 State Service

@page "/console"
@inject IRpaConsoleStateService Console

<h3>Engine: @Console.EngineState</h3>
<p>Current State: @Console.CurrentStateName</p>

<button @onclick="RunAsync">Execute</button>
<button @onclick="() => Console.PauseAsync()">Pause</button>
<button @onclick="() => Console.ResumeAsync()">Resume</button>
<button @onclick="() => Console.AbortAsync()">Abort</button>

@code {
protected override async Task OnInitializedAsync()
{
Console.OnChange += StateHasChanged;
await Console.ConnectAsync();
}

private async Task RunAsync()
{
var script = "/* 從 Monaco Editor 取得 */";
await Console.ExecuteScriptAsync(script);
}

public void Dispose()
{
Console.OnChange -= StateHasChanged;
}
}

8. 架構決策

為什麼選 Blazor Server?

考量說明
即時性Blazor Server 透過 SignalR 維持永久連線,天然適合接收 Engine 的即時推播
安全性所有 C# 邏輯在 Server 端執行,敏感的 RPA 腳本不會暴露到 Client
無 WASM 限制不受 WebAssembly sandbox 限制,可直接參照 Core / Scripting 等內部模組
部署簡易單一 ASP.NET Core 程序,開發者本機即可啟動

Mock vs Real 雙模式設計

透過 RpaWebOptions.UseMockEngine 切換注入的實作:

  • 開發階段 — 使用 MockConsoleStateService 重播 JSON scenario,不需實際 RPA Engine 運行
  • 整合測試 — 設計不同 scenario JSON 模擬 error / pause / breakpoint hit 等邊界情境
  • 正式環境 — 使用 SignalRConsoleStateService 連線實際 Engine

兩個實作共用同一 IRpaConsoleStateService 介面,元件端完全不需知道底層是 mock 還是 real。

JS Interop 策略

元件Interop 類別對應 JS 模組原因
Monaco EditorMonacoInteropmonaco-interop.jsMonaco Editor 僅有 JS API,必須透過 Interop 操作 decoration / marker
GraphvizGraphvizInteropgraphviz-interop.jsD3-graphviz 為 JS library,DOT → SVG 渲染只能在瀏覽器端完成

Interop 類別統一使用 Primary Constructor 注入 IJSRuntime,並將 JS function name 硬編碼為字串常數,確保 C# 端與 JS 端的對應關係清晰可查。GraphvizInterop 實作 IAsyncDisposable 以確保在元件銷毀時釋放 JS 端分配的資源。


9. 版本紀錄

版本日期變更
1.02026-04-04初版