Dynamics CRM 2011 サンプル紹介 REST エンドポイントを利用した Silverlight

みなさん、こんにちは。

前回まで REST エンドポイントを利用した JScript を紹介してきましたが、
今回より Silverlight のサンプルを紹介していきます。バックエンドで
REST エンドポイントを使う点は同じですが、実装が少し変わります。

また先日 Dynamics CRM 2011 SDK の新しいバージョンが出ましたので
是非最新版をダウンロードしておいてください。
https://www.microsoft.com/downloads/en/details.aspx?FamilyID=420f0f05-c226-4194-b7e1-f23ceaa83b69

サンプルは、sdk\samplecode\cs\silverlight\crmodatasilverlight
にあるものを利用します。動作を試すために、事前にこのフォルダにある
restjquerycontacteditor_1_0_0_0_managed.zip ファイルを、ソリューションとして
インポートしておいてください。

このサンプルは、はじめの JScript サンプルのように、取引先企業の
レコードの作成、読み取り、更新、削除を行うシンプルなものです。

image

ソリューションは Silverlight アプリケーションを HTML ページがホストする
構成で作成されています。では中身を見ていきましょう

Silvelight アプリケーション

Silverlight アプリケーションは、画面描写を担当する XAML ファイルと、コードを
記述する cs ファイルからなり、その構成はまさに .NET アプリケーションです。内容は
Visual Studio で見るのが容易ですので、Visual Studio 2010 を起動して、
sdk\samplecode\cs\silverlight\crmodatasilverlight\crmodatasilverlight.sln を
開きます。

参照設定
Silverlight アプリケーションを開発する場合、事前準備として参照設定を行います。
いくつか方法はありますが、その中のひとつに、サービス参照を直接追加する方法が
あり、このサンプルではその方法を使っています。 CrmODataService という名称で
Service Reference にすでに登録されています。

他にも、SDK で提供されている dll とコード生成ツールを利用して生成された
ファイルを参照する方法もありますが、別の機会に紹介します。

ユーティリティー - ServerUtility.cs
アプリケーション内で利用するユーティリティーとして、Utility/ServerUtility.cs が同梱
されています。このクラスはサーバーの URL を取得するために利用され、新しい
クライアントオブジェクトモデルで紹介した、Xrm.Page.context も利用しています。

private static String GetServerUrlFromContext()
        {
            try
            {
                // Silverlight アプリケーションがフォーム上にある場合
                // または ClientGlobalContext.js.aspx を読みこんだ HTML 上の場合
                // Xrm.Page.context を利用可能
                ScriptObject xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
                ScriptObject page = (ScriptObject)xrm.GetProperty("Page");
                ScriptObject pageContext = (ScriptObject)page.GetProperty("context");
 
                // getServerUrl 実行
                String serverUrl = (String)pageContext.Invoke("getServerUrl");

                // 最後にスラッシュがある場合は削除
                if (serverUrl.EndsWith("/"))
                {
                    serverUrl = serverUrl.Substring(0, serverUrl.Length - 1);
                }

                return serverUrl;
            }
            catch
            {
                return String.Empty;
            }
        }

App.xaml
App.xaml では、スタートアップ画面の指定等を行っているだけですので、画面描写は
ありません。App.xaml を右クリックして、コードの表示をクリックしてください。

private void Application_Startup(object sender, StartupEventArgs e)
        {
            this.RootVisual = new MainPage();
        }

上記のとおり、MainPage クラスが呼び込まれています。

MainPage.xaml
では次に、Views フォルダ配下にある MainPage.xaml を見ていきます。まずは
MainPage.xaml をダブルクリックで開いてください。画面の上部に UI が、画面の
下部に XAML 定義が見えます。真っ白の画面で UI 要素は見えませんが、XAML
の定義を見ると Class="Microsoft.Crm.Sdk.Samples.MainPage" の定義から
これが MainPage クラスということが分かり、また UI 要素としては <Grid> 配下の
<StackPanel x:Name="MessagePanel" VerticalAlignment="Top" /> 要素から
MessagePanel という名称のスタックパネルが実際にはあることが分かります。

MainPage.xaml.cs
次にソースファイルを見ていきましょう。MainPage.xaml を右クリックして、コードの
表示をクリックします。以下に内容を見ていきましょう。

前準備
まず参照を追加しています。
using Microsoft.Crm.Sdk.Samples.CrmODataService;

また変数として同期コンテキスト、Dynamics CRM のコンテキスト、サーバー名を
保持する文字列を定義しています。 Dynamics CRM のコンテキストはサービス
参照が持っているもので、通常 「組織名Context」 になります。

private SynchronizationContext _syncContext;
private AdventureWorksCycleContext _context;
private String _serverUrl;

次にメイン関数で同期コンテキストやサーバー名を取得します。最後に
取引先企業作成のメソッドを呼んでいます。

public MainPage()
{
    InitializeComponent();
 
    // UI スレッドのコンテキストを取得
    // エラー発生時にエラーを送る先として後ほど利用
    _syncContext = SynchronizationContext.Current;
 
    // ユーティリティを利用してサーバー名を取得
    _serverUrl = ServerUtility.GetServerUrl();
 
    if (!String.IsNullOrEmpty(_serverUrl))
    {

        // サーバー名を使用してコンテキストを作成
        // この時点で実際に使用されるサービスが参照される
        // サービスの参照ポイントは REST エンドポイント (OrganizationData.svc)
        _context = new AdventureWorksCycleContext(
            new Uri(String.Format("{0}/xrmservices/2011/organizationdata.svc/",
                _serverUrl), UriKind.Absolute));
 
        // すでに保持している内容よりサーバー側が新しい場合
        // 問題が出るため、その差分を無視する設定を行う
        _context.IgnoreMissingProperties = true;
 
        // UI にあるスタックパネル内に、テキストブロックを追加し
        // 作業開始のメッセージを表記
        MessagePanel.Children.Add(new TextBlock() {
            Text = "Starting Create, Retrieve, Update, and Delete Operations."});
 
        // 取引先企業作成を開始
        BeginCreateAccount();
    }
            else
    {
        // サーバー名が取得できない場合はエラー
        // これは Silverlight そのもののエラーではないため、 Silverlight 上の
        // スタックパネル (MessagePanel) にメッセージを表示
        MessagePanel.Children.Add(new TextBlock()
        {
            Text =
                "Unable to access server url. Launch this Silverlight " +
                "Web Resource from a CRM Form OR host it in a valid " +
                "HTML Web Resource with a " +
                "<script src='../ClientGlobalContext.js.aspx' " +
                "type='text/javascript'></script>"
        });
    }
}

BeginCreateAccount メソッド
さてここから実際の操作が始まります。まず取引先企業レコードの作成です。

private void BeginCreateAccount()
{
      // 取引先企業クラスを作成
      Account newAccount = new Account();
      // 名前を設定
      newAccount.Name = "New Account Created in Silverlight";
      // Dynamics CRM のコンテキストより AccountSet に作成したクラス追加
      _context.AddToAccountSet(newAccount);
      // SaveChanges メソッドに終了時に呼ばれるコールバックメソッドを渡す
      // SaveChanges メソッドは WCF の機能で、CRM の独自メソッドではない
      _context.BeginSaveChanges(OnCreateAccountComplete, newAccount);
}

内容はシンプルですが、JScript サンプルと同様に取引先企業のオブジェクトを
作成し、それをレコード作成用のメソッドに渡しています。またバックエンドでは
REST エンドポイントを使っているため処理は非同期となり、処理終了時に呼ばれる
コールバック用のメソッドを渡しています。

OnCreateAccountComplete メソッド
上記メソッド完了時に呼ばれるコールバックメソッドの定義です。
ここで処理の結果を確認して、成功なら引き続き次の処理を実行します。

private void OnCreateAccountComplete(IAsyncResult result)
{
    try
    {      
        // 結果を取得
        // EndSaveChanges メソッドは WCF の機能で、CRM の独自メソッドではない
        _context.EndSaveChanges(result)
        // 結果を取引先企業に変換
        Account createdAccount = result.AsyncState as Account;
        // スタックパネルに作成完了の旨を記述
        MessagePanel.Children.Add(new TextBlock() {
   Text = String.Format("Created a new account named \"{0}\"\n\twith AccountId = \"{1}\".",
       createdAccount.Name, createdAccount.AccountId)
        });
       
        // 作成した取引先企業の ID を渡して読み取りの処理を呼ぶ
        BeginRetrieveAccount(createdAccount.AccountId);
    }
    catch (SystemException se)
    {
        // 失敗した場合には、同期コンテキストにエラーを引き渡す 
        _syncContext.Send(new SendOrPostCallback(showErrorDetails), se);
    }
}

BeginRetrieveAccount メソッド
次に作成したばかりの取引先企業を読み取ります。レコードの指定には
LINQ を利用しています。

// 読み取りは LINQ を利用してクエリを記述
DataServiceQuery<Account> query = (DataServiceQuery<Account>)_context
            .AccountSet.Where<Account>(a => a.AccountId == Id);

// BeginExecute メソッドは WCF の機能で、CRM の独自メソッドではない
query.BeginExecute(OnRetrieveAccountComplete, query);

OnRetrieveAccountComplete メソッド
読み取った結果が配列として返されるので、一番初めのものを取り出します。

// EndExecute メソッドは WCF の機能で、CRM の独自メソッドではない
// 以下の流れで、結果を読み取ることが可能
DataServiceQuery<Account> results =
            result.AsyncState as DataServiceQuery<Account>; 

Account retrievedAccount = new DataServiceCollection<Account>(results
            .EndExecute(result)).First<Account>();

その後も、順次メソッドを呼出していきます。

DeleteConfirmation.xaml
もう 1 つの画面として、DeleteConfirmation.xaml があります。こちらは
レコードを最後に削除する前にポップアップさせる、確認画面になります。

image

またこの画面を出しているコードは以下のようになっています。

// DeleteConfirmation クラスを新規に作成
DeleteConfirmation confirm = new DeleteConfirmation(updatedAccount);
// クローズイベントにカスタムハンドラーをアタッチ
confirm.Closed += new EventHandler(OnDeleteConfirmationClosed) ;
// 画面をポップアップ
confirm.Show();

コンパイル
Silverlight アプリケーションは、Visual Studio でコンパイルすると XAP という
拡張子のファイルになります。コンパイル後 Bin\Debug フォルダを確認して
CrmODataSilverlight.xap ファイルを探してください。このファイルの実態は
zip ファイルです。拡張子を xap から zip に変更して開くと、中身が確認できます。

ホストする HTML ファイル

次に Silverlight アプリケーションをホストする HTML を確認しましょう。
JScript のサンプルとよく似ており、スクリプトの読み込みブロックがあり、そして
Silverlight の読み込みブロックがあります。すべて相対パスを利用しています。

crmodatasilverlight\crmodatasilverlight.web\crmodatasilverlighttestpage.html
を開いて確認していきます。

スクリプト部分
ClientGlobalContext.js.aspx の読み込みと独自関数の設定をしています。
独自関数ではエラー処理を行っており、Silverlight アプリケーションでハンドル
されなかったエラーの対処をしています。

<script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script>
<script type="text/javascript">
        function onSilverlightError(sender, args) {
            ...省略...
        }
</script>

Silverlight アプリケーション読み込み部分
Silverlight アプリケーションファイルは相対パスで読み込み、またエラー発生時の関数も
ここで設定が行われています。また Silverlight のインストールリンクも設定されています。

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
   <param name="source" value="ClientBin/CrmODataSilverlight.xap"/>
   <param name="onError" value="onSilverlightError" />
   <param name="background" value="white" />
   <param name="minRuntimeVersion" value="4.0.50401.0" />
   <param name="autoUpgrade" value="true" />
   <a href="https://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50401.0" style='text-decoration:none'>
  <img src="https://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style='border-style:none'/>
   </a>
</object>

HTTP 通信レベルでの動作確認

Silverlight サンプルの説明としてはここまでなのですが、折角?なのでHTTP 通信
レベルの動作も確認しておきます。ネットワークのトレースをするにあたり、今回は
Fiddler 2 を利用しました。 Fiddler 2 は HTTP プロトコルに特化してモニターを
行えるツールで、以下からダウンロード可能です。
https://www.fiddler2.com/fiddler2/

1. Web リソースより crmodatasilverlighttestpage.html を開く準備をします。

2. Fiddler 2 を起動します。Web リソースの画面に戻ります。

3. crmodatasilverlighttestpage.html Web リソースをリンクから開きます。

4. 削除確認の画面が出たら、OK で進めます。

5. 全ての操作が終わると Fiddler 2 の画面に戻り File | Capture Traffic
をクリックしてネットワークキャプチャを停止します。

以下のような結果が得られるはずです。
image

一番上は、Web リソースを開いたときのものです。次から実際の操作の
記録となっていきます。上記スクリーンショットの 2 行目をクリックしてみます。
画面右側に詳細が出ますので、 右画面上側の、 Inspectors タブをクリックします。
image

REST エンドポイントに POST リクエストが投げられています。次に TextView を
クリックしましょう。こちらに POST の中身、つまり作成したい取引先企業のデータが
入っています。d:AccountId のデータを確認すると、0 の羅列が入っており、まだ
実際の GUID がないことが分かります。

次に右画面下側の TextView をクリックします。こちらには、サーバーからの
応答が入っています。そこで再度 d:AccountId を検索してください。
image

今度は実際の値が入っています。スクリーンショット 3 行目はこの ID を使って
データの取得をしています。
image

4 行目では再度 POST をしていますが、MERGE メソッドを利用していることがわかります。
image

これは更新処理をしているためです。 TextView を確認してデータを見てください。
そして削除では同じく POST で DELETE メソッドを利用しています。
image

一番最後には、はじめの 5 件を取得するために TOP を $filter に渡して
いるところが見えます。
image

まとめ

Silverlight を利用した開発では、JScript を利用した開発と同じ REST エンドポイントを
利用しているにも関わらず、HTTP リクエストを作る必要はなく、WCF で提供されている
Begin および End メソッドを利用して実装が可能です。

またクエリに LINQ を利用したり、Account や Contact 等独自型を利用できるメリットが
あります。そしてなによりリッチな UI が売りでしょうか。

次回も Silverlight のサンプルを紹介します。

- Dynamics CRM サポート 中村 憲一郎