[SharePoint 2010 開発] カスタムの Workflow Service を使用した外部システム連携 (ローカルサービス)


環境 : SharePoint Foundation 2010 (Beta 2), Visual Studio 2010 (Beta 2)

<<SharePoint 2010 開発 新機能>>

こんにちは。

SharePoint Conference (Japan) に関する補足のコードは終了したのですが、今日は、12 月 18 日の SharePoint Forum でご紹介したワークフローサービスについてコードをご紹介します。(SharePoint Conference Japan でも説明するつもりでスライドに入れてましたが、完全に説明をすっ飛ばしてました。。。失礼しました。。。)

この新機能、一見地味そうですが、SharePoint 上で動作する製品開発などに本気で取り組まれている方にとっては、いままでご質問の多かった (そのたびに残念な回答をしてきた . . .) インパクトの大きな新機能です。

ここでご紹介する内容を理解するには、まず、WF のデータ交換サービス (External Data Exchange Services, EDS) に関する基本的な知識が必要です。
本内容については、以下のビデオで紹介していますので、概念を事前に学んでおいてください。

WF の使用 (データ交換サービスを含む簡単サンプルと説明)

http://msdn.microsoft.com/ja-jp/events/dd266855.aspx

SharePoint を使用してワークフロー製品などを構築する場合、当然のことながら自社のシステムや、パッケージ製品などと連携した処理が不可欠となります。SharePoint 2007 では、カスタムコードを使用したワークフローと外部システム連携には課題がありました。

連携が「バックエンド連携」ならば BizTalk Server などの SOA 関連のソリューションを使うことで解決されますが、問題は「リアルタイム連携」です。丸裸の WF ならば、上記のビデオでもご紹介しているデータ交換サービスを自分で組み込んで「ワークフローから外部システムへ」、「外部システムからワークフローへ」という双方向の連携をおこなうことができますが、SharePoint Server 2007 の場合、このデータ交換サービスは、SharePoint が既定で組み込んでいるワークフローサービス (WF でいうところのデータ交換サービスのインタフェース) を使うしか方法がなく、よく使う回避策としては、タスクを媒介して外部システム連携をおこなう、といった手法が用いられていました。
この課題と考え方については、こちらの書籍 でも説明しましたので、持っている方は参照しておいてください (また、https://blogs.msdn.microsoft.com/tsmatsuz/2007/08/27/part-6-sharepoint-workflow-web-service/ では、このタスク連携の方法をご紹介しています)。

SharePoint 2010 では、実は、こうした課題に対する答えがちゃんと用意されています。もうタスクを使って面倒な連携をおこなう必要はありません。
この機能については、Paul Andrew が、MSDN Magazine の中で非常によくまとめてくれています。

MSDN Magazine : Improvements in SharePoint 2010

http://msdn.microsoft.com/en-us/magazine/ee335710.aspx

ここで Paul Andrew が書いているように、使用するシナリオは大きく以下の 2 つになるでしょう。

  • 上述したように、Web サービス呼び出しなどを経由して、外部システムと連携したい場合 (特に、外部システムからワークフローインスタンスを呼び出す場合にも使えます。)
  • もう 1 つは、長時間の処理を呼び出す場合です。長時間の処理を実行する際、そのままワークフローインスタンスの中で実行すると、ワークフローインスタンスがメモリ中で処理された状態でどんどんリソースを枯渇することになるでしょう。

では早速、まずは、CallExternalMethod (ワークフローインスタンスから、外部への呼び出し) を使って簡単な概念をみていきましょう。(そのあとで、HandleExternalEvent を使った例を示します。)

Visual Studio 2010 で、[シーケンシャルワークフロー] のプロジェクトを新規作成します。今回、[サイトワークフロー] を作成してみましょう。

早速、データ交換用のインタフェースとクラスを作成しましょう。ソリューションエクスプローラで、プロジェクトを右クリックして、[追加] - [新しい項目] で [クラス] を追加します。そして、このクラスを以下の通り実装します。(なお、たくさんのメソッドがありますが、下記の CustomExternalClass の各メソッドは、ICustomExternalInterface の実装メンバと SPWorkflowExternalDataExchangeService クラスの抽象メソッドであるため、コードファイル上で、ICustomExternalInterface、SPWorkflowExternalDataExchangeService を右クリックしてメンバメソッドのテンプレートを自動挿入すると良いでしょう。)

. . .
using System.Workflow.Activities;
using Microsoft.SharePoint.Workflow;

namespace WorkflowProject1
{
    [ExternalDataExchange]
    public interface ICustomExternalInterface
    {
        // 独自の引数を渡すには ExternalDataEventArgs を継承します
        // (今回はサンプルのため、ExternalDataEventArgs をそのまま使用します)
        event EventHandler<ExternalDataEventArgs> CallWfInstance;
        void CallExternalSystem(Guid workflowid, string testparam);
    }

    class CustomExternalClass : SPWorkflowExternalDataExchangeService, ICustomExternalInterface
    {
        //
        // 上記の ICustomExternalInterface インタフェースのメンバ
        //

        public event EventHandler<ExternalDataEventArgs> CallWfInstance;

        public void CallExternalSystem(Guid workflowid, string testparam)
        {
            // 何か処理を記述 . . .
        }

        //
        // SPWorkflowExternalDataExchangeService のメンバ
        //

        public override void CallEventHandler(Type eventType, string eventName, object[] eventData, SPWorkflow workflow, string identity, System.Workflow.Runtime.IPendingWork workHandler, object workItem)
        {
            throw new NotImplementedException();
        }

        public override void CreateSubscription(MessageEventSubscription subscription)
        {
            throw new NotImplementedException();
        }

        public override void DeleteSubscription(Guid subscriptionId)
        {
            throw new NotImplementedException();
        }
    }
}

このコードのミソは、 SPWorkflowExternalDataExchangeService という SharePoint 2010 からの新しいクラスを継承している点です。「これを継承すると何がありがたいのか?」については、このあとで説明します。

Workflow1.cs をデザイナーで表示し、まず、挿入されている既存の OnWorkflowActivated アクティビティをダブルクリックして、以下の通り、workflowId を設定しておきます。

private void onWorkflowActivated1_Invoked(object sender, ExternalDataEventArgs e)
{
    workflowId = WorkflowEnvironment.WorkflowInstanceId;
}

つぎに、データ交換サービスの CallExternalMethod アクティビティをドラッグ・アンド・ドロップします。(下図)

挿入した CallExternalMethod アクティビティの [InterfaceType] プロパティとして ICustomExternalInterface、[MethodName] プロパティとして CallExternalSystem を選択します。さらに、CallExternalSystem メソッドの引数である [workflowid] プロパティと[testparam] プロパティが表示されるので、[workflowid] として workflowId という変数 (上記で、インスタンス ID を設定した変数) をバインドし、[testparam] に、今回は、固定文字列として "test" を設定します。

いったんビルドをおこない、作成された .dll (アセンブリ) の厳密名を取得して、web.config (既定では、%wwwroot%\wss\VirtualDirectories\<ポート番号>\web.config) へ以下を追加します。(今回はわかりやすさを重視して以下を手動で変更していますが、ご存じの通り、FeatureActivated メソッド、あるいはサイト定義のプロビジョニングハンドラなどを作成して、機能の使用時にコードで自動的に .config を変更すると良いでしょう。なお、このように手動で設定した場合には、ワークフローのアンインストールなどでライブラリを削除するたびにちゃんとこの要素も消しておきましょう。でないと、ワークフロー全体が動かなくなります!)

<configuration>
  . . .
  <SharePoint>
    . . . 
    <WorkflowServices>
      . . . 
      <WorkflowService Assembly="WorkflowProject1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5e9005524d805f4d" Class="WorkflowProject1.CustomExternalClass">
      </WorkflowService>
    . . .

本来は、<SafeControl /> にも追加をおこなう必要があるので注意してください。
今回の場合、ワークフローモジュールの配置と同時に SafeControl にアセンブリが登録されるため、あえて追加していません。(ですので、別のアセンブリとしてワークフローサービスを作成した場合には、必ず SafeControl にも設定しておいてください。)

上記で作成した CallExternalSystem メソッドにブレークポイントをおいて、F5 でデバッグ実行すると、ワークフローが配置されてブラウザが起動するので、このワークフローを下記のメニューから開始して、ちゃんとブレークポイントに到達することを確認してみましょう。

今回は、この CallExternalSystem に何も処理を記述していませんが、データ交換サービスをご存じの方はおわかりだと思いますが、このメソッドは SharePoint のホスト側で実行されますが、あくまでも別スレッドで実行されていますので、その点だけ注意してコードを記述すると良いでしょう。

と、ここまでだとあまり感動しませんが、つぎに、HandleExternalEvent を考えてみましょう。外部システムから上記のワークフローインスタンスを呼び出すケースです !
ご存じの通り、SharePoint では、WF のワークフローランタイム (WorkflowRuntime オブジェクト) を取得して勝手にサービス (データ交換サービス) のインスタンスを取得することはできません。(WorkflowRuntime の実体は、実は SPWinOeHostServices というクラスにメンバとして入っていますが、internal スコープですので、外から取り出すことも不可です。) そこで、上述した SPWorkflowExternalDataExchangeService が活躍します。

SPWorkflowExternalDataExchangeService の "static メソッド" である SPWorkflowExternalDataExchangeService.RaiseEvent メソッドと、"オーバーライドメソッド" である CallEventHandler が重要な役割を果たします。SPWorkflowExternalDataExchangeService を使用すると、外部システムなどから SPWorkflowExternalDataExchangeService.RaiseEvent を呼び出すことで、ランタイムに登録されているデータ交換用のインスタンス (インタフェースを継承したオブジェクト) を取得して、そのオブジェクトの CallEventHandler メソッドを呼び出してくれるのです。

実験してみましょう。

CallEventHandler を実装するため、上記のコードを以下の通り変更します。

class CustomExternalClass : SPWorkflowExternalDataExchangeService, ICustomExternalInterface
{
    public event EventHandler<ExternalDataEventArgs> CallWfInstance;

    . . .

    public override void CallEventHandler(Type eventType, string eventName, object[] eventData, SPWorkflow workflow, string identity, System.Workflow.Runtime.IPendingWork workHandler, object workItem)
    {
        ExternalDataEventArgs arg = new ExternalDataEventArgs(workflow.InstanceId);
        this.CallWfInstance(null, arg);
    }

    . . .
}

ワークフローに、今度は、HandleExternalEvent アクティビティを下図の通り挿入し、挿入した HandleExternalEvent アクティビティの [InterfaceType] プロパティとして上記の ICustomExternalInterface、[EventName] プロパティとして CallWfInstance を選択します。

上記と同様に、CallExternalSystem メソッドにブレークポイントをおいて F5 でデバッグ実行してみます。
前回同様、ブレークポイントで止まりますので、入力引数としてわたってきている workflowid をメモ帳などにコピーしておき、そのまま F5 を押して継続実行しましょう。

当然、ワークフローは HandleExternalEvent のところで止まり、「進行中」のステータスのまま (下図) となります。(この時点で、このワークフローインスタンスはデータベースに永続化され、メモリ上から退避されます。)

つぎに、このサーバーマシン上で、Visual Studio を使って、ターゲットを .NET Framework 3.5 としてコンソールアプリケーションを作成してみます。

※注記: なお、この際、本旨と関係ありませんが、1つだけ注意があります。コンソールアプリケーションのプロジェクトのプロパティ画面を表示し、[ビルド] タブを選択し、[プラットフォームターゲット] を x64 に変更しておいてください。既定では下図の通り x86 になっているため、こうしておかないと、サイトが見つからずに FileNotFoundException が発生します。

つぎに、このプロジェクト (コンソールアプリケーション) に Microsoft.SharePoint.dll、System.Workflow.Activities.dll、System.Workflow.ComponentModel.dll、System.Workflow.Runtime.dll を参照追加します。

上記の ICustomExternalInterface が記述されたクラスファイル (.cs ファイル) を既存のアイテムとして追加し、以下の通りコードを記述します。今回は、http://kkdeveva07/sites/officedemo のサイトコレクション上を使用し、進行中のワークフローインスタンス ID (上記で、メモ帳に記録した ID) は {af89ee58-08d8-457b-accb-9dc08052aab0} であったと過程します。

. . .
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;

static void Main(string[] args)
{
    using (SPSite site = new SPSite("http://localhost/sites/officedemo"))
    {
        SPWeb web = site.RootWeb;
        SPWorkflowExternalDataExchangeService.RaiseEvent(web,
            new Guid(@"{af89ee58-08d8-457b-accb-9dc08052aab0}"),
            typeof(WorkflowProject1.ICustomExternalInterface),
            "CallWfInstance",
            null);
    }
}

このコンソールアプリケーションを実行すると、上記の「進行中」のワークフローで HandleExternalEvent が呼び出され、ワークフローを参照すると、無事、「完了」しているのがわかります。

今回はコンソールアプリケーションを使用しましたが、同じ原理で Web サービスや WCF サービスなどからワークフローインスタンスにイベントを渡すことができます。
SharePoint 2010 からは、パッケージ製品や独自のシステムなど、さまざまな外部システムを対象として、ワークフローと連携したアプリケーションが構築できるようになります。

 

Comments (0)

Skip to main content