SharePoint で ASP.NET (aspx) のワークフローフォームを作成する方法


赤字 -> 2007/08/01 追記 

環境:
Office SharePoint Server (MOSS) 2007
Visual Studio 2005
Office SharePoint 2007 SDK (ECM Starter Kit)

こんにちは。

今日は、aspx を使ってワークフローのフォーム (Initiation Form、Task Edit Form などなど) を作成する方法についてご紹介しましょう。この方法を使用すると、ワークフローフォームなど SharePoint 上のページでASP.NET の機能(コードビハインドのソース実装など)をフルに活用して作成することができます。(代わりに、こちら でご紹介した状況依存なフォームなど InfoPath のもつ利点は受けられないので注意しましょう。また ASP.NET AJAX と共に使用する場合にもいくつか留意点がありますので注意しましょう。→ どんな点があるのか、という疑問に答え、こちら に掲載しました)

この原理ですが、実は、ワークフローフォームの仕組みを理解するというよりは、ワークフローをいかにプログラムからいかに操作できるかを理解することになります。
例えば、Initiation Form では、ASP.NET のページの処理の中でボタンを押すと StartWorkflow メソッドがコールされています。このメソッドは、その名の通り、ワークフローをプログラムからスタートしたいときに使用するメソッドで、ワークフローフォームに限らず、他のプログラムにおいても同様にこの関数を使ってプログラムから起動することができます。(SharePoint では、ワークフローの開始で WorkflowRuntime を取得して直接呼び出す方法はサポートされていません。)
あとは、SharePoint のページをまっさらから作る上でのちょっとしたコツなどを理解しておけば、ASP.NET のコードビハインドから普通に上記のメソッドを使うことができます。

各ワークフローフォームで呼ばれているメソッドは以下の通りです。

[Association Form] SPWorkflowAssociation::CreateListAssociation と SPList.AddWorkflowAssociation
[Initiation Form] SPWorkflowManager.StartWorkflow
[Task Edit Form] SPWorkflowTask.AlterTask
[Modification Form] SPWorkflowManager.ModifyWorkflow

以前も記載しましたが、InfoPath のフォームにおいても実はまったく同じ仕組みで、InfoPath フォームは aspx 上の InfoPath の Web パーツとして張り付いており、submit (送信) 時に InfoPath のフォームのデータを aspx ページに送って、aspx ページは内部で StartWorkflow などをコールして動いています。

では早速作り方を説明していきましょう。
サンプルでは、よく、SharePoint のマスターページ (applicaiton.master) などを使って見た目の統一感も優れた、完成度の高い (ゴージャスな) サンプルが提供されていますが、例により話を複雑にしないために、世界でもっとも簡単な aspx の Initiation Form のサンプルを使って説明していきましょう。

まず、ASP.NET 開発者にとってはおなじみのごくごく普通の要領で ASP.NET サイトを作成していただければ結構です。マスターのプレースフォルダーなども気にせずに、最低限のものだけ組み込んでいきましょう。
ただし、Initiation Form は、さまざまなサイトから呼ばれると思いますので (例えば、http://machine1/ から呼ばれたり、http://machine1/site/test から呼ばれたり、など)、サイト共通で使われる _layouts フォルダ (実体は %programfiles%common filesmicrosoft sharedweb server extensions12TEMPLATELAYOUTS です) の下などにアプリケーションとして作成しておくと良いでしょう。(こうすると、すべてのサイトから同じ相対パスで参照できることでしょう。)
尚、_layouts フォルダの aspx ファイルを直接編集する方法は基本的には提供されていませんので、独自にアプリケーション用のサブフォルダを作成するなどして開発してください。

つぎに、いつものように [参照の追加] で %programfiles%common filesmicrosoft sharedweb server extensions12ISAPIMicrosoft.SharePoint.dll への参照を追加してください。

さらに、作成された Default.aspx に、若干のおまじないを入れる必要があります。
今回、StartWorkflow の関数を呼び出しますが、SharePoint ではこうしたページ遷移を伴う関数の内部で認証情報などのバリデーションチェックのために ValidateFormDigest メソッドを呼び出しています。よって、ここに適切な値を提供するために以下のコントロールを入れておく必要があります。こうしないと、このメソッドで妥当性検証のエラー (error) が発生することでしょう。

以下のプレフィクスを挿入し、

<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

さらに、以下のコントロールを form タグの中に挿入します。(この処理はあまりみかけない方法と思われるかもしれませんが、実は SharePoint のマスターページに設定されていて、SharePoint 上のすべてのページではこのコントロールが使われています。)

<SharePoint:FormDigest runat="server"/>

さらにページですが、継承元を System.Web.UI.Page から Microsoft.SharePoint.WebControls.LayoutsPageBase に変更しておきましょう。こうしておくと、Web サイト (SPWeb) の情報の取得や、将来 SharePoint のパーミッションチェック等々を埋め込んだりする場合にも組み込まれた仕組みを利用することができます。

また、この LayoutsPageBase を使った場合には、ロードイベントは Page_Load イベント処理ではなく、専用の OnLoad メソッドをオーバーライドします。(Page_Load イベント関数は削除しておきましょう。OnLoad でエラーになるはずです。)

そして、ボタンを配置し、そのボタンを押した際に上述した StartWorkflow を呼び出します。ここの引数に渡すリスト (SPList) やワークフローテンプレートのオブジェクトは、その ID の文字列が Initiation Form の GET パラメータとして渡されてくるので、OnLoad などでこの GET のリクエスト情報を使って取得しておきます。

ここまでのことを実装すると、コードビハインドのソース (Default.aspx.cs) は以下の通りになることでしょう。(渡す Initiation Data は空にすることはできません。)

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Workflow;

public partial class _Default : LayoutsPageBase
{
    protected SPListItem m_listItem;
    protected SPWorkflowAssociation m_assocTemplate;
    protected SPList List;

    protected override void OnLoad(EventArgs ea)
    {
        base.OnLoad(ea);

        List = Web.Lists[new Guid(Request.QueryString["List"])];

        m_listItem = List.GetItemById(Convert.ToInt32(Request.Params["ID"]));

        m_assocTemplate = List.WorkflowAssociations[new Guid(Request.Params["TemplateID"])];
        if (m_assocTemplate == null) // リストではなく、コンテンツ タイプ上
        {
            SPContentTypeId ctid = (SPContentTypeId)m_listItem["ContentTypeId"];
            SPContentType ct = List.ContentTypes[ctid];
            m_assocTemplate = ct.WorkflowAssociations[new Guid(Request.Params["TemplateID"])];
        }
    }

    protected void Button1_Click(object sender, EventArgs e)
    {
        Web.Site.WorkflowManager.StartWorkflow(m_listItem, m_assocTemplate, "テスト");

        SPUtility.Redirect(List.DefaultViewUrl, SPRedirectFlags.UseSource, HttpContext.Current);
    }

}

実際の開発では、Initiation data として、この例のように単一の文字列(上記の例では「テスト」の文字列)を渡すのではなく、コントロールのデータ一式を渡すことがほとんどがあるでしょうから、実際には、[Serializable()] の入れ物 (クラス) を作成しておいて、XmlSerializer を使って変換 (クラス -> XML) して渡せば良いでしょう。この辺りのノウハウは、こちら に記載している InfoPath のときの方法と似たやり方です。(受け取った側のワークフローでは、Deserialize メソッドで XML からクラスに変換して利用すると良いです。)

さいごに、ワークフローマニフェスト (通常は workflow.xml) の InstantiationUrl に作成したページへのパスを記載すれば完成です。

なお、デバッグ方法ですが、継承元も LayoutsPageBase なので、SharePoint 用のハンドラを使用しないと動きません (ASP.NET のデバッグ時に自動で起動する開発サーバでは動きません) 。このため、Visual Studio の [Web サイト] - [開始オプション] で [カスタムサーバを使用する] をチェックして、URL に該当のページの URL (例 http://machine1/site/test/Default.aspx) を設定してデバッグすると良いでしょう。
 

Comments (1)

  1. alice says:

    > ワークフローフォームに限らず、他のプログラムにおいても同様にこの関数を使ってプログラムから起動することができます。

    このサンプルをお持ちではないでしょうか?

    ワークフローを呼び出したリスト/ページではないリスト/ページに対しワークフローを実行しようとして達成できずにいます。もしくは完全に独立した_layout/some.aspxから。

    つまり上のサンプル(これは動きます)におけるm_listItemとm_assocTemplateにそれぞれGetListItemなどして取得したSPListItem、SPWorkflowAssociationを設定しています。

    するとStartWorkflowにおいてThe security validation for this page is invalidという例外が発生してしまいます。

    お忙しいところ恐縮ですが、何かヒントでもいただければ大変助かります。

Skip to main content