首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

Windows Workflow Foundation 進階教學:第十七章 - 關聯與本機通訊 (上)

     編輯:關於.NET

本章學習目標

  • 理解工作流關聯 (Correlation) 的概念、適用情境與重要性
  • 使用關聯參數 (CorrelationParameter) 控制資料流向
  • 建立並使用支援關聯的本機通訊服務

從一對一到一對多:為何需要關聯?

前幾章的範例皆為單一宿主應用程式對應單一工作流實例 (一對一),資料交換自然不會混淆。但實務上常見單一應用程式同時啟動多個同類型工作流實例 (一對多),例如同時處理多筆訂單、追蹤多輛卡車。此時宿主必須明確指定要與「哪一個」工作流實例通訊,而工作流本身只屬於單一宿主,無需識別宿主身分。

WF 透過 關聯權杖 解決此問題:執行階段建立儲存容器記錄工作流實例與資料的對應關係,通訊雙方需出示相同的關聯權杖才能進行資料交換,否則拋出 EventDeliveryFailedException。

關聯核心屬性

屬性用途
CorrelationParameterAttribute標示介面方法/事件中哪個參數攜帶關聯識別碼 (如 truckID)。建構子接收參數名稱字串。
CorrelationInitializerAttribute標示初始化關聯的方法或事件。必須先執行帶有此屬性的方法/事件,後續通訊才合法。
CorrelationAliasAttribute當事件參數 (EventArgs) 而非直接參數攜帶關聯識別碼時,指定別名路徑 (如 e.TruckID)。

重要規則: 單一工作流實例的關聯識別碼在其生命週期內不可變更。若嘗試用不同識別碼與同一實例通訊,將拋出例外。因此識別碼必須在啟動工作流前就已確定 (如預先產生訂單號),不可依賴資料庫自動產生或稍後指派。

建立關聯通訊介面 (ITruckService)

本章範例模擬貨運公司追蹤卡車 GPS 位置。每輛卡車對應一個工作流實例,以 truckID 為關聯鍵。

介面完整程式碼

[CorrelationParameter("truckID")]
[ExternalDataExchange]
public interface ITruckService
{
    // Workflow → Host
    [CorrelationInitializer]
    void ReadyTruck(Int32 truckID, Int32 startingX, Int32 startingY);
    void UpdateTruck(Int32 truckID, Int32 X, Int32 Y);
    void RemoveTruck(Int32 truckID);

    // Host → Workflow
    [CorrelationAlias("truckID", "e.TruckID")]
    event EventHandler<CancelTruckEventArgs> CancelTruck;

    [CorrelationInitializer]
    [CorrelationAlias("truckID", "e.TruckID")]
    event EventHandler<AddTruckEventArgs> AddTruck;
}

關鍵點:

  • 所有方法/事件皆含 truckID 參數或屬性。
  • ReadyTruck (工作流主動通知宿主) 與 AddTruck (宿主啟動卡車) 皆標記 CorrelationInitializer,任一執行即建立關聯。
  • 事件使用 CorrelationAlias 將關聯參數對應至 EventArgs.TruckID。

實作關聯資料連接器 (TruckServiceDataConnector)

繼承 ITruckService,在執行階段以單例模式運作,負責以 (InstanceID, TruckID) 為鍵管理各工作流實例的關聯資料。

核心資料結構

protected const string KeyFormat = "{0}.Truck_{1}";
protected static Dictionary<string, string> _dataValues = new Dictionary<string, string>();
protected static Dictionary<string, WorkflowTruckTrackingDataService> _dataServices = new Dictionary<string, WorkflowTruckTrackingDataService>();
private static object _syncLock = new object();

服務註冊與查詢


public static WorkflowTruckTrackingDataService GetRegisteredWorkflowDataService(Guid instanceID, Int32 truckID)
{
    string key = String.Format(KeyFormat, instanceID, truckID);
    return _dataServices.ContainsKey(key) ? _dataServices[key] : null;
}

public static void RegisterDataService(WorkflowTruckTrackingDataService dataService)
{
    string key = String.Format(KeyFormat, dataService.InstanceID.ToString(), dataService.TruckID);
    lock (_syncLock) _dataServices.Add(key, dataService);
}

資料存取 (XML 序列化)

因 WF 在工作流與宿主間傳遞物件參考而非副本,為避免雙邊修改同一物件導致競爭條件,連接器將資料序列化為 XML 字串儲存。


public string RetrieveTruckInfo(Guid instanceID, Int32 truckID)
{
    string key = String.Format(KeyFormat, instanceID, truckID);
    return _dataValues.ContainsKey(key) ? _dataValues[key] : String.Empty;
}

protected Truck UpdateTruckData(Guid instanceID, Int32 truckID, Int32 X, Int32 Y)
{
    string key = String.Format(KeyFormat, instanceID, truckID);
    Truck truck = _dataValues.ContainsKey(key)
        ? Deserialize(_dataValues[key])
        : new Truck { ID = truckID };
    truck.X = X; truck.Y = Y;
    _dataValues[key] = Serialize(truck);
    return truck;
}

介面方法實作 (觸發服務事件)


public void ReadyTruck(Int32 truckID, Int32 startingX, Int32 startingY)
{
    var svc = GetRegisteredWorkflowDataService(WorkflowEnvironment.WorkflowInstanceId, truckID);
    UpdateTruckData(svc.InstanceID, truckID, startingX, startingY);
    svc?.RaiseTruckLeavingEvent(truckID, startingX, startingY);
}

public void UpdateTruck(Int32 truckID, Int32 X, Int32 Y)
{
    var svc = GetRegisteredWorkflowDataService(WorkflowEnvironment.WorkflowInstanceId, truckID);
    UpdateTruckData(svc.InstanceID, truckID, X, Y);
    svc?.RaiseRouteUpdatedEvent(truckID, X, Y);
}

public void RemoveTruck(Int32 truckID)
{
    var svc = GetRegisteredWorkflowDataService(WorkflowEnvironment.WorkflowInstanceId, truckID);
    string key = String.Format(KeyFormat, svc.InstanceID, truckID);
    if (_dataValues.ContainsKey(key)) _dataValues.Remove(key);
    svc?.RaiseTruckArrivedEvent(truckID);
}

宿主向工作流事件


public event EventHandler<CancelTruckEventArgs> CancelTruck;
public void RaiseCancelTruck(Guid instanceID, Int32 truckID)
    => CancelTruck?.Invoke(null, new CancelTruckEventArgs(instanceID, truckID));

public event EventHandler<AddTruckEventArgs> AddTruck;
public void RaiseAddTruck(Guid instanceID, Int32 truckID, Int32 routeID)
    => AddTruck?.Invoke(null, new AddTruckEventArgs(instanceID, truckID, routeID));

建立關聯資料服務

WorkflowTruckTrackingDataService 封裝 ExternalDataExchangeService 與連接器註冊邏輯,提供事件引發方法供連接器呼叫,並保存 InstanceID、TruckID 識別自身。

靜態工廠方法


public static WorkflowTruckTrackingDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime, Int32 truckID)
{
    lock (_syncRoot)
    {
        if (_workflowRuntime == null) _workflowRuntime = workflowRuntime;
        if (_dataExchangeService == null)
        {
            _dataExchangeService = new ExternalDataExchangeService();
            _workflowRuntime.AddService(_dataExchangeService);
        }
        var connector = (TruckServiceDataConnector)workflowRuntime.GetService(typeof(TruckServiceDataConnector));
        if (connector == null)
        {
            _dataConnector = new TruckServiceDataConnector();
            _dataExchangeService.AddService(_dataConnector);
        }
        else _dataConnector = connector;
        return GetRegisteredWorkflowDataService(instanceID, truckID);
    }
}

建構子與清理


private WorkflowTruckTrackingDataService(Guid instanceID, Int32 truckID)
{
    _instanceID = instanceID; _truckID = truckID;
}
~WorkflowTruckTrackingDataService()
{
    _workflowRuntime = _dataExchangeService = _dataConnector = null;
}

註記: 解構子而非 IDisposable 用於清理,因服務從執行階段移除時不會呼叫 Dispose。建構子為私有,強制透過工廠方法建立,避免循環參考。

本章小結 (上)

本節完成了關聯通訊的基礎設施:定義帶有關聯屬性的介面、實作管理多實例資料的連接器與服務類別。下一節將使用 wca.exe 產生強型別活動,並設計關聯工作流與宿主應用程式整合。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved