ClickOnceプログラミング 其の弐 | あとはよしなに

ClickOnceプログラミング 其の弐

ClickOnceプログラミング 其の弐 「差分ダウンロード機能の仕組みと罠パターン」


初回は「差分ダウンロード機能」について掘り下げてみます。


では、ちょっとだけ前置きします。


差分ダウンロード機能とは、「変更されてないファイルはそのまま使いたい」という要求に応えた機能です。


アプリケーションのVer1.00を配置して数日後にバグが見つかったので、すぐさまパッチをあてたVer1.01をリリースしたケースを考えてみてください。バグは膨大なアプリケーション全体から見るとたった1行の勘違いが原因で、修正はファイル1つだけだったなんてこと、よくあることです。

※ネットワークを介するテストはしづらいので初期リリースにはよくありがちなんです。いや、言い訳ですけど・・・。


このアプリケーションをネットワークを介してクライアントにダウンロードさせる。このとき、「アプリケーション全部がダウンロードされる」のと「ファイル1つだけダウンロードされる」のでは、通信帯域の圧迫度合が全然違います。


でも、この差分ダウンロード機能はClickOnceで初めて登場したわけじゃありません。

実は前身技術であるノータッチデプロイメントでも差分ダウンロードはありました。


ノータッチデプロイメントでの差分ダウンロードはHTTPヘッダのIf-modified-sinceフィールドを用いて実装されていますので、ファイルタイムスタンプがちょっとでも違うと、全く同じファイルでも別のファイルとして認識されてしまうという問題がありました。リリース担当者が5月1日に配置したファイルと同一のファイルを5月5日に再配置してファイルタイムスタンプが変わっちゃうと・・・。
あとはよしなに

ファイルが別モノとして認識されちゃいます。

ここまでをふまえてClickOnceの差分ダウンロードを考えてみましょう。


前置き終わり。




ClickOnceで差分ダウンロードがファイルを同一か否かを認識する方法は以下です。

テキトーにアプリケーションを公開してみます。Visual StudioでWindowsアプリケーションをすぐさま発行という手抜きっぷりには目をつぶってくださいな^-^;発行が終わったら、発行されたアプリケーションの「アプリケーションマニフェスト(拡張子manifest)」を開いてみます。


<dependency>
 <dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="WindowsFormsApplication1.exe" size="6656">
  <assemblyIdentity name="WindowsFormsApplication1" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
  <hash>
   <dsig:Transforms>
   <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
   </dsig:Transforms>
   <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1 " />
   <dsig:DigestValue>5D3KU1fjkM9nEjFnBgW6VyZC2q4=</dsig:DigestValue>
  </hash>
 </dependentAssembly>
</dependency>>

エントリポイントに関する記載 WindowsFormsApplication1.exe.manifest(ver1.0.0.0)


DigestValueという項目はメッセージダイジェスト値を示しており、この値は以下の2つの用途に使われています。

・DigestValueが同じファイルは同一として、ダウンロードさせない(差分ダウンロード機能)。
・DigestValueと実際のメッセージダイジェスト値を比較して、異なる場合はダウンロードを破棄(改ざん検知機能)。


ちなみに。

このDigestValueはファイル名に依存しないので、ファイル名が異なってもDigestValueが全く同じなら差分ダウンロード機能が働きます(笑)偶然同じ文字列になることはないでしょうけど、万が一同じになったら誤作動するかもしれませんね。

このようにClickOnceの差分ダウンロード機能ではファイルのタイムスタンプは完全に無視されるため、前身技術であるノータッチデプロイメントの弱点を解決しています。


ではでは、ClickOnceで気にしておきたい2つの問題を考えてみましょう。

これが差分ダウンロード機能の罠にあたります。

たいていの場合はそこまで気にするほどのことではないのですが、ネットワーク負荷を考慮しなくてはならない案件だと面倒かもしれないです。


1.もう一度アプリケーションを発行する。

ここでは、先ほどのアプリケーションをもう一度発行してみます。再度アプリケーションマニフェストを見てください。


<dsig:DigestValue>++BqjwzkX5Kbnkc38eiSntYVzu8=</dsig:DigestValue>

エントリポイントに関する記載 WindowsFormsApplication1.exe.manifest(ver1.0.0.1)


なんだか値変わってませんか?
これがVisual Studioを用いてClickOnceアプリケーションを発行した場合に必ずエントリポイントがダウンロードされてしまう原因です。プログラム上では全く変更されていなくとも、エントリポイントは必ずダウンロードされてしまいます。

この問題は以下の方法で対処します。
回避: .NET Framework SDK(またはWindows SDK)のMage.exeもしくはMageUI.exeを使う。
軽減: エントリポイントのサイズを極力小さくするよう、アセンブリ分割する。
受容: それくらい気にしないと言って無視する(笑) ※結構アリだと思っています。

2.クラスライブラリを追加してみる。
ビジネスロジックやデータアクセスロジックコンポーネントのためにアセンブリ分割したことを考えて見ます。
クラスライブラリを追加して、アプリケーションを再度発行します。

クラスライブラリに関する項目が追加されていますので見てみます。

<dependency>
 <dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="ClassLibrary1.dll" size="4096">
  <assemblyIdentity name="ClassLibrary1" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
  <hash>
   <dsig:Transforms>
   <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
   </dsig:Transforms>
   <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1 " />
   <dsig:DigestValue>WhGfB0gihK5vWsqOFS2zKIPVj3M=</dsig:DigestValue>
  </hash>
 </dependentAssembly>
</dependency>

クラスライブラリに関する記載 WindowsFormsApplication1.exe.manifest(ver1.0.0.2)


ではもう一度発行してみます。

<dsig:DigestValue>WhGfB0gihK5vWsqOFS2zKIPVj3M=</dsig:DigestValue>

クラスライブラリに関する記載 WindowsFormsApplication1.exe.manifest(ver1.0.0.3)


DigestValueが一緒ですので差分ダウンロード機能が働きます^-^
さてさて、次はクラスライブラリをリビルドしてから、アプリケーションを再度発行します。

<dsig:DigestValue>+srR4/OyE5TxgCWYHSrfWGHbUHg=</dsig:DigestValue>

クラスライブラリに関する記載 WindowsFormsApplication1.exe.manifest(ver1.0.0.4)


値が変わりました!
さっきと違うところはプロジェクトを「リビルドした」ということだけ。
これだと差分ダウンロードが働きません。興味のある方は試してみてください。
さらに興味のある方はildasmでアセンブリ内にあるmanifestをDFにかけてみてくださいー。

以上より分かるのは、「アセンブリをリビルドしてしまうと、差分ダウンロード機能は決して働かない」ということです。

この問題、効果的な回避策は今のところ見つけられていません。
しかも、この回避策もせっかく作ったアーキテクチャを壊しがちになるのでここには書きません(というかアーキテクトとしてあまり書きたくない・・・)。

本投稿は、問題提起をした段階で終了することにします。

Visual Studioでソリューションの「ビルド」を選択した際に、「リビルドされる範囲」について考えてみます。
以下のような構成で案件共通アセンブリを変更してしまうと・・・。

構成例) エントリポイント→ビジネス用アセンブリ→業務共通アセンブリ→案件共通アセンブリ

有識者の方からのご意見を頂ければ幸いです。