印刷用ページ       送信     
クリックして評価とフィードバックをお寄せください
MSDN
MSDN ライブラリ
テクニカルドキュメント
Coding4fun
Coding4Fun: XML for Fun
 Coding4Fun: BackPack API を利用する - 第 ...
Coding4Fun: BackPack API を利用する - 第 II 部
BackPack API を利用する - 第 II 部

Michael K. Campbell
3Leaf Development

November 11, 2005
日本語版最終更新日 2006 年 1 月 13 日

.NET 2.0 のエキサイティングな新機能の 1 つは、Generics の導入です。これはテンプレートに似た特殊なクラスであり、開発者が厳密に型指定された効率的な方法でオブジェクトのコレクションと迅速かつ簡単に対話できるようにします。しかし、Generics に関する記事やドキュメントをインターネットで検索すると大量の情報が見つかりますが、Generics の XML へのシリアル化に関する情報はほとんどありません。実際には、ドキュメントは事実上存在しません。Generics はシリアル化に非常に役立ち、いくつかの単純だが重要なポイントに対処すればオブジェクトのコレクションのシリアル化をかなり容易にできるため、資料がないのは残念です。この記事では、これらの重要ポイントのいくつかを検討し、Generics のシリアル化の具体例をいくつか示すと共に、オブジェクトのコレクションをシリアル化する場合に Generics がどのように役立つかについて概説します。サンプル プログラム ファイル内では実際のコメント行は英語で書かれていますが、この記事内では説明目的で日本語で書かれています。

Back Pack API - サンプル アプリケーションへのロードマップ

前の記事では、BackPack API を紹介し、BackPackIt.com サーバーと直接対話してユーザー情報を操作できるだけでなく、データをローカルに取得してオフラインで操作することもできる Winforms アプリケーションの構築方法を 3 回の記事で考察すると述べました。前の記事では、BackPack の概要、その付属 API、および BackPack サーバーに戻して処理できるユーザー入力を表す XML データを動的に生成するコードの作成方法を中心に説明しました。前の記事のサンプル アプリケーションは少々不十分でしたが、DOM を使用した動的な XML 生成の重要概念をいくつか扱い、'XML' Web サーバーと簡単に通信する方法も示していました。これらはすべて、3 部から成るこのシリーズで完成する最終的なアプリケーションの構築の重要な構成要素です。

この記事では、別の重要な考慮事項と、最終的なアプリケーションの必須構成要素である BackPack データのローカル格納を中心に説明します。BackPack をオフラインで簡単に使用できるようにするために、最終的なアプリケーションではユーザーの BackPack データのローカル コピーを操作する必要があります。最終的なアプリケーションでは、ユーザーがデータを変更する場合に、変更は最初にローカル データ ストアに対して行われ、対応する変更が、処理を目的としてサーバーに送信されます。アプリケーションがオンラインの場合、変更は即時に送信されます。オンラインでない場合は、アプリケーションがインターネット接続を回復し、必要に応じてコマンドを BackPack サーバーに渡せるようになるまで、コマンドが棚上げされます。したがって、第 1 の目標は、BackPack を "移植可能" にすることです。XML は、この目標の達成に重要な役割を果たします (また、Generics のシリアル化も、XML を利用してニーズを満たす際に大きな役割を果たします)。

当然、操作がサーバー上で正常に完了するまでは、ページのローカル コピーが "変更済み" であるかサーバー上で更新保留中であることをなんらかの方法で知る必要があります。また、なんらかの理由で変更をサーバーで正しく実行できない場合に、このような問題から回復する方法があることも確認する必要があります。ただし、これらの詳細についてはすべて次の記事で扱います。ここでは、BackPack データを必要に応じてクエリできるようにローカルに保存するためのフレームワークに注目します。ローカルに保存されたデータは、オンラインかオフラインかにかかわらず、サーバーに対して変更を行う際の基礎になります。つまり、この記事の目的は、次の処理を行える単純なアプリケーションの形式で、最終的なアプリケーションに到達するための重要な構成要素をさらにいくつか示すことです。

  1. サーバーからページを読み込む

  2. そのページをディスクに保存する

  3. そのページを必要に応じてサーバーまたはディスクから読み込む

アーキテクチャと方針

BackPack API はデータを XML として返します。XML は優れた転送メカニズムですが、最終的なアプリケーションでは XML を操作しません。実際に必要なものは、オブジェクトと対話する機能 (どのような方法で行うかは気にせずに、何を行うべきかをオブジェクトに指示できる機能) が必要です。つまり、カプセル化が必要です。カプセル化とは、手順を変更する必要がある場合でもアプリケーションが無効にならないような方法で、アプリケーションの他の "要素" から実装の詳細を隠す機能です。BackPack API のクイック レビューでは、ユーザー データにアクセスする方法がいくつかあることが示されています。たとえば、API のドキュメントから抽出した次の画面キャプチャに示すように、要求されたページに関連する現在の注記をすべて含む XML "データグラム" を返す特定の URL をクエリすることにより、ある特定のページに対する注記を取得できます。

つまり、API には SLICE および DICE データを返す機能があります。これは便利ですが、最終的なアプリケーションでは不要です。最終的なアプリケーションの目的は、API で使用できるすべてのもの (ほぼすべてのもの) をカプセル化することです。BackPack アプリケーションはページを中心に展開されるため、ここで作成するアプリケーションでもページをターゲットとします。つまり、各ページのローカル コピーがあれば、特定のページに属するリスト、注記、タグ、リンクなどを必要に応じて確認できます。何かについて知る必要があるたびに、サーバーと一連の対話を行う必要はありません。したがって、アプリケーションは "ページ中心" の方法で設計する必要があります。このため、この記事の全般的な焦点とフローは次のようになります。

  1. ページ中心のオブジェクト モデルを作成します。このモデルでは、ページと対話することで、そのページに属する資産 (データ) を参照でき、(記事 3 で説明するように) ページに属するデータの変更はページ オブジェクト自体によって処理されます。

  2. 必要に応じてページ オブジェクトを操作できるビジネス オブジェクト (中間層) の基礎を作成します。このビジネス オブジェクトでは、サーバーからかディスクからかにかかわらず、ページの作成や削除に関連するすべての詳細を処理できます。

  3. 最後に、ページの読み込みと保存に関連する操作を簡単に処理できる基本的な GUI に前述の機能をラップする必要があります。

オブジェクト モデルを構築する

適切なページ中心のオブジェクト モデルを構築するために、どのデータ ポイント、すなわち属性が各 BackPack ページにあるかを調べて、対応するデータ ポイント、すなわちプロパティをクラス内に作成します (第 3 の記事で、動作をモデル化します。つまり、ページ オブジェクトの変更と対話を行える方法をモデル化します)。API に文書化されているサンプル ページを見ると、表す必要のあるデータ ポイントをすぐに理解できます。

前の記事で説明したように、BackPack API は内容の濃い優れた例で詳細に文書化されています。このことは、API で扱われているサンプル ページに当てはまります。必要になる可能性のあるすべてのデータ ポイントが単一のページに適切にモデル化されます。したがって、ご覧のように、ページは ID、タイトル、本文、および場合によってはリスト、注記、タグなどのコレクションから構成されます。この最後のリソース タイプのモデル化は簡単です。これらはある特定のページの構成要素であるため、開始するのに合理的な場所です。

注: Generic Collection は System.Collections.Generic.List に格納されることが多いため、BackPack に関する話題では、リストをオブジェクト モデル内のタスクと呼び、混乱を避けることにします。

数秒かけて C# Express を入力するだけで (コード スニペットを使用してプロパティ定義を生成します)、Links、Notes、Tags、および Tasks がすばやくモデル化されます (ただし、次の画像は VS 2005 で生成されました)。

これらのオブジェクトが定義された後、これらのデータ型を子ノードとして含む Page オブジェクトを同様の方法で作成できます。前述のクラスと同様に、このオブジェクトも、id プロパティ、その他いくつかの文字列型 (String) フィールド、また場合によっては上記で作成したクラスのコレクションから構成されます。

少なくとも実行時のデータ格納の観点からは、この時点でオブジェクト モデルは完成です。

シリアル化を開始する

オブジェクト モデルが完成したら、永続化のニーズを満たすために、オブジェクトから XML への変換とその逆の変換のプロセスに注目します。最終的なアプリケーションでは、BackPackIt.com サーバーから読み込まれるか、インターネットに接続していないときに使用するローカル コピーを保存したファイル システムから読み込まれるかにかかわらず、XML からページ オブジェクトを "作成" する必要があります。通常、外部組織から提供される XML をオブジェクトのベースにする場合は、外部組織が XML を変更した場合でもアプリケーションが無効にならないこと、少なくともこのような変更に簡単に対処できることを確認することをお勧めします。外部 XML は、アプリケーションの内部で使用される信頼できる形式への変換を通じて実行することをお勧めします。この処理は、XML 変換を通じて簡単に行うことができます。XML 変換は、アプリケーションでは認識されない XML の詳細の抽象化を支援するようにデザインされたバッファ インターフェイスと考えることができます。

変換を使用すると、他の利点もあります。着信 XML をアプリケーションで使用されるのと同じ形式に変換する場合に、事実上同じ機能を使用してオブジェクトを BackPack サーバーとファイル システムからシリアル化解除できます。つまり、サーバーから読み込まれた単一ページが変換されると、そのページはアプリケーションからはディスクにシリアル化されたページのコレクションと同じように見えます。また、コレクションに関しては、ここで再び Generics に注目する必要があります。Page クラスの実装を見ると、Notes、Tasks、Tags、および Links が汎用型 SerializableList として実装されていることがわかります。これは、このアプリケーションのために作成したカスタム クラスです。この型の "宣言全体" は次のようになります。

[XmlType("{T}s")]
public class SerializableList<T> : List<T> { }    

コードはそれほど長くありません。なぜなら、SerializableList は単に、.NET Framework 2.0 に付属する System.Collections.Generic.List クラス (Generics のコンテナ) から派生するクラスであるためです。内部では、List オブジェクトとまったく同じ動作をしますが、小さな違いが 1 つあります。このカスタム クラスには、シリアル化される情報の型を示す XML Serialization 属性があります。[XMLType("{T}s"] が示す内容がこれであり、{T} はコレクションに保持されるオブジェクトの型のプレースホルダとして使用されるトークンです。次に、大まかなシリアル化方法の概念を示します。

<{T}s>
    <TypeName>data</TypeName>
    <TypeName>data</TypeName>
</{T}s>

含まれるオブジェクトの型は、{T} トークンで示される親要素として出力され、後続の子要素は必要に応じてシリアル化されます。このため、SerializableList に文字列 (SerializableList<string>) のような単純な内容が含まれている場合、XML タグは次のようになります。

<strings>
    <string>one</string>
    <string>two</string>
    <string>etc.</string>
</strings>

ご覧のとおり単純です。複雑なオブジェクトを同じ方法でシリアル化すると、Generics はコレクションのシリアル化における強力なツールになります。秘訣は、シリアル化されるあらゆるものを示すのに十分に汎用的な XmlTypeAttribute 名を付けることだけです。これにより、1 つのコレクション オブジェクトがあらゆるシリアル化ニーズに適合します。ここで使用している例では、型名自体の最後に "s" を追加し、それでよしとしましたが、名前の最後に "list" などを付けることも簡単にでき、同じように適切に機能します。

このため、説明したわずかな情報を使用して、Page オブジェクトがシリアル化され、Tasks、Tags、Links、または Notes は、データが現在存在していれば対応するエンティティのリストとしてシリアル化されます。次に例を示します。

<?xml version="1.0" ?>
<Pages>
<Page>
    <PageIsDirty>false</PageIsDirty>
    <Title>Example Page</Title>
    <Body>This page was created via the BackPack API.</Body>
    <Tasks />
    <Notes>
        <Note>
            <Title>Sample Note</Title>
            <Body>This is the body of a note</Body>
            <Id>279762</Id>
            <Created>2005-09-29 15:08:08</Created>
        </Note>
        <Note>
            <Title>Another Note</Title>
            <Body>More body/sample</Body>
            <Id>279385</Id>
            <Created>2005-09-29 17:05:35</Created>
        </Note>
    </Notes>    
    <Links />
    <Tags />
</Page>

Note オブジェクトのコレクションが <Notes> (または型の名前が "Note" である <{T}s>) などとしてシリアル化される方法に注目してください。また、Pages のコレクションをシリアル化する場合も、同じ方法で、対応するネストした構成データのコレクションを持つ <Pages> コレクションとしてシリアル化されます。これにより、すべてが整然とし、扱いやすくなります。

<Pages>
 <Page>
      <PageIsDirty>false</PageIsDirty>
      <Title>Example Page</Title>
      <Body>This page was created via the BackPack API.</Body>
      <Tasks>
<!-- 省略 -->
   </Tasks>                
      <Links />
      <Tags />
 </Page>
 <Page>
      <PageIsDirty>false</PageIsDirty>
      <Title>Another Page</Title>
      <Body>This page doesn't have any data in it yetother than title/body.</Body>
      <Tasks />
      <Notes />
      <Links />
      <Tags />
 </Page>
 <Page>
      <PageIsDirty>false</PageIsDirty>
      <Title>And Yet Another Page</Title>
      <Body>Unlike the above page, this one has some data loaded...</Body>
      <Tasks>
     <Task>
            <Text>Get Stuff Done</Text>
            <Id>1347087</Id>
            <Complete>false</Complete>
        </Task>
        <Task>
            <Text>Do more stuff</Text>
            <Id>1347088</Id>
            <Complete>false</Complete>
        </Task>
      </Tasks>
      <Notes />
      <Links />
      <Tags />
 </Page>
</Pages>

オブジェクトのシリアル化は、この時点でも非常に簡単です。知る必要があるのは、シリアル化するオブジェクトの型 (この例では Generic Collection)、情報の保存場所、およびシリアル化が必要なデータを含む、シリアル化するオブジェクトの型のインスタンスだけです。資格情報と接続情報に加えて、ページ (また、後でオフラインになったときにデータを変更するためのコマンドさえ) もシリアル化するため、次のようなヘルパ メソッドを作成しました。

private void SerializeToFile(Type type, object data, string path, SavedFileType resourceType)
{
    try
    {
        XmlSerializer serializer = new XmlSerializer(type);
        XmlTextWriter tw = new XmlTextWriter(path, Encoding.UTF8);

        tw.Formatting = Formatting.Indented;
        tw.WriteRaw("<?xml version=\"1.0\" ?>");

        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
        ns.Add(string.Empty, string.Empty);

        serializer.Serialize(tw, data, ns);
        tw.Close();

        this.OnSerializationComplete(new SerializationCompleteEventArgs(resourceType));
    }
    catch // etc.. 
}

コードはかなり単純です。指定された型の XmlSerializer インスタンスを作成し、既定の XML Serialization 名前空間 (シリアル化された XML に挿入されます) を、シリアル化された XML を整然と保つ null または空白の名前空間で置換するコード スニペットを含んでいるだけです。

着信 XML を変換する

シリアル化された後にページがどのようになるかを示したので、着信 BackPack XML を同じ形式に変換して、独自の形式でローカルに格納されている場合と同じぐらい簡単に BackPack ページをサーバーからシリアル化解除できるようにする簡単な XSL 変換を構築できます。BackPack XML と独自形式はどちらも非常に要素が多く、マッピングは実際には着信ドキュメントの名前を独自のオブジェクトの名前に合わせて変更するだけであるため、変換は単純です。

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" />
<xsl:template match="/">
    <Page>
        <PageIsDirty>false</PageIsDirty>
        <Title><xsl:value-of select="response/page/@title" /></Title>
        <Body><xsl:value-of select="response/page/description" /></Body>
        <xsl:if test="count(response/page/items/*) > 0">
        <Tasks>
            <xsl:apply-templates select="response/page/items" />
        </Tasks>
        </xsl:if>
        <xsl:if test="count(response/page/notes) > 0">
        <Notes>
            <xsl:apply-templates select="response/page/notes" />
        </Notes>
        </xsl:if>
        <xsl:if test="count(response/page/links) > 0">
        <Links>
            <xsl:apply-templates select="response/page/links" />
        </Links>
        </xsl:if>
        <xsl:if test="count(response/page/tags) > 0">
        <Tags>
            <xsl:apply-templates select="response/page/tags" />
        </Tags>
        </xsl:if>
    </Page>    
</xsl:template>
<xsl:template match="item">
    <Task>
        <Text><xsl:value-of select="." /></Text>
        <Id><xsl:value-of select="./@id" /></Id>
        <Complete><xsl:value-of select="./@completed" /></Complete>
 </Task>
</xsl:template>
<!-- 簡潔にするため省略 (詳細についてはサンプル コードを参照) -- >
</xsl:stylesheet>

筆者は少々 (咳払い) 潔癖症でもあるため、サーバーから到着した空要素が変換後の XML に含まれていないことを確認するためのロジックを追加しましたが、これはまったくエネルギーと CPU サイクルの浪費です (ただし、XML を判読しやすくする場合にどうすればよいかの単純な例としては役立ちます)。

変換の実行は、次のコードで示すように、ページがサーバーから到着したときにメモリ内で行われます。

private void SinglePageReturned(string pageData)
{
// 必要な作業:try/catch の追加など

    byte[] data = Encoding.UTF8.GetBytes(pageData);
    MemoryStream stream = new MemoryStream(data);
    XPathDocument input = new XPathDocument(stream);

    XslCompiledTransform xsl = new XslCompiledTransform();
    XmlNode stylesheet = this.LoadTransformDocument();
    xsl.Load(stylesheet);

    MemoryStream ms = new MemoryStream();
    StreamWriter sw = new StreamWriter(ms);

    xsl.Transform(input, null, sw);

    XmlDocument page = new XmlDocument();
    byte[] bytes = ms.ToArray();
    string transformedXml = Encoding.UTF8.GetString(bytes);
    page.LoadXml(transformedXml);
    sw.Dispose();
    ms.Close();
    ms.Dispose();

    XmlNode node = page.SelectSingleNode("/");
    Page output = this.GetPageFromXml(node);

    this.AddPage(output);
}

XSL ファイルを取得するヘルパ メソッド LoadTransformDocument() に注目してください。前述の変換ドキュメントの取得に使用するメソッドを抽象化するために、このメソッドをヘルパとして追加しました。このサンプル アプリケーションでは、この XSL ドキュメントをアセンブリ自体から取得しています。その目的は、この操作が可能であることを示すことだけでした。メモリ内の XSL 変換を完全に読み込んで、サテライト ファイルなどへの依存を不要にする場合は、この操作が役立ちます。ただし、ここで使用している例では、XSL ドキュメントを使用して着信 XML を変更から保護しているため、実際にはこの操作の意味はほとんどありません。37Signals によって XML 形式が変更された場合は、新しい変換を構築し、アプリケーションを再コンパイルして、アプリケーションでの使用を継続するために XSL ドキュメントをアセンブリのリソース ストリームに戻す必要があります。このため、ここでは XML ドキュメントをアセンブリから取得する方法をモデル化することのみを目的として、この方法を使用したことに注意してください。次に、XSL ドキュメントをフェッチするコードを示します。

private XmlNode LoadTransformDocument()
{
    if (this._stylesheetNode == null)
    {
// xslt ドキュメントをマニフェストから取得する:
        Assembly current = Assembly.GetExecutingAssembly();
        XmlDocument doc = new XmlDocument();

        using (StreamReader sr = new StreamReader _
        (current.GetManifestResourceStream(this.GetType(), "transform.xsl")))
            doc.Load(sr);

        XmlNode output = doc.SelectSingleNode(".");
        this._stylesheetNode = output;
    }
    return this._stylesheetNode;
}

最終的なアプリケーションでは、このメソッドで、アプリケーションが存在する現在のディレクトリへのストリームを開き、.xsl ドキュメントに取得する方がよいと思われます。そうすれば、形式を変更する必要がある場合に、Transform ドキュメントを置換するだけで済み、アプリケーションを再コンパイルする必要はありません。

ページを XML からオブジェクトに変換する

変換が構築され、ページをディスクにシリアル化するメソッドが完成したら、次に注目する必要があるのは、ページを BackPack サーバーから取得してオブジェクトに変換することです。その後、ディスクに保存されたページからオブジェクトへの変換に注目できます。ただし、XML 形式は同じであるため、両方の操作は最終的に非常によく似たものになります。これらの操作を次の 2 つのメソッドで扱います (一方は単一の Page を返し、もう一方はコレクション、つまり Page の SerializableList を返します)。

private Page GetPageFromXml(XmlNode input)
{
    XmlSerializer s = new XmlSerializer(typeof(Page));
    Page page = (Page)s.Deserialize(new XmlNodeReader(input));

    return page;
}

private SerializableList<Page> GetPagesFromXml(XmlNode input)
{
    XmlSerializer s = new XmlSerializer(typeof(SerializableList<Page>));
    SerializableList<Page> list =
        (SerializableList<Page>)s.Deserialize(new XmlNodeReader(input));

    return list;
}

BackPack ページを表す XML は、シリアル化されたオブジェクトによって出力される形式に変換されたため、その取り込みは高速かつ単純な操作です。また、Generic SerializableList には、シリアル化されるオブジェクト コレクションに型に基づいて要素名を合成する XML Serialization 属性があるため、ネストしたオブジェクトがすべて完全に揃い、必要に応じて取り込みや格納を行うことができます。

ページを読み込む - ユーザー インターフェイスをフリーズさせない

これで、最終的なアプリケーションの主要コンポーネントの多くが完成しました。最初の記事で作成した、BackPackIt.com サーバーから XML をフェッチするための接続メカニズムがあり、アプリケーションで使用できるオブジェクトに XML を変換するメカニズムも作成しました。また、これらのオブジェクトをオフラインで使用するために Page のコレクションとしてディスクに格納する機能もあります。他に必要なことは、前回の記事の既存の機能を組み合わせて、アプリケーションで使用できるオブジェクトに変換できるように、サーバーにページを要求することです。また、資格情報を指定し、サーバー (またはファイル) からのページの読み込みやディスクへのページの保存などの操作を指示できるユーザー インターフェイスも必要です。

ユーザー インターフェイスの構築では、ファイル システムやリモート サーバーと長時間対話しても UI が停止 (フリーズ) しないという保証が必要です。これを保証するために、これらの対話を非同期に実装する必要があります。これにより、UI スレッドのロックを防ぎ、リモート サーバーまたはファイル システムから返されるデータでの衝突または競合条件の発生を回避できます。長時間実行メソッドの非同期呼び出しを使用するのと同時に、イベントを使用して新しいデータの到着を "通知" し、必要に応じてユーザー インターフェイスへのそのデータのバインドを処理します。これにより、個々のページがサーバーまたはファイル システムから読み込まれている間に Winform が応答できます。また、この機能は、次の記事で扱う変更指向の操作のフレームワークとして機能します。

非同期アーキテクチャ

アプリケーションの応答性を維持するために、ユーザー インターフェイスでは、ユーザー要求をビジネス ロジック層 (PageManager オブジェクト) にルーティングします。ビジネス ロジック層では、要求された実際の操作が非同期デリゲートを使用して実行されるため、UI スレッドはユーザー対話に集中できます。要求された操作が完了すると、PageManager および RemoteMethodComplete イベントが起動し、Winform によってバブルアップされて処理されます。各イベント タイプに関連するハンドラ ロジックでは、必要に応じて Winform で着信情報を処理し、操作自体の実際の詳細 (個々のページなど) は各イベントに付属の対応する EventArgs クラスの一部としてイベント内で渡されます。これにより、各操作の完了に関連する状態またはデータが、同時に完了した別の操作によって上書きされるおそれがなくなります (ロギング デリゲートにより返される状態データの処理にメンバ変数を使用していた最初の例では、この上書きが発生しました)。

BackPack サーバーにデータを要求するために必要なメソッドの実際の呼び出しは、必要な XML をバンドルする PageManager 内のヘルパ メソッドおよび対応する XML を通じてルーティングされ、Connection オブジェクトにバインドされているデリゲート シグネチャで非同期に呼び出すことで操作の完了を "スケジューリング" することにより、独自のスレッドに明示的に配置されます。

private void InvokeRemoteOperation(string url, XmlElement[] args)
{
    if (this._gateway.ConnectionInfo.IsOnline)
    {
        AsyncRemoteOperation operation = new AsyncRemoteOperation(this._gateway.ExecuteWebMethod);
        operation.BeginInvoke(url, args, null, null);
    }
    else
// 第 III 部で説明
}

また、このヘルパ メソッドはすべてのコマンドのルーティングを処理するため、BackPack サーバーにコマンドをルーティングする場所、または後で実行するためにコマンドを格納する (次の記事で扱います) 場所として最適です。前回の記事の例のように、XML データは必要に応じて生成され、適切な URL に対して起動される args の XmlElement 配列として渡されます。ただし、前回の記事では、Winform がそのデータのバンドルと準備を行いました。論理的には、このような対話は Winform が PageManager クラスを通じて透過的に処理する必要があります。そのためには、XML のバンドルと適切な URL の検索に関する詳細の処理方法がわかっている必要があるため、この記事では PageManager でこれらの詳細をすべて処理しています。XML が生成され、URL が構築されると、PageManager は要求を InvokeRemoteOperation() メソッドにルーティングします。このメソッドは、前の記事で使用した BackPackRequest クラスをわずかに変更した BackPackGateway オブジェクトにコマンドをルーティングします。BackPackGateway オブジェクトは、接続の詳細を処理し、必要に応じてデータを転送し、完了後にイベントを起動し (このイベントは、発生したイベントのタイプを評価できる PageManager で処理されます)、返された情報に対して行う操作を決定します。

public void GatewayResponded(object sender, RemoteMethodCompleteEventArgs e)
{
    if (e.OperationStatus == CompletionStatus.Fail)
    {
        throw new BackPackServerException("Asynchronous BackPack Method Failed.", e.Exception);
    }
// 後で、返された情報をより詳細に解析し、
//  適宜処理する - ここでは、単一ページであるか、すべてのページのリストであるかのみ
//  知る必要がある。
    if (e.MethodUrl == "/ws/pages/all")
    {
        this.PagesListed(e.ResponseText);
    }
else // これは個々のページである
    {
        this.SinglePageReturned(e.ResponseText);
    }
}

PageManager は、返されるページのリストを検出すると、これらのページを読み込む必要があることを認識しているため、現在のバックグラウンド スレッドでコレクション内の各ページの要求に進みます。したがって、URL は、要求を処理するように構築され、BackPackGateway オブジェクトにルーティングされます。このオブジェクトは、要求とユーザー資格情報のバンドル、BackPack サーバー上の適切な URL への要求のルーティング、応答の処理、および RemoteMethodCompleted イベントを通じて PageManager にバブルアップされる、対応する RemoteMethodCompletedEventArgs クラスへの結果のバンドルを行います。PageManager は、GatewayResonded ハンドラでイベントを再び処理します。この時点でのみ、個々のページが返されたことがわかります。イベントからのデータ (ページを表す XML) は、XML を独自の永続形式に変換するメソッドにルーティングされ、このメソッドが結果の XML を完全な Page オブジェクトに変換します。ページが取り込まれると、PageManager がページを独自の Page の内部コレクションに追加し、ページを PageAddedEventArgs クラスにバンドルして、PageAdded イベントを通じて新しいページの到着を Winform に通知します。ユーザー入力 (または、バック エンド プロセスからの通知) を待機していた Winform は、PageAdded イベントを処理し、この記事では、Page のタイトルを、Page オブジェクトを表すために使用される TreeView コントロールにバインドすることで応答します。

ハリッハするよ拡大画像が表示され?す

(画像をクリックすると拡大表示されます。)

このようにして、ページの読み込みに必要なアクションが透過的に実行され、ページは、バックエンド ロジックと処理によって完全に読み込まれて Winform に通知された後で UI に表示されます。ページがサーバーから完全に読み込まれると、UI によって、ページにディスクを保存する Pages Menu Item コマンドが切り替えられます。この処理は、PageManager の Page オブジェクトの内部コレクションを、前に作成したシリアル化ヘルパ メソッド (SerializeToFile()) に送信するだけで行うことができます。

ページがディスクに保存された後で、サーバーから読み込まれる場合とほぼ同じ方法でディスクからそれらのページを読み込むことができます。つまり、非同期デリゲートが永続 XML へのストリームを開き、それを GetPagesFromXml() ヘルパ メソッドに渡し、そのヘルパ メソッドが Page の SerializableList を返します。この Page の Generic Collection を PageManager の Page の内部コレクションに割り当てることができます。また、PageAddedEventArg でページをバンドルし、BackPack サーバーから到着したときに個々のページがバインドされる場合と同じ方法で Winform の TreeView コントロールにバインドできるように PageAdded イベントを通じて Winform にバブルアップすることで、各ページを Winform に通知できます。

まとめ

これで、事実上この記事で取り上げる機能が完成しました。1) サーバーからページを読み込み、2) ページをディスクに保存し、3) それらのページをディスクから再び読み込むことができます。アプリケーション、具体的には Winform にとっては、サーバーから読み込まれるページとディスクから読み込まれるページに違いはありません。これらは単に、XML として表現されていたオブジェクトです。次の記事では、これらのオブジェクトとの対話について、また、オブジェクトに対して行われた変更をアプリケーションと BackPack サーバー間でマーシャリングする方法について検証します。アプリケーションがオフラインの場合、データを変更するコマンドは XML としてローカルに永続化されます。この XML は、アプリケーションがインターネット接続を回復した後で再び取り込み、サーバーに送信できます。サーバーに対する変更が完了すると、イベントが BackPackGateway によって起動され、PageManager によって処理され、必要に応じて成功/失敗が Winform に通知されます。

ここまでの処理を終えたら、宿題があります。サンプル コードを見てください。ディスクへのページのシリアル化に使用したヘルパ メソッドは、資格情報のシリアル化にも使用されました。これをディスクにもシリアル化して、テストやデバッグの目的でアプリケーションを起動するたびに情報を API キーに貼り付ける必要をなくすことができます。イベント モデルを調べて、データ チャネルを明確に保つ方法と、複数の同時スレッド間で共有変数を使用している場合に発生する可能性のある潜在的な衝突や競合条件を回避する方法を調べてください。次に、テスト用のアプリケーションを準備し、長時間実行アプリケーションがバックグランドで実行されているときの応答性を確認します。これは、次の記事でデータを変更する際の重要な考慮事項です。データは、ローカルに変更された場合にフラグが付けられ、サーバー上で完了/コミットされた後で、変更済みとされている特定のデータ ポイントのフラグを解除するイベントがローカルに起動されます。すばらしいですね。この処理は、オンラインかオフラインかにかかわらず行われるため、BackPack がオフラインのときに変更されたノードが一目でわかります。

それでは、次回お会いできるのを楽しみにしています。コーディングをお楽しみください。

Michael K. Campbell は開発者であり、データ処理に力を注いでいます。プログラミング、SQL Server 関連、および XML で長年の経験があります。現在、彼はほとんどの時間を 3Leaf Development でのエバンジェリズムに取り組んでいます。ご意見については、彼のサイト (http://www.angrypets.com/contact/) (英語) にアクセスするのが一番簡単な方法です。

© 2009 Microsoft Corporation. All rights reserved. 使用条件 | 商標 | プライバシー
Page view tracker