November, 2012

  • SharePoint の委任コントロール (DelegateControl) を活用する

    こんにちは、SharePoint サポートの森 健吾 (kenmori) です。今回の投稿では、委任コントロールを活用するシナリオについてご紹介します。

     1.    委任コントロールとは

    実は SharePoint には、旧バージョンから委任コントロールという機能が備わっております。これは、簡単に言うと *.master や *.aspx などのファイルを直接書き換えることなく、機能のアクティブ化だけでページの内容を変更することができる機能です。

    具体的な例を挙げると以下のような検索ボックスです。WSS 3.0 の標準の検索ボックスは以下となります。

     一方、MOSS 2007 グループ作業ポータルを使用している場合の検索ボックスは以下のようになります。

    どちらの *.master ページを SharePoint Designer で確認しても、SharePoint:Delegate コントロールとしか記載されていません。

    実はこの DelegateControl というのは、機能が有効化されることによってこの領域 (ここでは SmallSearchBoxInput という領域にどのコントロールを描画するかを選べる機能を保持しています。実際に FEATURES フォルダ内を SmallSearchInputBox というキーワードにて、*.xml を絞り込み grep 検索すると、以下のフォルダ内にこの委任コントロールを使用した機能が確認できます。

    ContentLightup
    OSearchBasicFeature
    OSearchEnhancedFeature

    例えば OSearchEnhancedFeature フォルダ内にある SearchArea.xml を確認します。

    <?xml version="1.0"  encoding="utf-8" ?>
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
        <Control
            Id="SmallSearchInputBox"
            Sequence="25"
            ControlClass="Microsoft.SharePoint.Portal.WebControls.SearchBoxEx"
            ControlAssembly="Microsoft.SharePoint.Portal, Version=12.0.0.0,
            Culture=neutral, PublicKeyToken=71e9bce111e9429c">
           <Property  Name="GoImageUrl">/_layouts/images/gosearch.gif</Property>
           <Property Name="GoImageUrlRTL">/_layouts/images/goRTL.gif</Property>
           <Property  Name="GoImageActiveUrl">/_layouts/images/gosearch.gif</Property>
            <Property  Name="GoImageActiveUrlRTL">/_layouts/images/goRTL.gif</Property>
            <Property Name="UseSiteDefaults">true</Property>
            <Property Name="FrameType">None</Property>
            <Property Name="ShowAdvancedSearch">true</Property>
         </Control>   
    </Elements>

     
    このように、SmallSearchInputBox という領域内に Microsoft.SharePoint.Portal.WebControls.SearchBoxEx コントロールを描画するように指定していることになります。なお、Sequence 属性は委任コントロールが該当コントロールを採用する際の優先順位であり、数値が少ない方が優先されます。例えば、WSS 3.0 の検索ボックスと MOSS 2007 の検索ボックスが両方有効となった場合は、MOSS 2007 の機能が優先されるなどの措置が実現可能となるわけです。

    委任コントロールは、他にも SharePoint 2010 ではナビゲーションなどにも使用されており、例えばメタデータ ナビゲーションとフィルター処理機能をアクティブ化すると、サイドリンクバーにキーフィルターなどの機能が追加されるなどの仕組みもあります。

     

    2.    委任コントロールが効果を発揮するシナリオについて

    運用においてよくある話として、ある特定の JavaScript などを全��のページで実行したいということがあると思います。

    ・1 つのページ上で実行するのであれば、コンテンツ エディタ Web パーツなどに JavaScript を張り付けるソリューションがあります。
    ・複数のページで実行したいのであれば、該当ページが参照するマスタ ページ (例. default.master) などに JavaScript を組み込めば、そのマスタ ページを参照するページ (サイト、発行サイトなら最大でサイト コレクション全体のページ) において、JavaScript が実行されることになります。

    では、サイトが数千個あったらどうなるでしょうか?SharePoint Designer で数千サイト、この作業を繰り返すのは現実的ではありません。

    この時に委任コントロールは大きな力を発揮すると思われます。SharePoint 2010 には、v4.master などを見るとお分かりの通り AdditionalPageHead という委任コントロールが張られており、このコントロール内に JavaScript を埋め込んだカスタム コントロールを描画するようにすれば対処が可能です。

    ソリューションを SharePoint Server にインストールしてしまえば、後は以下のようなというバッチをサイト分実行するようにしてしまえば、あっという間に完成です。

    stsadm –o activatefeature –name <機能> -url <サイト URL>

    そして、これを実施した場合においては、JavaScript の適用が必要なくなった際においても、以下のようなコマンドを用意してサイトの数だけ実行するだけで JavaScript は外れます。

    stsadm –o deactivatefeature –name <機能> -url <サイト URL>

     

    3.    実戦編

     さて、重要な開発方法についてですが、実はすでに MSDN に非常に詳細に記載されています。ただ、これがどのように活用できるかということについては、触れられていないと考えたため、今回の記載に至りました。
    以下のページを今後の運用にお役立ていただければ幸いです。

    タイトル : [方法] 委任コントロールをカスタマイズする
    アドレス : http://msdn.microsoft.com/ja-jp/library/ms470880.aspx

    今回の投稿は以上になります。

     

  • 内部エラー (WinWF Internal Error) 発生を想定したSharePoint ワークフローをデザインする

    こんにちは SharePoint サポートの森 健吾 (kenmori) です。今回の投稿では、内部エラー (WinWF Internal Error) 発生を想定したSharePoint 2007 および 2010 におけるワークフローをデザインする方法をお伝えします。

    少し古くなってしまいますが、前回の投稿 (http://blogs.technet.com/b/sharepoint_support/archive/2011/01/12/sharepoint.aspx) と合わせて、ワークフローの導入前や導入後においても問題を最小限に抑えるためにご確認いただきたい内容をまとめています。

     

    導入 : アプリケーションの内部エラーという宿命

    SharePoint ワークフローも、他のアプリケーションと同様内部エラーが発生することがあります。

    アプリケーション開発者が、アプリケーションで予期せぬエラーが発生することを全て予期し完全に避けるようコーディングすることは不可能です。そのため、一般的にアプリケーション開発者は、例外処理として該当コードブロック内でエラー発生時にカスタム処理を実装して、予期せぬエラーが発生した場合はログを記載したり、例えばエラーを管理者に通知したり、場合によってはリトライしたりする実装を行います。

    C# 例外処理の例

    ----

    try
    {
        // 処理
    }
    catch(Exception ex)
    {
        // 例外処理
    }

    ---- 

    事前に認識しておくべき点として、SharePoint ワークフローは標準機能では診断ログにエラー内容を記録するよう留めています。様々な種類のワークフローが動作する基盤を提供しており、ランタイム側は各処理の重要度がどのようなものかや、エラーの種類 (入力ミスなどを含む) を認識できませんのでやむを得ません。このことから、 Visual Studio 等でカスタマイズ (FaultHandler などを定義) しない限り、標準機能の範囲でエラーを捕捉して独自処理 (特定のアドレスに通知) を実装したり、リトライしたりということはできません。

    ただし、エラー終了してしまうことはシステム上やむを得ないとしても、ビジネス フローが止まってしまい、それに気づかずに業務が遅延または処理されないなど影響を与えてしまうことは、業務上あってはならないことです。

    SharePoint ワークフローでは、このようにエラーとなったワークフローに対する対処策としては、エラーの原因が入力ミスの場合等を除き、速やかに一旦終了した上で再度開始させるのみとなります。このような問題解決に対する対処をあらかじめ想定しておくことは、ワークフローを使用する上で不可欠になると考えています。
    また、多くのユーザーの間で、Visual Studio を使用しない範囲でワークフローを使用するにとどめていることも認識しており、そのような条件下においてエラー発生時をシミュレーションした現実的な対処策を練っていく必要があると認識しています。

    今回の投稿では、その対処策として考えられるベスト プラクティスとしての一案を共有させていただきたいと考えています。

     

    エラーを想定したワークフロー テンプレートの実装方法

    このセクションでは、ワークフローにて発生し得るエラーを想定した上で、実際のワークフローの作成方法についての推奨内容を確認します。


    1.   
    ワークフローを分割する

    上述の通りワークフローが失敗した場合には、該当のワークフローを一度終了して、再度開始する必要があります。このため、複数人による承認プロセスが存在する場合は、最初の人からやり直しとなり、承認者の二度手間になるだけでなく、ビジネス プロセスに遅延が生じます。


    図 1  : 1 つのワークフローで複数の承認プロセスを進める際のイメージ


    複数人による承認を必要とするワークフローを実装する場合、人数分にワークフローを分割した方が得策です。その場合、ワークフローがエラー終了したとしても、再度開始した際にはワークフローの最初から全体のプロセスが再度開始します。


    図 2 : ワークフローを承認プロセスごとに分割した際のイメージ


    これに対し、ワークフローを上図のように分散 (ワークフロー A をワークフロー A, B, C に分散) して、それぞれのワークフローが順番に起動するように実装した場合、それぞれのワークフロー内でエラーが発生した場合にはそれぞれのワークフローの最初から開始する動作となります。そのため、エラー発生時のリスクが減ります。

    なお、具体的に上記のようにワークフローを連鎖させるような実装方法例として、1) 各ワークフローの条件で動作が必要な際は抜ける方法 (下記図 3 参照のこと) や、2) サードパーティ製のアクションを使用してワークフローを直接指定して起動する方法 (またはそれを開発する方法) などが考えられます。

    また、ワークフローの進捗状況を管理するために、アイテムに列などを追加し、どこまで承認されたかをワークフローの処理側で記録することも合わせて必要となります。

    補足 : 競合の保存について
    ワークフローからアイテムを更新する際には、UI 側の処理と同じタイミングで実施すると更新の競合が発生する場合がありますので、事前に待機処理を加える必要も考慮する必要があります。
    更新の競合は、ユーザーの入力内容をを保護する目的で実装されています。ユーザーの更新操作とワークフローの処理が同時にアイテムを更新すると、ユーザーの入力内容が直後のワークフローによってそのまま更新され消失してしまいます。ワークフローは、この状況を防ぐために更新前後の内部バージョン値を使用して更新時に競合を検出し、エラーを発生させます。

     
    図 3 : ワークフローで条件を使用する

     

    2. ワークフローが行ったアイテムの更新処理はロールバックされない

    ワークフローの再実行を考慮した設計を行う際に、予め認識しておく事項としてワークフローがエラーになった際に、それまでアイテム等に実施された内容はロールバックされない点にあります。

    例えば、ワークフローに条件が追加されていて処理を分岐している場合、初期状態ではないと判断すれば、初回実行と 2 回目の実行では動作が異なる結果に至ります。

    このような再実行の可能性を考慮してワークフローは作成される必要があります。例えば、場合によってはワークフローの内部処理にて、値の初期化等を実施するなど追加の処理を加える必要が生じる可能性が考えられます。

     

    3. "アイテム作成時に自動起動する" のみにしない

    上述の通りワークフローが失敗した場合には、該当のワークフローを手動で再起動する必要があります。上述 1. に記載した内容と比較すると細かな点となりますが、ワークフローがアイテム作成時に起動するだけの場合ですと、ワークフロー開始のトリガとなるイベントが起こせない (手動で開始、アイテムの更新では起動しない) ため、通常のオペレーションでは再起動はできません。


    図 4 : ワークフロー開始オプション


    ワークフローの再起動を運用上必要とするのであれば、アイテムが作成されたときにワークフローを自動的に開始するだけではなく、上下にあるいずれかのオプション (このワークフローを手動で開始できるようにする、アイテムが変更されたときにワークフローを自動的に開始する) を有効化する必要があります。

    た��し、上図の状況においても、プログラム (SPWorkflowManager.StartWorkflow) から開始することは可能です。ワークフローの再起動のために別のインターフェースを用意する場合は、この設定値について考慮する必要はありません。

     

    4. 開始フォームのパラメータを使用しない

    プログラムなどで機械的にエラー終了したワークフローを管理者が再実行したり、プログラムによって自動再開 (SPWorkflowManager.StartWorkflow) する方法が考えられます。このような運用やシステム開発を前提とすると、ワークフローに開始フォームのパラメータがあると特別な考慮が必須となります。


    図 5 : ワークフロー開始フォームのパラメータ


    例えば、管理者がエラー終了したワークフローを手動で再実行する際においても、プログラムがワークフローを起動する際にも、開始時にユーザーが何を指定したかを確認する必要が出てきます。

      
    図 6 : ワークフロー開始フォーム


    管理者がこのような画面に遭遇した場合、何を入力して良いかを即時に判断できず、リカバリが遅れることにつながります。失敗したワークフローの再実行等を考慮するのであれば、このような混乱を防ぐため、開始フォームではなくアイテムの列などから情報を採取するよう作成することが現実的です。

     

    ワークフローを監視・リトライする方法

    このセクションでは、具体的にワークフローのエラーを監視したり、ワークフローを再起動するなど運用的な対処案についてご紹介します。

    1.    リスト ビューから失敗したワークフローを抽出する方法

     ワークフローを作成すると、ワークフローの状態列がリストに登録されます。この列の値を使用して、エラー終了や開始時に失敗したワークフローを抽出して UI 上に表示することが可能です。

     1)   新しいビューを追加し、ビューの編集画面を開きます。
     2)   以下のように、ワークフローの状態列に対してフィルターを構成します。

    赤枠で囲った値には、以下のテーブルを参考に対応する数値を指定します。

    管理者は作成されたビューを使用して、エラー発生したワークフローを定期的に監視して、再起動するといった運用が可能となります。

     

    2.    リストを監視し、エラー発生したアイテムを検出して再起動するプログラムを作成する

    上記にて指定した管理者による監視および再起動を自動化することも可能です。以下のコードは実装のイメージを説明するためのサンプルであり、弊社として動作保障をしているものではありませんので、ご了承ください。

    -- サンプル 開始 --
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.SharePoint;
    using System.Threading;
    using Microsoft.SharePoint.Workflow;


    namespace SampleWorkflowRestart
    {

    class Program
    {
        static void Main(string[] args)
        {
                try
                {
                    string siteUrl = "http://sharepoint/sites/site1/web1/";  // サイトの URL を指定します。
                    string listName = "ListName"; // リスト名を指定します。
                    using (SPSite site = new SPSite(siteUrl))
                    {
                        using (SPWeb web = site.OpenWeb())
                        {
                            // リストに関連付けられたワークフローの再起動処理呼び出し
                           SPListRestartWorkflow(web.Lists[listName]);
                        }
                    }

                    Thread.Sleep(5000); // コンソール アプリケーションの場合のみ、ワークフロー起動後にすぐプロセスを終了させないため待機
                    Console.WriteLine("完了しました。");
                }
                catch (Exception ex)
                {
                   Console.WriteLine(ex.ToString());
                }
        }

        static public void SPListRestartWorkflow(SPList list)
        {
                foreach (SPField field in list.Fields)
                {
                    // ワークフロー状態列を列挙します。
                    if (field.Type == SPFieldType.WorkflowStatus)
                    {
                        // 取得したワークフロー状態列が 3 の値のアイテムを取得します。
                        SPQuery query = new SPQuery();
                        query.Query = string.Format("<Where><Eq><FieldRef Name='{0}'/><Value Type='Int32'>{1}</Value></Eq></Where>", field.InternalName, 3);
                        SPListItemCollection items = list.GetItems(query);
                        // 列名に紐づく WorkflowAssociation を取得しておきます。
                        SPWorkflowAssociation assoc = list.WorkflowAssociations.GetAssociationByName(field.Title, System.Globalization.CultureInfo.CurrentCulture);

                        DebugWrite(string.Format("Restarting Workflow - Workflow : {0} ; List : {1}", assoc.Name, list.Title));

                         foreach (SPListItem item in items)
                        {
                            try
                            {
                                foreach (SPWorkflow wf in item.Workflows)
                                {
                                    if (wf.AssociationId == assoc.Id)
                                    {
                                        // ワークフローを一旦終了します。
                                        SPWorkflowManager.CancelWorkflow(wf);
                                        DebugWrite(string.Format("Cancel Workflow - Workflow : {0} ; List : {1} ; Item : {2}", assoc.Name, list.Title, item.Name));
                                        SPWorkflowManager spm = item.Web.Site.WorkflowManager;

                                        // ワークフローを開始します。
                                       spm.StartWorkflow(item, assoc, assoc.AssociationData, true);
                                       DebugWrite(string.Format("Start Workflow - Workflow : {0} ; List : {1} ; Item : {2}", assoc.Name, list.Title, item.Name));
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                               DebugWrite(string.Format("Start Workflow - Workflow : {0} ; List : {1} ; Item : {2}; Error : {3}", assoc.Name, list.Title, item.Name, ex.Message));
                            }
                       }
                       DebugWrite(string.Format("Restarted Workflow - Workflow : {0} ;List : {1}", assoc.Name, list.Title));
                    }
                }
        }

        static public void DebugWrite(string dbgText)
        {
             Console.WriteLine(DateTime.Now.ToString() + "  " + dbgText);
        }
    }
    }

    -- サンプル 終了 --

    このサンプルは開始フォームのオプションに値が追加されているワークフローに対しては正常に起動させることができません。

    この処理を実行するプロセス内で、別スレッドが生成されてワークフローが動作する形となります。メイン スレッドが終了するとプロセスが終了してしまうことを危惧し、Sleep メソッドの呼び出しは行っていますが、コンソール アプリケーション (または PowerShell スクリプトなど) で実装するよりは、常駐アプリケーションなどで実装することが望ましい処理内容となります。

    以下のサイトなどを参考にしていただき、特にカスタム ジョブ定義などで実装することがお勧めです。

    タイトル : [方法] すべての Web サーバ���でコードを実行する
    アドレス : http://msdn.microsoft.com/ja-jp/library/ff464297.aspx

    今回の投稿は以上になります。

     

  • 2012 年 10 月の CU がリリースされました

    2012 年 10 月の CU がリリースされましたのでお知らせします。

    以下のリンクよりダウンロードできます。

    注意点

    • SharePoint Server 2010、SharePoint Foundation 2010 の CU 適用前には SP1 をインストールする必要があります。
    • MOSS 2007、WSS 3.0 の CU 適用前には SP3 をインストールする必要があります。
    • SharePoint Server 2010 の CU には SharePoint Foundation 2010 の CU が含まれます。SharePoint Server 2010 with Project Server の CU には SharePoint Server 2010、SharePoint Foundation 2010 の CU が含まれます。いずれか 1 つの製品ファミリに対応した CU を適用すれば問題ありません。
    • インストールの完了後 SharePoint 製品構成ウィザードを実行する必要があります。

    関連 KB

    • KB 2687535 - WSS 3.0
    • KB 2687533 - MOSS 2007
    • KB 2687566 - SharePoint Foundation 2010
    • KB 2687564 - SharePoint Server 2010
    • KB 2687565 - SharePoint Server 2010 with Project Server

    入手先

    関連サイト