Quantcast
Channel: 荒井省三のBlog
Viewing all 163 articles
Browse latest View live

Windows Azure におけるシンボリックリンク作成について

$
0
0

前回のエントリでは、Windows Azureのローカルリソースを使った場合にパスの長さが問題になるかも知れませんと記述しました。何故かという理由を以下に示します。

  • Windows Azure上のローカルリソースは、リソースドライブに中で「guid.リソース名」という名前が付与されます。このために、指定したリソース名の正式なフォルダー名はかなり長くなります。
  • Development Fabricでは、「%UserProfile%\AppData\Local\dftmp\s0\deployment id\res\instance id\diirectory\リソース名」になります。

Development Fabricを使ったテストでは、長いパスの問題が顕著に発生する傾向にあります。これを回避するには、コマンドプロンプト(cmd.exe)が用意しているコマンドである「mklink」を活用する必要があります。mklinkの良く使うオプションを以下に示します。

  • mklink  シンボリックリンク名  リンク元のファイル名
  • mklink  /d  シンボリックリンク名  リンク元のフォルダ名

mklinkコマンドで作成するシンボリックリンクですが、コマンドの入力時に存在しないファイル名かフォルダー名である必要があります。単純に言えば、新しいファイルシステムのアイテムを作成するので、同名のアイテムがあるとエラーになるということです。無事に作成できると、dirコマンドでは「2011/08/19  16:49    <SYMLINKD>     リンク先名 [リンク元名]」と表示されます。シンボリックリンクを削除するには、rmコマンドやdelコマンドやWindows エクスプローラーを使う必要があります。特にシンボリックリンクのフォルダーを削除するのに、.NET FrameworkのDirectoryクラスやDirectoryInfoクラスを使用してはいけません。その理由は、リンク元のフォルダーが削除されてしまうからです。この理由から、C#などではProcessクラスを使用する必要があります。以下にサンプルコードを示します。

static bool CreateSymbolicLink(string linkDestination, string linkSource)
{
    var cmdexe = new ProcessStartInfo("cmd.exe");
    cmdexe.WorkingDirectory = System.Environment.CurrentDirectory;
    cmdexe.UseShellExecute = false;
    var proc = new Process();
    cmdexe.Arguments = "/c mklink /d " +
                       linkDestination + " " +
                       linkSource;

    proc.StartInfo = cmdexe;
    proc.Start();
    proc.WaitForExit();
}


このサンプルでは、ProcessStartInfo.UseShellExecuteプロパティをfalseにしているために、cmd.exeに対して「/c」オプション付きでmklinkコマンドを設定しています。これをDevelopment Fabricなどで利用する場合は、シンボリックリンクが存在している場合に削除するコードも用意した方が良いでしょう。

var cmdexe = new ProcessStartInfo("cmd.exe");
cmdexe.WorkingDirectory = System.Environment.CurrentDirectory;
cmdexe.UseShellExecute = false;
var proc = new Process();
// シンボリックリンクが存在すれば削除する
if (Directory.Exists(linkDestination))
{
    cmdexe.Arguments = "/c rd " + linkDestination;
    proc.StartInfo = cmdexe;
    proc.Start();
    proc.WaitForExit();
}


この理由は、デバッグ実行する毎にDevelopment Fabricに対するDeployment Idが変化するために、デバッグを終了するとDevelopment Fabricが使用した一時ファイルを削除しだし、リンク元が存在しないシンボリックリンクになってしまうからです。ここまでの例では、UseShellExecuteプロパティをfalseで使用していますが、trueにして使用することもできます。この場合は、コマンドとしてmklinkだけを設定することができます。UseShellExecuteプロパティをfalseにしたサンプルを提示した理由は、エラー処理にあります。サンプルにエラー処理は含めていませんが、Windows Azureのロールインスタンスなどで呼び出す場合にUIが無い前提でエラー処理を厳密に記述する必要があるからです。従って、RedirectStandardErrorプロパティをtrueに設定して、標準エラー出力を処理することでエラー処理を記述する必要があるわけです。


Ruby on Rails on Windows Azure (1)

$
0
0

このところ、色々なことがありまして、Blogも更新していませんでした。タイトルにあるようにRubyをWindows Azureのコンピュート サービスで動かすにはどうしたら良いか、ということについて、何回かにわけて書いて行こうと思います。そもそものきっかけは、7月にあったRuby会議2011の約10日前に秋葉原の某所で開催されたミーティングに参加したことでした。このミーティングの主題は、Ruby on RailsアプリケーションをAzureで動かしてみたら遅かったので、Windows上のRubyは遅いのではないかという話から、実際にベンチマークなどを計測して検証しようというものでした。このミーティングの中で、artonさんが作成されている能楽堂(NougakuDo)というx64 Windows用のRuby on Railsの開発環境を使ってベンチマークを計測しているという話がでました。ミーティング終了後に食事をしながら、能楽堂をAzureに配置して動作させるというホワイトペーパーを作成できませんかという話が出ました。この話がきっかけになって、実際にAzureへ配置した場合にデータベースをどうしたら良いかという検討を私がしました。ここで考えられるのが、次の2種類になります。

  • SQLite:能楽堂にバンドルされている。
  • SQL Azure:何が使えるかは、調べなきゃいけません。

SQLiteを採用した場合の問題点を考えると、Windows Azureがステートレス サーバーであるためデータベース ファイルなどの維持をどうするかという点に行きつきます。また、複数インスタンス(サーバー)を起動した場合にデータの一元管理をどうするかという問題も考えなければなりません。そこで、SQL Server用に使用できるActiveRecord用のAdapterがないかと探してみた結果、activerecord-sqlserver-adapterが見つかりました。このadapterの説明を読むと、SQL Azureにも対応していて今回の用途に使えそうなので、必要なモジュールを調べると以下のものが必要であることがわかりました。

  • TinyTDS:RubyからDB-Libraryを使用するためのモジュール
  • FreeTDS:UNIXやLinuxからSQL ServerやSybase databaseへ接続するためのプロトコルであるTDSの実装モジュール

この2つのモジュールのインストール方法を調べると、x86向けばかりで、このままではx64環境である能楽堂で使用することができません。そこで、artonさんにお願いして能楽堂へバンドルして頂いたのが、NougakuDo 1.0.2になります。この作業と並行して、Azureのコンピューティング サービスへ手動で能楽堂を配置して、Ruby on Railsアプリケーションを作成して、問題なく動作するのを確認していました。確認作業は、以下のような方法をとりました。

  • 実装のないWorkerロールのパッケージを配置。
  • リモートデスクトップで接続して、次の作業を実施。
  • 能楽堂のインストール。
  • Ruby on Rails アプリケーションの作成と動作確認。

Azure上で能楽堂の動作に問題がないことが確認できれば、次は能楽堂で作成したRailsアプリケーションをAzure上へ配置する方法の検討を行いました。検討したことは次のような目標です。

  • Azure のマルチインスタンスへ対応する
  • Railsアプリケーションのアップデートができる
  • 能楽堂のアップデートができる
  • Railsアプリケーションの再起動を含めた運用管理ができる。

あまり参考にしていないのですが、Smarx roleなどもありましたが、私が考える上記のような目標を達成できないので、目標を実現するための実装方法の検討と最小限度のコンセプトプログラムを作成しました。この作成を何とかRuby会議2011までに作成したのが、NougakuDo Companionの最初のバージョンになります。NougakuDo Companion の説明は、次以降のエントリーを考えていますが、能楽堂の特徴を簡単に説明します。

  • x64 対応の Ruby 1.9系で、MSVCRT(Visual C++ 2010)対応。
  • Ennou という http.sysを使用する Web サーバーを Rackハンドラーとして提供。
    仮想ホスト機能(http://host/)とサブディレクトリー機能(http://host/application/)を提供。
  • Rails 3.1という最新環境に対応(1.0.5は、Rails 3.0対応)。
  • Railsに必要なgemパッケージをオールインワンで提供(オフライン開発が可能)。
  • SQL Server用のアダプターをバンドル。

上記に挙げた特徴で、特に素晴らしいと考えているのが Ennouの存在です。Ennouがあれば、IIS との共存も可能ですし、何よりもWindows Upadteだけでパッチを管理することができます。つまり、LinuxではなくWindowsを使う上での運用管理の工数を限りなく、最小限にできるのです。必要なソフトウェアが少なければ、保守対象が削減できますので、仮に脆弱性が発見されたとしても、対策が少なくて済むというメリットを享受することができますから、Railsアプリケーションを運用する上で能楽堂は素晴らしいRailsの開発環境だと言えます。また、Ennouによる効果はこれだけではありません。http.sysを使うことで、x64 Windows のIISと同等の性能を引き出すことが可能になります。つまり、Web アプリケーションとして高速という特徴を持ちます。まさに、WindowsでRailsを運用するのであれば、素晴らしいという言葉以外は思い当たらないのです。欠点があるとすると、使用するgemパッケージにC言語によるモジュールが含まれていた場合でしょう。何故なら、x64対応とVisual C++ 2010対応でモジュールをビルドする必要があり、既存のrakeタスクが失敗するので、利用者がポートする必要があります。そうはいっても、能楽堂に既にバンドルされているモジュールで済んでしまう場合も多いことでしょう。

  • libyaml、gzip
  • sqlite3、openssl
  • freetds、TinyTDS

などがx64ライブラリとしてバンドルされているので、通常の使用では問題が出ないと私は考えています。

最後に能楽堂を開発されているartonさんによるホワイトペーパーが、MSDNで昨晩から公開されています。来月から、HTML化した記事でも公開を予定しています。Ruby on Rails アプリケーションに携わっていらっしゃる方は、是非ともご覧ください。また、公開されたホワイトペーパーに数か所の誤字がありまして、来週以降に差し替えをする予定になっていますので、この点もご了承ください。誤字の変更箇所は、p16で2ヶ所、p17で1ヶ所の合計3ヶ所を「ノンプリエンティティブ」の表記を「ノンプリエンティブ」に変更させていただきます。ご指摘いただいた、猪俣さんありがとうございました。

Ruby on Rails on Windows Azure (2)

$
0
0

前回のエントリーでは、能楽堂の概要と特徴を説明して、RailsアプリケーションをWindows Azureに配置する場合に考えた目標を説明しました。なぜ、このような目標を考えたのかを最初に説明します。Ruby on RailsをWindows Azureで動作させる情報は、検索するとたくさん見つけつることができます。たとえば、Ruby on Rails in Windows Azureなどでは、RubyのインストールからRails環境の構築と Windows Azureへ配置するための Azure用のアプリケーション パッケージの作成方法までを解説しています。この方法でも、技術的にはRailsがWindows Azureで動作するという観点からは良くできていると私も考えています。でも、Railsアプリケーションを開発されている方の立場になって考えた場合は、どうなんだろうという疑問があるのも事実です。私が考えるRailsアプリケーションの開発者は、アプリケーションの開発から運用を以下の手順で行うと思います。

  • 開発環境の準備(RubyやRuby on Rails、データベース環境)。
  • Railsアプリケーションの開発とテスト
  • 本番環境への配置
    自前でサーバーを用意するか、ホスティング環境を用意して、環境を整えてからアプリケーションを配置

このように考えた場合に、Windows Azure アプリケーション開発環境を理解するというのはRailsアプリケーション開発以外に学ばないといけない知識になってくるわけです。つまり、余計な負担が生じてしまうのです。もちろん、採用したプラットフォームを最大限に生かすのであれば、それらの知識は必要不可欠です。でも、Railsアプリケーションのホスティング先としてのWindows Azureを考えた場合は、必要のない知識だと言えるでしょう。このように考えた結果が、RailsアプリケーションをWindows Azureへ配置するためのツールとして NougakuDo Companionの設計目標にまりました。つまり、NougakuDo Comapnionを使う上で、利用者が学ぶべき知識は以下の事柄になります。

  • Windows Azure コンピュートサービスの製品知識(仮想マシンのサイズなど)とVisual Studio 2010の操作
    Visual Studio のプロジェクト プロパティで仮想マシンサイズを変更して、設定情報を変更できる。
    Visual Studio でAzure用のアプリケーションパッケージの作成ができる。
  • Windows Azure 管理ポータルの使用方法
  • ストレージ サービスの使用方法(たとえば、Azure Storage Explorerなどのツールの操作)

つまり、Windows Azure のWeb ロールなどの知識やC#などのプログラミング言語の知識がなくても、Visual Studioの操作方法とWindows Azure管理ポータルを使った操作ができれば、RailsアプリケーションをWindows Azureへ配置することができるようになります。この程度であれば、セルフサービスとしてのホスティング環境として多くの人に検討してもらえるのではないか、と考えたことがNougakuDo Comapnionの設計目標になったのです。

NougakuDo Comapnionの最初のプレビューが動き出したのが、Ruby会議2011の直前だったことは前回のエントリーに記載しました。もちろん、この時点ではNougakuDo Companionという名前もなく、配置ツールとして最小限の機能だけで管理用のツールも無い状態でした。それから、配置ツールとしての基本機能の実装が完了したのが7月末でした。次に取り掛かったのが、管理機能のユーザーインターフェースで、アルファ版としてはASP.NET MVC 2で実装しました。基本的な管理機能の実装と合わせて、配置ツールの機能拡充が完了したのが、8月の初めで、使用したAzure SDKは1.4でAzure Tools は1.3でした。この頃にリリースされた能楽堂のバージョンは、1.0.4だったと記憶しています。また、Azure Toolsも1.4がリリースされていましたので、開発環境をAzure SDK 1.4とAzure Tools for Visual Studio 2010 1.4へ切り替えて、管理機能をASP.NET MVC 3へとバージョンアップしました。この理由は、能楽堂のポリシーと同じで、その時期に入手できる最適な環境で開発するということを前提にしていたからです。また、この時期にブレークスルーキャンプに参加してRuby on Railsで開発しているチームにヘルパーツールとして早期リリースとして提供しました。彼らのフィードバックから、NougakuDo Comapnionの改善や機能追加を行ったのはいうまでもありません。8月の後半になると、能楽堂も1.0.5がリリースされて、9月にブレークスルーキャンプの決勝がありますから、それまでにNougakuDo Companionを完成させなければなりません。そうして、リリースしたのがRC3になります。このRC3からが、codeplexで一般公開したバージョンになります。

次に大きな機能改善として、能楽堂が1.1になりました。能楽堂1.1系は、それまでの1.0系と違ってEnnouによる仮想ホスト機能のサポートが入りました。仮想ホスト機能とは、artonさんの言葉を借りればApache をリバースプロキシとして動作させてPassengerなどでRailsを動かす、Railsの黄金パターンになります。つまり、Ruby on Rails環境を作るための定石が、能楽堂によってサポートされたのです。この仮想ホスト機能に対して、能楽堂1.0系がサポートしていたのがサブディレクトリー機能になります。つまり、URLを使って表記すると以下のようになります。

  • 仮想ホスト機能:http://  ホスト名 /
  • サブディレクトリー機能: http:// ホスト名 /アプリケーションルート

それとRuby on Railsが3.0.xから3.1系にバージョンアップしたのも、能楽堂1.1系になります。能楽堂1.1系に対応するために、NougakuDo Companionでも仮想ホスト機能のサポート機能を追加しながら、artonさんと協力しながら能楽堂1.1系のブラッシュアップを行っていたのが、ちょうどうMicrosoft Conference 2011の頃になります。中でも、Rails 3.1系での相対ディレクトリーモードでは、かなりartonさんにお世話になりました。ホワイトペーパーに書かれていますが、Ruby on Railsは、仮想ホストを中核としていますが、相対ディレクトリーもサポートしています。しかし、Ennou1.1で思うように相対ディレクトリー サポートが動作しなかったことから、Railsのデバッグを含めて様々な改善を行って頂いた結果として、Ennou1.2となり能楽堂1.1.7へのリリースへとなりました。能楽堂1.1.7をサポートすべく、NougakuDo Companion もRC4のリリースをしました。これが、ホワイトペパーの最終確認をしていた時期になります。公開したホワイトペーパーがまとまった時点で、最終的な動作確認を行っていた時に、SQL Azureへの接続に問題があることを発見しました。色々と調べた結果、activerecord-sqlserver-adapterにバグが入り込んでいることを見つけました。具体的には、以下のような状況でした。

  • 3.0.15:Rails 3.0対応で問題は無い。
  • 3.1.0と3.1.1:DBCC command 'useroptions'のエラーが発生。
  • 3.1.2:バグ対処済み。

この問題は、日付型の書式を設定する機能が追加されており、この機能を実現するために「DBCC useroptions」コマンドを無条件に呼び出していることが原因でした。この問題をactiverecord-sqlserver-adapterの開発チームにバグ報告をしてから、ホワイトペーパーには暫定パッチを掲載して公開作業に入りました。最終的には、開発チーム側でバグ対処済みの3.1.2がリリースされており、次の能楽堂に含まれる予定となっています。また、暫定パッチはNougakuDo Companion RC4のソースツリーにも入れていますので、パッチを使うことで問題なくSQL Azureを使用することができるようになっています。次回は、NougakuDo Companionの具体的な説明をする予定です。

Ruby on Rails on Windows Azure (3)

$
0
0

(1)(2)のエントリーで、どのいう経緯でホワイトペーパーNougakuDo Companionが出来上がってきたかを説明しました(まあ、前置きが長い気がしなくもありませんが...)。今回は、NougakuDo Companionを説明します。最初に���NougakuDo Companionの概要図を示します。

NougakuDoCompanion

NougakuDo Companionは、図に示すように大きな機能は4つに分類することができます。それぞれの機能は、以下のような役割を持っています。

  • AdminWeb:管理サイト(ポート8080)で、能楽堂で作成したRailsアプリケーションの運用管理や、NougakuDo Comapnionのログ情報などを参照できる、ASP.NET MVC 3による Web アプリケーションです。マルチインスタンスで起動したRailsアプリケーションへの操作(再起動など)は、該当するインスタンスに対してメッセージをQueueストレージへ送信します。
  • Web Role:Windows Azureによって起動されるロールで、NougakuDoControllerを動作させるための準備作業とNougakuDoControllerを起動します。また、マルチインスタンスで起動した場合のRailsアプリケーションに対する操作(再起動など)メッセージをQueueストレージから取得します。また、マルチインスタンスの管理用にインスタンス情報をTableストレージに格納します。
  • NougakuDoController:NougakuDoCompanionの中核機能を実装した実行ファイル(exe)で、能楽堂で作成したRailsアプリケーションに対する様々な操作を行います。
    1)インストール:Blobストレージから、能楽堂ランタイム(実行ファイル)やRailsアプリケーションのzipファイルを取得して、ローカルリソースに展開します。
    2)設定:能楽堂のローンチャで行う設定情報をAdminWebで設定した情報を使って設定します。
    3)運用管理:NougakudoLauncherを使って起動したRailsアプリケーションに対する停止や起動、再起動などの操作を行います。
  • NougakudoLauncher:能楽堂を使ったRailsアプリケーションの起動を行う実行ファイル(exe)で、停止操作ではruby.exeにCTRL+Cを送る機能を持っています。

最初のプレビューは、NougakuDoControllerとNougakudoLauncherです。この2つが動き出せば、能楽堂を使ったRailsアプリケーションを動かせますので、このプレビューを使ってローカル環境でのテストとAzureを使ったテストを行っていたのは、いうまでもありません。唯、このプレビューでの失敗もありました。それは、当初からAzureを前提としていたために SDK のStrage Emulatorを想定していなかったことです。このプレビューに、応急処置的にASP.NETのUIを付けたものを artonさんにお渡しして、SDK 環境で試せないの?と質問された時に、慌てて対応したのが、7月後半のシアトル出張の真っ最中だったのは内緒です。NougakuDoControllerやNougakudoLauncherを単独で実行できるように実装したのは、明確な理由があります。その理由は、以下のような考えを前提としたからです。

  • シンプルであるべき(Simple is best)
  • やり易い環境であるべき

ご存じのようにAzureはクラウド環境ですから、インテリトレースが使えると言ってもデバッグを考えると難しいのも事実でしょう。だからこそ、シンプルで実装し易い環境へ持ってくるべきだと考えたのです。この考えから、NougakuDoControllerは、Azure SDKで提供されるAPIからストレージサービスAPIしか使用していません。では、Azure特有のインスタンス情報などをどうしたかといえば、azure.xmlという設定ファイルから情報を取得するようにしています。つまり、ロールインスタンスのコードでazure.xmlを作成することで、Azureのサービスランタイム APIに依存しないようにしたことになります。この特徴が、Azure以外の環境に持っていく場合に効果を発揮できるでしょうし、NougakuDoController単体でのデバッグを容易にするという効果を発揮しました。利点ばかりではなく、もちろん欠点もあります。それは、管理サイトやWeb ロールとの連携をどのように行うかということです。基本的に、別プロセスとして動作していますから、プロセス間連携をどのように実装するかという課題です。
この課題に対して採用した方法が、ファイルを介して連携するという方法です。共有メモリなどを使うよりも、シンプルで、実装コストもかかりません。ファイルを使った連携で注意しないといけないのが、ファイルの排他ロックの問題です。この理由から、FileSystemWatcherクラスを使ったCreatedイベントを多用しています。一般的に、FileSystemWatcherクラスのイベントは、次のように発生します。

  • Created:空の新規ファイルを作成(Windows エクスプローラーで新しいテキストファイルを作成)
  • Changed:内容を出力した場合(メモ帳で内容を書き換え、上書き保存した場合など)
  • Renamed:ファイル名を変更(Windows エクスプローラーで新しいテキストファイルを作成して、直後にファイル名を変更など)

という順序で、イベントが発生します。この理由から、ファイル名に意味を持たせて空のファイルを作成することでプロセス間連携に利用しています。もちろん、連携ミスなどを考慮して空のファイルを作成する前に、既存のファイルを削除してから作成するようにして目的のファイル システム イベントを発生させるようにしています。ローカル環境ではスムーズに動作しましたが、実際にAzure上で動作させた場合に問題も発生しました。それは、ファイルロックが発生して例外が発生したことです。この原因は、可能性として仮想マシンであるがためにファイル システム イベントの発生タイミングに遅れがあることではないかと考えて、リトライロジックを組み込んで回避しています。

NougakuDoControllerがRailsアプリケーションを管理する上で重要なものは、アプリケーションの設定ファイル(configuration.xml)です。この設定ファイルは、管理サイトの「NougakuDo Runtime & Apps」でも設定することができます。NougakuDoControllerでは、この設定ファイルを使って以下のようなことを行っています。

  • バージョンチェック(シンプルに、バージョンが一致するかしないかの判断です)
    標準で5分間隔でBlobストレージに対してチェックを行います。
  • バージョンが一致しなければ、以下の作業を実施。
  • zip ファイルをBlobストレージからダウンロード
  • Rails アプリケーションであれば、アプリケーションの停止
    NougakuDoそのものであれば、全てのRails アプリケーションの停止
  • zip ファイルの展開を含めたインストール作業
  • Rails アプリケーションの起動

それとNougakudoLauncherをRailsアプリケーションの起動用に独立した実行ファイルにしたのには、明確な理由があります。それは、GenerateConsoleCtrlEvent というWin 32 APIになります。このAPIを使用すると、アプリケーションに「CTRL+C」というブレークシグナルを通知することができるのですが、このAPIを呼び出すと呼び出したプログラムを含めてブレーク シグナルを受取ってしまうために、ruby.exeだけでなく呼び出した自分自身も終了してしまうのです。この理由から、非常に軽いEnnnouの起動用にNougakudoLauncherを別プロセスとして実装したのです。

管理サイトは、Windows Azure Companionをモチーフにしてシンプルな作りにしています。管理機能自体は、次のような機能を提供します。

  • NougakuDo Runtime & Apps:設定ファイルの内容を確認したり、更新します。更新した場合は、Blobストレージへアップロードします。
  • Controller Logs:NougakuDoControllerのログファイルを日付の降順に出力します。リフレッシュ間隔を20秒に設定しています。
  • NougakuDo Status:NougakudoLauncherを使って起動したRailsアプリケーションの稼働状況を表示します。また、停止、再起動などの操作を行うことができます。操作を選択すると、操作内容をQueueストレージへメッセージ登録します。リフレッシュ間隔を1分に設定しています。
  • Cleanup:設定ファイルをBlobストレージから削除します。こうすることで、NougakuDoControllerが次のバージョンチェック時に設定ファイルが削除されているのを検知して、起動済みのRailsアプリケーションの停止作業を行い、設定ファイルなどの削除を行います。

それから図に記載していないものとして、ヘルパーツールの「NougakudSetupTool」があります。このツールは、WPFを使ったデスクトップ アプリケーションで、能楽堂やRailsアプリケーションのzipファイルの作成と設定ファイル(configuration.xml)の作成を行うためのものです。出来上がった、zip ファイルやconfiguration.xmlをBlobストレージへアップロードすれば、後はNougakuDoCompanionがconfiguration.xmlをチェックした時点でアプリケーションの展開と起動が行われることになります。

NougakuDo Companionは、内部的にプロセス連携をファイルを使用して行っていることうを既に説明しました。このような構造にしている関係からも、内部的には以下のようなログファイルを用意しています(ログに記録される内容は、リリースとデバッグでは異なります)。

  • NougakuDoRole.txt:ロール インスタンスが出力するログファイルです。
  • NougakuDoController.txt:NougakuDoControllerが出力するログファイルで、管理サイトのContoller Logsで確認ができます。
  • running.txt:NougakudoLauncherを使って起動したRailsアプリケーションに対する情報で、管理サイトのNougakuDo Statusで確認ができます。
  • NougakudoLauncher.txt:NougakudoLauncherが出力するログファイルです。

何か問題があれば、リモートデスクトップなどで接続して上記のログファイルを検討すれば、問題点を明らかにすることができることでしょう。また、マルチ インスタンスを管理するために使用している手法は、以下のようなものになります。

  • ロール インスタンスの起動時に、Tableストレージへインスタンス情報を追加。
  • NougakuDoControllerが、NougakuDoController.txt と running.txt を定期的にBlogストレージへインスタンス名を付与したファイル名でアップロード。
  • 管理サイトは、Tableストレージからインスタンス情報を取得して、Blobストレージからログファイルをダウンロードして表示。
  • ロール インスタンスの停止時に、Tableストレージからインスタンス情報を削除。

マルチインスタンス管理をシンプルにするために、Azureの管理APIではなく、TableストレージとBlobストレージを組み合わせて実現しています。もちろん、ログファイルに関しては、管理サイトが接続されているインスタンスに関する情報はそのインスタンス内から取得することでネットワーク リクエストを削減しています。

それから、能楽堂1.1以降が提供する仮想ホストへの対応方法について説明します。仮想ホストへの対応は、それなりに悩ましくもあったところなので、Azureのコンピュート サービス上で様々な組み合わせを検証しました。能楽堂では、起動時に「o ホスト名」オプションを付けるだけなのですが、その正常に動作する検証結果を以下に示します。

  • 仮想マシンが持つIPアドレス
  • コンピュートサービスに割り当てるDNS名

Azureのコンピュートサービスを作成すると、仮想IPアドレス(VIP)が割り当てられて、指定したDNS名でサービスが出来上がります。仮想IPアドレスはロードバランサが公開するアドレスになっており、APIで簡単に取得することができません(DNSを使って名前を検索すれば、取得することは可能ですが)。また、DNSに対して利用者が独自のCNAMEなどを割り当てることはできません。それから、仮想マシンのIPアドレスはDHCPで割り当てられており、このIPアドレスを設定するとIPアドレスが変更するごとにRailsアプリケーションの再起動が必要になってしまいます。この理由から、DNS名をServiceConfigurationに手動で設定する方法を採用することで、仮想ホスト機能に対応しています。

最後に、NougakuDo ComapnionがWorkerロールではなくWebロールを採用した理由を説明します。NougakuDoController自体は、どちらのロールでも動作するようになっていますが、管理サイトを起動するための手間を削減したかったことが理由です。結果として、権限という問題がありました。というのは、管理サイトはIISで動作しており、Network Serviceがその実態となります。これに対してWebロールは、System権限で動作しています。それぞれが、異なるユーザー権限で動作していることから、ファイルを使ったプロセス連携では、最初にACLの設定を行うことで共有ができるよ���にしています。この権限の問題は、Workerロールで行う限りは発生することはありません。なぜなら、IISが必要であれば自分でIISの設定コードなども作成する必要があるからです。

NougakuDo Companionにおいて不足すると思われるものを考えると、Railsのログファイル参照機能かなとも思っています。が、ログの管理自体は環境に応じて、色々な対応が考えらることから、特に何の機能も用意していません。この点は、使われる方が考えていただければと考えています。現在、NougakuDo CompanionのWindows Azure SDK 1.5対応を行っているところであり、今朝に公開された能楽堂 1.1.9での動作確認ができれば、1.0として公開しようかと考えています。このシリーズは、次の4回目で1.0リリース完了を持って終了する予定です。

Async CTP v3 がリリースされています

$
0
0

Visual Studio Asynchronous Programmingで、Async CTP が v3にアップデートされています。このバージョンの必要条件を読むと、

  • Visual Studio 2010 SP1
  • 4月のAsync CTP SP1 Refreshからのアップグレードが可能

となっていました。実際にインストールしてみると、Async CTP SP1 Refreshに対して、以下のような更新が行われているようです。

  • Silverlight 5 対応
  • Windows Phone SDK 7.1 対応(ライブラリに変更はなかったです)
  • Roslyn CTPとの互換性対応

サンプルなどは、変更されていませんでした。Async CTP SP1 Refreshは、インストール条件にVisual Studio 2010 SP1をインストールしたクリーンな環境が必要だったのですが、v3は現時点の環境で問題なく導入できました。この機会にインストールされてみては、如何でしょうか。

Ruby on Rails on Windows Azure(4)

$
0
0

一応、このシリーズは今回で終了する予定です(本当?)。これまでのシリーズを振り返ると以下のように記述してきました。

  1. 能楽堂がactiverecord-sqlserver-adapterを含めた理由とAzure対応の目標
  2. Azure対応の目標を立てた理由とホワイトペーパー公開直前のバグ騒ぎ
  3. 能楽堂コンパニオンの解説

何とか、能楽堂 1.1.9の動作確認ができたのが先週末でした。能楽堂 1.1.9の動作確認では、私のrails 3.1.1に対する知識が無かったこともあり、artonさんにはとてもお世話になりました。能楽堂 1.1.9では、能楽堂 1.1.7に比べて新しいパッケージが含まれています。具体的には、以下の通りです。

  • Ruby 1.9.3-p0(1.9.3の正式版です)
  • Rails 3.1.1(能楽堂 1.1.7は、Rails 3.1.0)
  • activerecord-sqlserver-adapter-3.1.3 (SQL AzureでのDBCCバグは修正されています)
  • waz-storage-1.1.1(Azureのストレージサービス用のパッケージです)
    これに伴って、rest-client-1.6.7とruby-hmac-0.4.0も追加されています。

私の無知が何かということを説明すると、Assetsに対する動作が Rails 3.1.0 と Rails 3.1.1で変更されていたことに気が付かなったことです。正確かどうかは不明ですが、私が遭遇した現象は以下のようなものです。

  • Rails 3.1.0:Assetsのリクエストを処理していたのがRailsらしい(production.logに記録される)。
  • Rails 3.1.1:Assetsのリクエストを処理するのはRailsからWebサーバーになった(production.logにルーティングエラーが記録される)。

この理由から、Rails 3.1.1 ではAssetsをEnnouに処理させなければなりません。この設定は、config/environments/production.rbで config.serve_static_assetstrueにします。この設定に辿り着くまでに、時間がかかってしまいました。

この設定が理解できたので、NougakuDoCompanionでは以下のような設定方法を追加しています。

  • config/environments/production.rb は、config.server_static_assets を必ず true に設定。
  • config/environment.rb は、Rails 3.1.xの場合に ENV['RAILS_RELATIVE_URL_ROOT'] を設定。
  • config/application.rb は、Rails 3.0.xの場合に config.action_controller.asset_path を設定。

この変更が完了してから、能楽堂 1.0.5、1.1.7、1.1.9で作成した Rails アプリケーションの動作確認ができてから NougakuDoCompanion v1.0として公開しました。公開したcspkgには、Small、Medium、Large、Extra Largeを入れていますので、xsで使用しない限りはソースコードからビルドする必要がなくなっています。もちろん、ソースコードも公開しています。

ホワイトペーパーの環境は、能楽堂 1.1.7なのでRailsは3.1.0になります。Rails が 3.1.1になったことで、Assetsのプリコンパイルに関する手順で変更があり、具体的な内容を以下に示します。

# NougakuDo - prompt で作業を行います
use_closure アプリケーションのディレクトリー名

# use_closureを使用しない場合は、config/environemnts/production.rb を以下のように編集します
config.assets.compress = false


この理由は、uglifier-1.0.4 の JScript対応に問題があるためです。この理由から、能楽堂 1.1.9では、use_closureコマンドを用意して JavaScript の圧縮をclosure-compiler で行うように変更することができます。closure-compiler を使用するためには、JRE 6 が必要になりますので、「rake assets:precompile」 コマンドを実行する前に java.exeに対するパスを追加しておく必要があります。
closure-compilerは、JavaScriptの圧縮に必要になるだけで、Railsの実行時に JRE 6 を必要としません。つまり、Azureのコンピュート サービスにJREは必要がないということです。 

それから能楽堂 1.1.9で追加されたwaz-storageですが、私からartonさんにお願いして追加していただきました。このパッケージは、waz-cmdなどで使用されているもので、せっかくRubyを使用するのですから、Azure Storage Explporerなど使用しないでAzureのストレージサービスにNougakuDoCompanionで必要とする設定情報をアップロードするのとRailsアプリケーションからも使用できるようにしたいと考えたからです。 waz-storageを簡単に使用するためのサンプルを以下に示します。

class Uploader
  require 'waz-blobs'
  def initialize(options = {})
    my_options = {}
    if (options[':use_devenv'])
      my_options[:account_name] = 'devstoreaccount1'
      my_options[:access_key] = 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=='
      my_options[:use_devenv] = true
      my_options[:base_url] = '127.0.0.1:10000'
      my_options[:use_ssl] = true if options[:use_ssl]
    else
      my_options = options.dup
    end
    WAZ::Storage::Base.establish_connection!(my_options)
  end

  def connect?
    WAZ::Storage::Base.connected?
  end

  def upload(name, filename)
    fname = File::basename(filename)
    container = get_container(name)
    File.open(filename, 'rb') do |fp|
      container.upload(fname, fp, 'application/octet-stream')
    end
  end

  def get_container(name)
    container = WAZ::Blobs::Container.find(name)
    container = WAZ::Blobs::Container.create(name) if container.nil?
    container
  end
end

#使い方
uploader = Uploader.new(:use_devenv => true)
uploader.upload('nougakudo', 'C:\data\NougakuDo.zip')



こんな感じで使用します。waz-storageでは、establish_connection!メソッドによって指定したオプション毎に接続がキャッシュされていき、最後の接続がdefault_connectionになります。establish_connectionメソッドを使えば、キャッシュした接続を切り替えたり、新しい接続を作成したりすることができます。使ってみた感じでは、バグが無いとは云いませんが、アップロードやダウンロード用途にはそれなりに使用することができます。SDKのStorage エミュレーターを使う時は、指定するオプションが多いのでサンプルではクラスにしています。Azure上のストレージサービスを使用する場合であれば、:accout_name、:access_keyと:use_sslを指定するだけになります。 NougakuDoComapnionを使用する場合に、是非ともwaz-storageを使用してみてください。

追記:uglifier の最新版である 1.1.0 をインストールして確認したところ、rake assets:procompile コマンドのエラーは無くなっていました。このことから、uglifier-1.0.4 が持っていた JScript対応のバグは解消したと考えられます。uglifier-1.1.0をインストールした場合は、use_closure コマンドの実行は不要になります。

Azure Storage Analytics サービスについて

$
0
0

Winodws Azure のストレージ サービスでは、Storage Analytics サービスが2011年8月より提供されています。リリースされた直後では、REST APIを使って有効や無効を切り替えていましたが、Windows Azure SDK  for .NET(1.6)ではマネージ API が提供されています。  Storage Analytics サービスは、ログ($Logs)がBlob ストレージに記録し、メトリック($MetricsTransactionsBlob、$MetricsTransactionsTable、$MetricsTransactionsQueue、$MetricsBlobCapacity)が Table ストレージに記録されます。 Storage Analytics サービスをマネージ API で有効にするには、以下のようなコードを使用します。Storage Analytics サービスの有効や無効の設定には、少し時間がかかることに注意してください。 

var serviceProperty = new ServiceProperties()
                         {   Logging = new LoggingProperties()
                                   {   Version = "1.0",
                                       LoggingOperations = LoggingOperations.All, 
                                       RetentionDays = 7 },
                             Metrics = new MetricsProperties() 
                                   {   Version = "1.0",
                                       MetricsLevel = MetricsLevel.ServiceAndApi, 
                                       RetentionDays = 7 }
                         };
var blobClient = storageAccount.CreateCloudBlobClient();
blobClient.SetServiceProperties(serviceProperty);


上記のコードは、サービス プロパティの新規のインスタンスを作成して SetServiceProperties メソッドで設定を行っています。設定で重要なことは、RetentionDays(Retation ポリシー)になります。 Storage Analytics サービスは、記録するデータを 20TBを上限として、一杯になると空きができるまで記録を停止します。もちろん、Storage サービスの上限である 100TB とは独立しています。この記録する領域を再利用するための設定が、Retention ポリシーになります。具体的には、RetationDays にデータを保持する日数を設定することで、古いデータは自動的に消去されるようになります。RetatntionDays の上限は、365、即ち 1 年ということになりますから Retation ポリシーを設定しないということは、RetaionDays を 365 に設定したということになります。
Storage Analytics サービスを無効にするには、RetationDays に null を設定します。具体的には、ServicePropertyを以下のようにするのが良いでしょう。

var serviceProperty = new ServiceProperties()
                         {   Logging = new LoggingProperties()
                                   {   Version = "1.0",
                                       LoggingOperations = LoggingOperations.None, 
                                       RetentionDays = null },
                             Metrics = new MetricsProperties() 
                                   {   Version = "1.0",
                                       MetricsLevel = MetricsLevel.None, 
                                       RetentionDays = null }
                         };

Storage Analytics サービスで記録されるデータを取得するには、ログであれば $Logsコンテナーに対して ListBlobs API で Blobのコレクションを取得してから該当する Blob を取得します。記録されるログは、このようなフォーマットになっています。メトリクスに対しては、$Metrics で始まる Tableに対してクエリーを実行してエンティティを取得します。メトリクスは、このようなフォーマットになっています。

Storage Analytics サービスで注意しないといけないのは、記録するデータも Storage サービスとして課金対象になることです。つまり、記録容量とトランザクション(ListBlobs、Tableに対するクエリーなどの API 呼び出し)が課金されるのです。記録するデータの上限が 20TB で Storage サービスの上限である 100TB とは独立していることを既に説明しましたが、このことから類推できることは Storage Analytics サービスの記録場所が Storage サービスから独立していることです。この理由から、Azure Storage Explorerなどでは記録したデータを取得することができません。これは、Storage サービスのアカウントに対して ListContainers API などを呼び出してもコンテナーを取得できないことからも明らかです。これらの事から考えると、Storage Analytics サービスとは、Storage サービス側の課金システムが取得している情報を利用者に提供するサービスであり、課金システム側がデータを記録する仕組みのためパフォーマンスへ影響を与えないものだと理解することができます。

Storage サービスに対するトランザクションの考え方は、Windows Azure Storage Team のBlogに記載があります。細かい部分は割愛しますが、大まかにまとめれば以下のようになります。

  • 全ての REST API ��び出し毎にトランザクションとしてカウントする。
  • リスト系の API は、継続トークンが含まれている場合に +1 が加算される。
  • PutBlob では、32MB以上の場合は データ/4MB(切り上げ) + 1 (PutBlockList)がカウントされる。
  • Table に対するバッチトランザクションは、1 トランザクションとしてカウントされる(SaveChanges)。
  • Tableに対するバッチでないトランザクションは、AddObjectなどが 1トランザクションとしてカウントされる。

特に規模が大きくなる場合は、Storage サービスに対するトランザクション数の見積もりを事前に行って、Storage アカウントが 5 つで十分かどうかを検討した方が良いでしょう。

追記:$Logsコンテナーの内容を参照するには、CloudBerryのフリー版を使用することができます。また、メトリクスについてTableXplorerのフリー版を使用することができます。これらのツール使わない場合は、自分で内容を参照するためのコードを作成する必要があります。

Silverlight から Azure Table Storage Service へダイレクトにアクセスするには

$
0
0

Silverlight から、Windows Azure の Table ストレージ サービスを利用する方法を検討してみました。考えられる方法は、以下が考えられます。

  • Web サービスを経由してアクセスする
    RIA サービス(内部で Table ストレージを使用する) -- Silverlight クライアント
    Web サービス側では、Azure SDK のストレージ クライアント ライブラリを使用できる
  • ダイレクトにアクセスする
    Table ストレージ サービス -- Silverlight クライアント
    Table ストレージは OData 形式なので、 WCF Data サービス クライアントが使えるかも

RIA サービスを使った Table ストレージの利用方法は、探せばサンプルが見つかることでしょう。ここでは、残りの WCF Data サービス クライアントを使う方法を検討します。最初に検討しないといけないのが、リクエスト ヘッダーに対して情報を追加することが可能かという点です。これに関しては、System.Data.Services.Client.DataServiceContext クラスが SendingRequest イベントを公開しているので、このイベントを使用すればできそうです。 SendingRequest イベントは、 引数として sender (DataServiceContextクラス) と e (SendingRequestEventArgs クラス) を取ります。以下にイベントの シグネチャと SendingRequest EventArgs クラスのシグネチャを示します。

// SendignRequest イベントのシグネチャ
private void DataContextSendingRequest(object sender, SendingRequestEventArgs e)
// SendingRequestEventArgs クラスのシグネチャ
public class SendingRequestEventArgs : EventArgs
{
    public WebHeaderCollection RequestHeaders { get; }
    public virtual bool Equals(object obj)
    protected virtual void Finalize()
    public virtual int GetHashCode()
    public Type GetType()
    protected object MemberwiseClone()
    public virtual string ToString()
}


SendingRequestEventArgs クラスのメンバーをデスクトップ用のライブラリと較べると、リクエスト オブジェクトを取得するプロパティが無いことに気が付きます。 Azure の Storage サービスでは、SAS を public にしない限りは認証スキームに従って Authorization ヘッダーを付与する必要があります。Authorization ヘッダーに設定する署名を作成するための CanonicalizedResource を作るには リクエスト URI が必要になります。リクエスト オブジェクトが取得できないため、別の方法でリクエスト URI を取得するか設定しなければなりません。この部分は、Azure Toolkit for Windows Phoneでは、 独自にData Server Client を実装することで解決しています。この辺りの実装方法を Azure Toolkit for Windows Phone を参考にしながら、考えていきましょう。どのように実装するかと言えば、DataServiceContext を継承した TableServiceContext クラスを実装することで、リクエスト URI を取り出せないかを模索してみます(デスクトップ用のライブラリでも TableServiceContext クラスが提供されています)。

public class TableServiceContext : DataServiceContext
{
    public IStorageCredentials StorageCredentials { get; set; }

    // Azure Toolkit の StorageCredentialsAccountAndKey クラスを引数にします
    // StorageCredentialsAccountAndKey.GetEndpoint メソッドは、追加メソッドで
    // 該当する Uri 文字列を返すものです
    public TableServiceContext(StorageCredentialsAccountAndKey credentials)
           :base(new Uri(credentials.GetEndpoint(ServiceType.table))) 
    {   // SendingRequest イベント ハンドラー を設定します
        this.SendingRequest += this.DataContextSendingRequest;
        this.IgnoreMissingProperties = true;
        this.MergeOption = MergeOption.PreserveChanges;

        this.StorageCredentials = credentials;
    }
    private void DataContextSendingRequest(object sender, SendingRequestEventArgs e)
    {
        // SendingRequestEventArgs には、リクエスト オブジェクトが含まれません
        // この実装をこれから考えます
    }
}

DataService クライアントの使用方法を考えると、考慮しないといけない点が何点かあります。それは、以下のような使い方です。

  • DataServiceContext.CreateQuery メソッドでクエリーを作成して、DataServiceCollection<T>.LoadAsync メソッドでの読み込み
  • DataServiceContext クラスの AddObject、DeleteObject メソッドなどの使用。
  • バッチ トランザクション

クエリーを使用するパターンで考えると、クエリーオブジェクト(DataServiceQuery)を取得することでリクエスト URI を取り出すことができますから、DataServiceCollection<T> を継承した TableServiceCollection<T> クラスを定義して、LoadAsync メソッドをオーバーライドしてみます。

public class TableServiceCollection<t> : DataServiceCollection<t> 
{
    public void LoadAsync(IQueryable<t> query)
    {
        // TableDataContext を取得して クエリーオブジェクトを設定する
         var field = query.Provider.GetType().GetField("Context",
                BindingFlags.NonPublic | BindingFlags.Instance);
        var context = (TableServiceContext)field.GetValue(query.Provider);
        context.requestQuery = (DataServiceQuery) query;
        base.LoadAsync(query);
    }
}


DataServiceQuery.Provider オブジェクトは、プライベート フィールド Context に DataServiceContext オブジェクトを保持しています(CreateQuery メソッドが内部的に行っています)。この DataServiceContext オブジェクトをリフレクションで取り出して、TableServiceContext クラスに requestQuery フィールドを追加して DataServiceQuery オブジェクトを設定します。こうすることで、TableServiceContext 側で DataServiceQuery オブジェクトを使用することでリクエスト URI を取り出すことができます。
次に、DataServiceContext クラスの AddObject メソッドなどから リクエスト URI を取り出す方法を考えて、TableServiceContext クラスに以下のようなメソッドを記述します。

// DataServiceQuery 用のフィールド
internal DataServiceQuery requestQuery;
// 処理している EntityDescriptor オブジェクトを取得
private EntityDescriptor GetProcessEntity()
{
    foreach (var item in this.Entities)
    {
        if (GetEntitySaveStatus(item))
        {    return item;    }
    }
    return null;
}
// EntityDescriptor が処理済みかどうか
private bool GetEntitySaveStatus(EntityDescriptor descriptor)
{
    var value = (bool)descriptor.GetType().InvokeMember(
                             "ContentGeneratedForSave",
                             BindingFlags.GetProperty | BindingFlags.Instance 
                                                      | BindingFlags.NonPublic,
                             null, descriptor, null);
    return !value;
}
// EntityDescriptor が保持している entitySetName(テーブル)名を取得 
private string GetEntitySetName(EntityDescriptor descriptor)
{
    var field = descriptor.GetType().GetField("entitySetName",
                           BindingFlags.NonPublic | BindingFlags.Instance);
    var value = field.GetValue(descriptor).ToString();
    return value;
}


GetProcessEntity メソッドは、処理しようとしている EntityDescriptor オブジェクトを取得するメソッドです。EntityDescriptor オブジェクトが処理されると、プライベート である ContentGeneratedForSave  プロパティが true になりますので、このプロパティをリフレクションで取り出しています(これは、バッチトランザクションを指定しない場合の動作になります)。次に、GetEntitySetName メソッドで EntityDescriptor オブジェクトから エンティティセット名(テーブル名)をリフレクションで取り出しています。これは、テーブルに対する操作のリクエスト URI を組み立てるために用意したメソッドになります。ここまで出来たら、TableServiceContext クラスの SendingRequest イベントハンドラを記述します。

private void DataContextSendingRequest(object sender, SendingRequestEventArgs e)
{
    // リクエスト URI を格納する RequestData 構造体を作成します
    var context = (TableServiceContext) sender;
    var data = new StorageCredentialsAccountAndKey.RequestData();
    if (context.requestQuery != null)
    {
        // DataServiceQuery を使ったパターン
        data = new StorageCredentialsAccountAndKey.RequestData()
                   { RequestUri = context.requestQuery.RequestUri };
        context.requestQuery = null;
    }
    else if (context.SaveChangesDefaultOptions == SaveChangesOptions.Batch)
    {
        // バッチトランザクション を��ったパターン
        data.RequestUri = new Uri(this.BaseUri, "$batch");
    }
    else
    {
        var entity = GetProcessEntity();
        // AddObject などの個別のトランザクション
        if (entity != null)
        {
            var name = GetEntitySetName(entity);
            // EditLink プロパティが有効であれば、更新か削除の操作
            if (entity.EditLink != null)
                data.RequestUri = entity.EditLink;
            else if (entity.State == EntityStates.Deleted
                  || entity.State == EntityStates.Modified)
            {
                // 更新か削除操作のリクエスト URI を作成
                var query = string.Format("{0}(PartitionKey=\"{1}\",RowKey=\"{2}\")",
                             name, ((TableServiceEntity)entity.Entity).PartitionKey,
                             ((TableServiceEntity)entity.Entity).RowKey);
                data.RequestUri = new Uri(context.BaseUri, query);
            }
            else  // 追加操作のリクエスト URI を作成
                data.RequestUri = new Uri(context.BaseUri, name);
        }
        else    // EntityDescriptor オブジェクトが null なので、テーブル サービスへの操作へ
            data.RequestUri = new Uri(this.BaseUri, "Tables");
    }
    // 認証ヘッダーの作成
    ((StorageCredentialsAccountAndKey)this.StorageCredentials)
                                     .AddAuthenticationHeadersLite(
                                            data, e.RequestHeaders, true);
}

SendingRequest イベント ハンドラができたら、テーブル サービスに対する操作は以下のようにします。

[DataServiceKey("TableName")]
class TableServiceTable
{
    private string tableName;
    public TableServiceTable() {    }
    public TableServiceTable(string name)
    {   this.TableName = name;    }
    public override bool Equals(object obj)
    {
        if (obj == null)
        {   return false;   }
        TableServiceTable table = obj as TableServiceTable;
        if (table == null)
        {   return false;   }
        return this.TableName.Equals(
                    table.TableName, 
                    StringComparison.InvariantCultureIgnoreCase);
    }
    public override int GetHashCode()
    {  return this.TableName.GetHashCode();   }
    public string TableName
    {  get   {  return this.tableName;    }
       set   {  this.tableName = value;   }
    }
}

// テーブルを新規に作成する
public void CreateTable(string tableName, 
                  Action<CloudOperationResponse<bool>> callback)
{ 
    var context = this.GetServiceContext();
    var entity = new TableServiceTable() { TableName = tableName };
    context.AddObject("Tables", entity);
    context.BeginSaveChanges( asyncResult =>    { 
        try
        {
          var response = context.EndSaveChanges(asyncResult);
         callback.Invoke(new CloudOperationResponse<bool>(true, null));   }
         catch (Exception exception)         {
         callback.Invoke(new CloudOperationResponse<bool>(false,       StorageClientExceptionParser.ParseDataServiceException(exception)));
        }
      }, null);

// テーブルを削除する 
public void DeleteTable(string tableName, 
     Action<CloudOperationResponse<bool>> callback)
{
    var context = this.GetServiceContext();
    var entity = new TableServiceTable() { TableName = tableName };
    context.AttachTo("Tables", entity);
    context.DeleteObject(entity);
    context.BeginSaveChanges( asyncResult => 
      { 
         try 
         {
            var response = context.EndSaveChanges(asyncResult);
            callback.Invoke(new CloudOperationResponse<bool>(true, null));
         }
         catch (Exception exception)
         {
            callback.Invoke(new CloudOperationResponse<bool>(true,  StorageClientExceptionParser.ParseDataServiceException(exception)));
         }
     }, null)
}


コールバックの実装は示していませんが、テーブルサービスに対する扱い方を理解する ことができるでしょう。テーブルを作成する場合は、テーブル名を設定したTableServiceTable クラスのインスタンスを作成してから、AddObject メソッドで追加してから BeginSaveChanges メソッドを呼び出します。一方でテーブルを削除するには、TableServiceTable クラスのインスタンスを作成してから、AttachTo メソッドでオブジェクトへアッタチしてから DeleteObject メソッドを呼び出して削除にマークしてから、BeginSaveChanges メソッドを呼び出すのです。この操作は、テーブルに含まれるエンティティでも同様になります。クエリーを行うには、以下のようにします。

var storageCredential = new StorageCredentialsAccountAndKey();
try
{
   var tableClient = new CloudTableClient(storageCredential);
   _serviceContext = new TableServiceContext(storageCredential);
   var query = _serviceContext.CreateQuery<nougakudo>("nougakudo");
   query.AddQueryOption("$filter", "PrimaryKey='キー' , RowKey='ロー'");
   // AddQueryOption メソッド、もしくは以下のような拡張メソッドでも良い 
    //.Where(x => x.PartitionKey == "キー" && x.RowKey == "ロー");
   var datas = new TableServiceCollection<テーブル名>();
   datas.LoadCompleted += new EventHandler<loadcompletedeventargs>(datas_LoadCompleted);
   datas.LoadAsync(query);
}
catch (Exception ex) 
{ // エラー処理 }


LoadCompleted イベントハンドラを示していませんが、クエリーを扱うのも DataServiceClient と同様に扱えるのがわかることでしょう。全てのパターンを網羅したわけではありませんが、ここに示したような方法を使うことで Silverlight から DataServiceClient のライブラリを使用して Windows Azure の Table Storage サービスへアクセスすることが可能になります。サンプルではリフレクションを使用してプライベート メンバーを取り出していることに注意してください。DataServiceQuery が内部で DataServiceContext を保持することは変わらないと考えられますが、メンバー名などが変更される可能性があるからです。

最後に、Silverlight の DataServiceContext オブジェクトの SendingRequest イベントで リクエスト オブジェクトを提供しない理由ですが、現状の実装がクライアント http スタックと  XMLHttpRequest オブジェクトを使用するハイブリッドになっているためだと考えられます。Ajax などで使用する XMLHttpRequest オブジェクトを使用する条件は、XMLHttpRequest オブジェクトを作成できる(イン ブラウザー)状態で、かつ 同一のホストと通信を行う時です。異なるホストと通信を行う時は、クライアント http スタック(WebRequest.CreateHttp)を使用します。Silverlight 5 では、クライアント http スタックを使用する場合は、プロジェクト プロパティで「ブラウザー内での実行に昇格された信頼を要求する」を設定する必要があります。Azure Toolkit for Windows Phone は、OOB のモードしかありませんので クライアント http スタックを使用していることは言うまでもありません。


Silverlight 4 Toolkit の Silverlight5 へのマイグレーションについて

$
0
0

Silverlight 4 Toolkitは、ソースコードが公開されているコントロール集です。昨年に Silverlight 5 が公開されたこともあって、Toolkit を Silverlight 5 用に移行を検討している方もいらっしゃることでしょう。ソースコードを修正している場合は、Silverlight 5 Toolkit のソースコードを修正するか、Silverlight 4 用のプロジェクトを Silverlight 5 用に移行する必要があります。今回は、Silverlight 4 Toolkit のプロジェクトを Silverlight 5 プロジェクトに移行する方法を簡単に説明します。移行作業を行う前に、考慮しないといけないことがあります。それは、以下のようなプロジェクトです。

  • Silverlight.Control.Sdk ソリューション:System.Windows.Controls プロジェクト、System.Windows.Controls.Input プロジェクト
  • RiaClient.Sdk ソリューション:System.ComponentModel.DataAnnotations プロジェクト、System.Windows.Controls.Navigation プロジェクト、System.Windows.Data プロジェクト、System.Windows.Controls.Data プロジェクト

上記に含まれるプロジェクトが生成するアセンブリは、Silverlight 5 SDK が標準提供するように変更されています。つまり、これらのアセンブリに依存する箇所は、Silverlight 5 SDK のものに切り替える必要があります。これを踏まえて、移行に必要なものを以下に示します。

  • Silverlight 4 Toolkit April 2010のソースコード
  • Silverlight 5 Toolkit December 2011のソースコード

事前準備として、Silverlight 5 Toolkit に含まれる System.Windows.Controls.Toolkit.Internals.dll を Silverlight 4 Toolkit の Binaries フォルダーへコピーしておきます。この理由は、Silverlight 5 プロジェクト向けにビルドされたアセンブリに切り替える必要があるからです。また、Toolkit.snk を Silverlight 4 Toolkit の ソリューション ファイルがあるフォルダーにコピーします(厳密名の署名ファイルを Silverlight 5 Toolkit 用へ変更します)。

それでは、移行準備としてプロジェクト ファイルをテキストエディタで編集します。編集する内容を以下に示します。

  • <AssemblyOriginatorKeyFile>要素
    ..\System.Windows.Controls.snk を ..\Toolkit.snkに変更します。厳密名の署名ファイル名の変更です。
  • System.Windows.Controls.Toolkit.Internalsの<Reference>要素
    Version=2.0.5.0 を Version=5.0.5.0に変更します。バージョン番号は、ビルド用のマジック番号です。
  • <Import>要素におけるMSBuildのTargetsファイルに対するパスをバージョン固有から、バージョン非依存へと変更します。
    $(MSBuildExtensionsPath)\Microsoft\Silverlight\v4.0\Microsoft.Silverlight.CSharp.targets を $(MSBuildExtensionsPath32)\Microsoft\Silverlight\$(SilverlightVersion)\Microsoft.Silverlight.CSharp.targetsに変更します。Silverlight 4 Toolkit が、バージョン固定になっているためです。
  • <None>要素のInclude属性を変更します。
    ..\System.Windows.Controls.snk を ..\Toolkit.snkに変更します。厳密名の署名ファイル名の変更です。

プロジェクト ファイルの編集ができたら、Visual Studio 2010 を使ってソリューションを開きます。ソリューションが開いたら、プロジェクト毎に以下の作業を行います。

  • プロジェクト プロパティで対象の Silevrlight バージョンを Silverlight 5に変更します。これで、参照アセンブリの切り替えが行われます。
  • Silverlight 5 SDK で提供されるアセンブリを使って参照設定をやり直します。
  • ビルドを行ってビルドできることを確認します。

上記の手順をプロジェクトの依存関係に従って処理していけば、プロジェクトを Silverlight 5 用に移行することができます。RiaClient.Toolkit などは、Silverlight 5 用のソースをベースに再修正した方が良いでしょう。また、xxxx.Design ソリューションに関しても、同様の作業を注意深く行っていけば移行を行うことは可能ですが、Silverlight 5 用のソースコードを使うことをお勧めします。その理由は、プロジェクトの構造などがそれなりに変更されていますので、細かい変更点を調べるのが面倒だからです。私の場合は、この手順でコントロールをビルドしてから、Silverlight 5 Toolkit のサンプルを使って動作することを確認しています。

本番環境に適用するには、使用される環境で十分なテストを行ってから切り替えを行うようにしてください。Silverlight Toolkit を利用されている方はご存じだと思いますが、ライセンスがオープンソースとして提供されていますので、ここに示したような移行方法も自己責任で行う必要があるからです。また、Silverlight 5 Toolkit が提供されていることから、Silverlight 4 Toolkit に対する改善は行われなくなると考えられます。これは、新バージョンへの開発リソースが切り替わっているからであり、旧バージョンに対する保守は、必要に応じて使用者が行う必要があります。もし、改善などを加えましたら、是非とも情報の公開をお願いします。

Silverlight アプリケーションのマネージヒープを調べるには

$
0
0

Silverlight アプリケーションパフォーマンスを調べたりするには、以下のような方法があります。

マネージヒープに確保されたオブジェクトのメモリサイズなどを調べるには、SOS デバッガ拡張を使用することができます。SOS デバッガ拡張が使えることをご存じの方もいらっしゃることでしょう。今回は、SOS デバッガ拡張を使って Silverlight アプリケーションのマネージヒープの調べ方をご紹介します。必要になるツールは、Windows デバッガ(WinDbg)です。Windows デバッガは、Windows SDK に含まれていますので Windows SDK を導入する必要があります。

上記に示した URL から該当する Windows SDK をダウンロードしてください。ここでは、Windows 7 x64 環境に Windows SDK 7.1 をインストールします。この時にインストールするオプションは、少なくとも以下のものを選択します。

  • Debugging Tool for Windows
  • Redistributable Packages - Debugging Tools (再配布モジュール)

特に再配布モジュールからデバッグツールをインストールすることを忘れないようにしてください。これは、Windows 7 x64 環境でインストールされるデバッグツールが x64 向けになるためです。インストールが終了したら、「\ProgramFiles\Microsoft SDKs\Windows\v7.1\Redist\Debugging Tools for Windows\dbg_x86.msi」をインストールします。x86 用のデバッグツールをインストールする理由は、デバッグする Silverligjht ランタイムが 32 ビットだからです。これで、SOS デバッガ拡張を使用する準備が整いました。以降が、実際の SOS デバッガ拡張の使用方法になります。

  1. 目的の Silverlight アプリケーションを起動します。
    私の場合は、デバッグ用の HTML を IE9 でブラウズします。
  2. Silverlight アプリケーションが動作している IE9 のプロセス ID を調べます。この作業には、プロセスエクスプローラーか Visual Studio を使用すると便利です。
    Visual Studio の [デバッグ]-[プロセスへアッタチ] の場合
    Attach to process
    型の列に Silverlight が表示されているのが、目的の IE9 のプロセスです。

    プロセス エクスプローラーの場合
    Process Explorer
    プロセス エクスプローラーの下部にモジュールを表示していれば、その中に coreclr.dll が読み込まれているプロセスが該当する IE9 のプロセスになります。

    IE 8 / 9 は、プロセス分離によって子プロセスが生成されるためにこのようになっています。Windows のタスクマネージャのアプリケーションに表示されているプロセスは、親プロセスのため Silverlight をホストしているプロセスでないことに注意が必要です。
  3. [スタートメニュー]-[Debugging Tools for Windows (x86)]-[WinDbg] を起動します。
  4. [File]-[Attach to a process …] を使って、調べたプロセス ID へ接続します。
    WinDbg
  5. Command ウィンドウのコマンドラインに 「.load @"C:\Program Files (x86)\Microsoft Silverlight\5.0.61118.0\sos.dll"」を入力します。
    「@""」の形式でなければ、ダブルバックスラッシュをパス区切りに指定して下さい。x86 Windows 環境であれば、「Program Files」となります。ここのポイントは、sos.dll に対するフルパスで指定することです。
  6. 次にマネージヒープを統計情報を調べるために、「!dunmpheap -stat」コマンドを入力してみます。
    0:039> .load @"C:\Program Files (x86)\Microsoft Silverlight\5.0.61118.0\sos.dll"
    0:039> !dumpheap -stat
    *********************************************************************
    * Symbols can not be loaded because symbol path is not initialized. *
    *                                                                   *
    * The Symbol Path can be set by:                                    *
    *   using the _NT_SYMBOL_PATH environment variable.                 *
    *   using the -y <symbol_path> argument when starting the debugger. *
    *   using .sympath and .sympath+                                    *
    *********************************************************************
    PDB symbol for clr.dll not loaded
    Statistics:
          MT    Count    TotalSize Class Name
    71d3d4fc        1           12 System.Windows.RuntimeHost.HostAppDomainManager
    64a14ba8        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.Int32, mscorlib]]
    64a0bea8        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.UInt32, mscorlib]]
    64a08bb4        1           12 System.Nullable`1[[System.Boolean, mscorlib]]
    64a020dc        1           12 System.Runtime.Remoting.ObjectHandle
    649fe818        1           12 System.Reflection.Missing
    649fccb4        1           12 System.Collections.Generic.ObjectEqualityComparer`1[[System.Object, mscorlib]]
    649f84c8        1           12 System.RuntimeType+TypeCacheQueue
    649f45e0        1           12 System.Resources.FastResourceComparer
    649f3ab0        1           12 System.DefaultBinder
    以下省略
    Total 14491 objects
  7. 今度は、「!dumpheap -type クラス名」コマンドで特定のクラスの情報を確認してみます。
    0:039> !dumpheap -type Sl5test.MainPage
     Address       MT     Size
    16518f84 092f4e9c      100     
    
    Statistics:
          MT    Count    TotalSize Class Name
    092f4e9c        1          100 Sl5test.MainPage
    Total 1 objects
  8. クラス情報からオブジェクトのアドレスが判明すれば、「!dumpobj オブジェクトのアドレス」コマンドでオブジェクトの情報を確認します。
    0:039> !dumpobj 16518f84
    Name:        Sl5test.MainPage
    MethodTable: 092f4e9c
    EEClass:     092f19e4
    Size:        100(0x64) bytes
    File:        Sl5test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    60148494  4000499        4 ...eObjectSafeHandle  0 instance 16518ff4 m_nativePtr
    601486f4  400049a        8 ... System.Windows]]  0 instance 00000000 _valueTable
    以下省略

最後に WinDbg を終了する前にデバッガを停止します(つまり、プロセスからのデタッチです)。参考に示した SOS デバッガ拡張のコマンドを使用することで、マネージヒープの状態を調べることができます。.NET Framework 版の SOS デバッガ拡張との機能差があるとは思いますが、オブジェクトのメモリ使用量などを正確に調べることができることを理解できたことでしょう。

Blog の調子が悪いです...

$
0
0

最近ですが、Blogの調子がおかしいです。
下書き保存した記事が公開できない状態が続いています。私がBlogを書くときは、以下のような方法です。

  1. Live Writer で下書きを保存。
  2. Blogのダッシュボードでタイトルを調整してから、公開。

Live Writerを利用している理由は、画像の埋め込みが簡単だかたです。Blogダッシュボードで記事を書いてから、公開としても、下書き保存したものを公開に設定しても公開されない症状が出ています。この状態でLive Writerから投稿を行うと公開されます。うーん。理由が、よくわかんないです。もしかすると、容量が上限に達したのかもしれませんが。。。。記事は不定期なので、あまり気にしないでください。もしかすろ、他へBlog を移動するかも知れません。

テスト用の証明書の有効期限を更新するには

$
0
0

Silverlight の XAP や ClickOnce で使用する自己署名用の証明書の有効期限が切れた場合に、makecert.exeを使って新しい証明書を作成して使用すると証明書の不一致が発生します。こうなると、以前のアプリケーションをアンインストールしてから新しいアプリケーションをインストールする必要があります。この問題を避けるには、証明書の更新要求を行ってから、証明書を証明機関(CA)から発行してもらう必要があります。一般的に商用のコード署名用の証明書の有効期間は、1年か、長いものでも3年になります。テスト用の自己署名用の証明書の場合は、証明書の更新をmakecer.exeはサポートしていません。この問題に対する記事としては、以下のものがあります。

どちらの記事も Visual Studio 2005 のものになりますし、作成したプログラムの実行には VC 2005のランタイムが必要になります。VC 2005 のランタイムをインストールしていない環境でそうしようかと考えて、証明書の作成を C# で行うサンプルがないかと探していたら、以下の記事がありました。

この記事に添付されている Certificate.cs と 先に提示した記事を参考にしながら、RenewCert というプログラムを作成しました。このプログラムは、コマンドラインで使用するもので、以下のような使い方をします。

  • RenewCert.exe 既存の証明書.pfx 新しい証明書.pfx CN="発行機関" "パスワード" 有効年数
    このコマンドで今日から指定した有効年数が期限の証明書を作成します。
  • RenewCert.exe -c 新しい証明書.pfx CN="発行機関" "パスワード" 有効年数
    makecert.exeと同じように新しい証明書を作成します。

makecert.exe で作成した証明書との違いは、拡張プロパティのAuthority Key Identifier(機関キー識別子)が無いことです。証明書の発行日は、コマンドを実行した日になっており、指定した年数が有効期間になり、更新の場合は元の証明書データを使用しますので public keyは同じものになります。添付したプログラムは、Visual Studio 2010で作成して、Windows 7 と Windiows Server 2008 R2 で動作確認しています。

追記:書き忘れていましたが、鍵長は2048に設定してありますので、1024で試される方はコードを修正して下さい。

実践F# 関数型プログラミング入門の正誤表の補足

$
0
0

技術評論社さんで「実践F# 関数型プログラミング入門」の正誤表がやっと公開されました。正誤表の公開が遅れまして、本当に申し訳ございませんでした。本当なら、9月には公開されないといけなかったのですが、諸般の事情で遅れてしまいました。公開されて正誤表には掲載できなかった箇所がございますので、以下に追加を掲載させていただきます。分量が多いのですが、ご容赦ください。また、諸般の事情から公開が遅れまして、本当に申し訳ございませんでした。

P14

筆者が考える本書の読み方は、1章から読み進めることも、あるいは興味のある章から読んで、必要に応じて他の章を読むというものです。
本書の読み方ですが、1章から順に読み進めてもよいし、興味のある章を読みながら、必要に応じて他の章を読むのもよいでしょう。

P17

参照透過性(参照透明性とも呼ばれます)
参照透明性(参照透過性とも呼ばれます)

P19  1

アルゴリズム実装には関数型プログラミングに影響を受けて
アルゴリズムは関数型プログラミングに影響を受けて

P20  1.1

プログラムング言語
プログラミング言語

P27  図1-4

高級水準言語
高級言語

P27  1.4.1

適材適所で言語が存在するのかということです。
適材適所で言語が存在するのでしょうか。

P28  1.4.2

「解くべき問題」ということです。
「解くべき問題」です。

P28  1.4.2

解き方をモデル化したのがプログラミング言語であるということです。
解き方をモデル化したのがプログラミング言語です

P29  Column

「Abstract Art」
「Abstract art」

P30  1.5.1

メタ言語として開発された。
メタ言語として開発されたものである。

P30  1.5.1

ラムダ計算で証明できるというものです。
ラムダ計算を使って証明できるというものです。

P32  1.5.3

「不遇の関数型言語」と呼んでいます。
筆者は「不遇の関数型言語」と呼んでいます。

P33  1.6.1

回路以外に流れることで他のトランジスタ回路へ影響を及ぼす
回路以外に流れることでトランジスタ回路の動作に影響を及ぼす

P36  1.7.1

しかしながら、Webの世界では
我々が利用しているWebの世界では

P39  1.7.4

不変性(英語でImmutable)という特徴があります。不変性とは、変数に値を代入すると(関数型言語の世界では、代入ではなく束縛と呼びます)値を変更できなくる性質のことです。
不変(英語でImmutable)な変数という特徴があります。不変な変数とは、変数に値を代入すると(関数型言語の世界では、代入ではなく束縛と呼びます)値を変更できないものです。

P54  表2-7

(1行でも複数行)
(1行でも複数行でも可

P81  4.4

bool型に係る演算
bool型に関わる演算

P115  リスト5-1

x |> f
=> f x
 y |> g => (|>) y g // 演算子の関数化 
=> (fun x f -> f x) y g // インライン展開 
=> let x = y in let f = g in f x // インライン展開 
=> g y // 極めて単純なインライン関数でのみ行われる特殊な変換 

P116  表5-1

±1.5×10-45 ~ ±3.4×1038(7桁)
±1.5×10-45~ ±3.4×1038(7桁)

P116  表5-1

±5.0×10-324 ~ ±1.7×10308(15~16桁)
±5.0×10-324~ ±1.7×10308(15~16桁)

P116  表5-1

±1.0×10-28 ~ ±7.9×1028(28~29桁)
±1.0×10-28~ ±7.9×1028(28~29桁)

P129  リスト5-29

> printfn "%6.2d" 123.45;;
12345
val it : unit = ()> printfn "%+6d" 123; printfn "%+6d" -123;;
  +123
  -123
> printfn "%+6d" 123; printfn "%+6d" -123;; +123 -123 

P139  リスト5-47

when句によるガートと網羅性
when句によるガードと網羅性

P141  5.14

全体を全体を{ ... }
全体を{ ... }

P168  5.25.1

failewithで例外を発生させる
failwithで例外を発生させる

P176  図6-1

let func n =
    let f n = seq { 1..n }
              | > Seq.filter (fun n -> 
                   n%3=0 || n%5=0)
    n | > f | > Seq.sum
let func n =
    let f n = seq { 1..n }|> Seq.filter (fun n -> 
                   n%3=0 || n%5=0)
    n |> f |> Seq.sum

P176  図6-2

let func n =
    let f n = seq { 1..n }
              | > Seq.filter (fun n -> 
                   n%3=0 || n%5=0)
    n | > f | > Seq.sum
let func n =
    let f n = seq { 1..n }|> Seq.filter (fun n -> 
                   n%3=0 || n%5=0)
    n |> f |> Seq.sum

P181  6.4

ちなみにF#には、いわゆるbreakやcontinueなど、ループ内で使用する専用の制御文はありません。
脚注と重複した内容のため本文から削除させていただきます

P188  6.7

「(unit -> int)」
(unit -> int)

P193  6.11

束縛に束縛されます
束縛されます

P202  6.17.1

Recourceful.txt
Resourceful.txt

P210  図7-2

7と8のノードの配置について
7と8のノードを入れ替えます

P212  7.3

Queu<'T>クラス
Queue<'T>クラス

P213  7.4

insertSort
挿入ソートは英語でinsertion sortであり、関数名をinsertSortでなくinsertionSortとするのがより適切です。

P240  リスト7-65

Seq.empty;;
> Seq.empty;;

P254  7.19.1

union、diffrence、intersect(集合演算)
union、difference、intersect(集合演算)

P273  8.6.2

オプショナル引数(optional argument)と呼ぶ
オプショナル引数(optional argument)と言う

P284  8.10

リスト8-47のような
リスト8-48のような

P302  8.19

型拡張には、内在的拡張(intrinsic extension)と任意的拡張(optional extension)の2種類があり
本書で参照させていただいた「F#ドキュメント翻訳向上委員会」の最新のご提案では、intrinsic extensionの訳語は固有拡張、optional extensionの訳語は任意拡張となっています。

P306  8.20

単行演算子
演算子

P311  9

.NET Frameworkのライブラリ使い方を何度も前章までで説明してきました。
.NET Frameworkのライブラリを前章までで何度も使用してきました。

P311  9

Framewrokのライブラリは、アセンブリ(物理的な実行ファイルのことで、拡張子がDLLかEXEになります)
Frameworkのライブラリは、アセンブリ(物理的な実行ファイルのことで、拡張子がDLLかEXEになります)

P315  9.2.1

リスト9-1に示した
リスト9-3に示した

P317  Column

リスト9-1とリスト9-2に示した
リスト9-3とリスト9-4に示したに示した

P322  9.3

丸括弧を付けるのが当たり前と説明しまし。
丸括弧を付けるのが当たり前と説明しました。

P323  Column

機能の1として含まれています。
機能の1つとして含まれています。

P333  10

公開する仕様を宣言宣言するものです。
公開する仕様を宣言するものです。

P344  10.1.3

「モジュール宣言が必要」が必要
モジュール宣言」が必要

P346  表10-2

暗黙的にモジュール名をファイル名として使用できる
暗黙的にファイル名をモジュール名として使用できる

P362  図10-7

実装コードとシグネチャの説明文について
実装コードとシグネチャの説明文を入れ替えます

P372  表11-3の補助説明

pati
pati

P384  11.2.1

提供しますから、非同期プログラミングモデルを説明する前に同期プログラミングを振り返ってみましょう。
提供しますが、非同期プログラミングモデルを説明する前に同期プログラミングを見直してみましょう。

P387  11.2.1

.NET Frameworkが提供する非同期プログラミングとは、「BeginとEndという名前で始まるメソッド」を組み合わせることで(Begin/End パターン)実現しますから、複数の非同期プログラミングを組み合わせた場合を考えてみましょう。
.NET Frameworkが提供する非同期プログラミングは、「BeginとEndという名前で始まるメソッド」を組み合わせることで(Begin/End パターン)実現します。では、複数の非同期プログラミングを組み合わせた場合を考えてみましょう。

P395  11.2.3

表11-9に示したWebClient.AsyncDownloadString
11-10に示したWebClient.AsyncDownloadString

P406  11.3

エージェントと呼ばれるます
エージェントと呼ばれます

P420  図11-10

OL
0L

P420  図11-11

OL
0L

P429  12.2

表12-3に示したquery関数
12-4に示したquery関数

P452  表C-1

Val
Var

P459  索引

counBy
countBy

能楽堂 1.2.0 の assets:precompile コマンドについて

$
0
0

能楽堂 1.2.0が2011年12月にリリースされていまして、Rails のバージョンが3.1.2になっています。が、production環境で運用するために必要な「rake assets:recompile」コマンドがエラーとなります。この問題は、rails側にあります。Railsの最新版は、3.2.0なのですが、artonさんが試した限りはassets:precompileコマンドのエラーは解消されていなかったそうです。そこで、artonさんがパッチを作成してくれたのですが、このパッチがRails本体に取り込まれていました。このパッチを使って、railsのバージョンは異なりますが 能楽堂 1.2.0の rails 3.1.2にパッチを当ててみました。結果は、それまでの assets:precompileコマンドとコンソール出力が異なりますが、問題なくアセットのプリコンパイルができて、production環境で動作させることができるようになりました。

  • Rails 3.1.1までは、プリコンパイルのフェーズ毎にコンソール出力がありました。
  • パッチ環境は、コンソール出力はありませんが、publicにassetsディレクトリと結果が正常に出力されます。

パッチは、actionpack/lib/sprockets/assets.rake に投入します。artonさん、有難うございました。

WDD で、アプリのライフサイクルと実行環境の解説を担当します

$
0
0

4/24-25 に開催される Windows Developer Daysで、「アプリのライフサイクルと実行環境の解説」を担当します。このセッションの準備をしている最中ですが、このセッションで取り上げる話題として、以下のものを考えています。

  • アプリの状態遷移(実行中、一時停止、復帰、強制終了など)
  • アプリが起動される仕組みについて
  • アプリの状態の取り扱い
  • バックグラウンド タスク
  • etc

アプリが起動される仕組みについては、Build Windowsの「Windows Runtime internals: understanding "Hello World"」をモチーフにして取り扱うことを考えています。この説明を通して、Windows Runtime でのアプリの起動メカニズムを解きほぐせることでしょう。

早期登録も 4月18日に延長されたことですし、この機会にご参加ください。また、スペシャルトラックでは、1セッションのコーディネーターをしていまして、Azure での Ruby 活用を取り扱います。こちらは、私が話すのではなく、外部の方にお願いしていますが、Ruby の活用事例としても有益になることを目指して、一昨日に3時間の打ち合わせをしていましたので、ご期待ください。


WDD でお話するまでの経緯とデモ内容のフォロー

$
0
0

WDDのアプリのライフサイクルと実行環境に、多くの方が参加していただきまして有難うございました。少し、時間が延びましたが、本当に有難うございました。今回は、この時にお話しできなかった話題と準備作業の舞台裏を記載します。
資料の作成を始めたのは3月からなのですが、デモの準備をしながら資料の見直しやサンプルの作り直しなどを何度も行っていました。大体の準備ができたのが4/13で、翌週に最終チェックと環境整備を行っていました。私がデモで使用したPCは、自前のHP TouchSmart tx2なのですが、この時に結局3回ほどインストールを行っていました。この理由は、マルチタッチのドライバーです。タッチパネルがN-Trig製なのですが、HPから提供されているDuoSenseはWindows 8にインストールすることができません。ドライバーインストールを様々な方法で試した結果、シングルタッチでは動作していましたが、やはりマルチタッチにしたいということから再インストールを実施したのです。このPC特有かも知れませんが、DuoSenseのインストール方法を以下に記載します。

  1. Windows 7 環境でマルチタッチを有効にする(DuoSenseをインストール)。
  2. Windows 8 CPをアップグレードインストールします。
  3. N-Trigが公開しているベータードライバーをインストールします。
  4. マルチタッチが動作しない場合は、再起動してみる。

上記の方法で私の環境では動作していますが、デバイスマネージャーには「N-Trig device Not Win8 supported upgrade it」と表示されています。この表示は、N-TrigのNtrigDigi.infファイルに記載されていまして、ドライバーインストール時に何かの条件に一致するとこのようになるようです。

WDDでお見せしたデモアプリが、どのような動きをしていたかを以下に記載します。

  • アクティベーションのデモ
    アプリの状態には、実行していない(Not Running)、実行中(Running)、一時停止(Suspending)があります。また、以前の実行状態には、終了(Not Running)と強制終了(Terminat)があります。この状態に応じて、タイルのバッジにグリフ(アイコンのような図形です)を表示するというものでした。強制終了(OSが終了させる)した場合に、入力した内容を保存して、次に起動した場合に読み込むという動作もお見せしました。これは、強制終了する場合に、必ずSuspendingイベントが発生することを確認するためのものでした。
  • ライブタイルのデモ
    タイルを更新するためのタイルAPIを使って、更新することと、ローカルキューを介した更新をお見せしました。また、バッジを数字(1から99)で更新することもお見せしました。ここで説明が不足していたのは、タイルを更新してからアプリが終了した場合のタイルがどうなるかという点です。このケースでは、更新したタイルの中身(タイル自体やバッジ)は残ります。つまり、ライブタイルで更新した内容はアプリのインスタンスとは別にシステム側で管理されているということになります。残ったタイルの更新内容を消去するには、タイルAPIを使って更新内容を空にするなどの操作が必要になります。従って、ライブタイルを使用する場合は、有効期限を忘れずに設定するのが良いと私は考えています。
  • スプラッシュスクリーンのデモ
    スプラッシュ スクリーンは、パッケージマニフェストに指定した画像がシステムによって自動的に表示されます。アプリの初期化に15秒以上かかる場合に、強制終了となります。このような場合に行うのが、自前でスプラッシュスクリーンを表示することです。アプリの初期化の終了とは、CPの場合はActivateイベントを抜けるタイミングとなります。スプラッシュ スクリーンAPIを使用することで、スプラッシュスクリーンに指定した画像の表示位置やサイズなどの情報を取得できます。取得した情報を使って、自前のスプラッシュスクリーンとして表示する画像などの位置を調整します。デモでは、意図的に背景色を変えていました。これは、指定した画像ではなく自前で用意したことを明示するための処置でした。実際のアプリで、このようなことをしてはいけません。
  • バックグラウンド オーディオのデモ
    バックグラウンド タスクの利用例として、バックグラウンド オーディオを動作させるデモを行いました。バックグラウンド オーディオを実現するには、パッケージ マニフェストへのバックグラウンド タスクの定義(タスクタイプにオーディオ、スタートページを設定)とコントロールのAudioCategoryの設定とWindows.Media.MediaControlに対するイベントハンドラーの実装を行う必要があります。

バックグラウンド タスクには、タスクを動作させるためのホストプログラムが必要になります。ホストプログラムには、アプリ自身とbackgroundTaskHost.exeの2種類があります。バックグラウンド オーディオは、アプリ自身をホストとしてSystem Control Device の通知を処理するという種類のバックグラウンド タスクになります。そして、バックグラウンド タスクのSDKサンプルからも理解することができますが、backgroundTaskHost.exeがホストできるタスクは、C#、VBやC++で作成したWindowsランタイム コンポーネントだけになります。つまり、JavaScriptで記述したバックグラウンド タスクをホストするには、アプリ自身がホストするしかないということです。これが、SDKサンプルにJavaScript + C# というサンプルが含まれている理由となります。この辺りを時間切れで、WDDでお話しすることはできませんでした。

Windowsストアアプリにおける グリッドアプリケーションについて(1)

$
0
0

久し振りのエントリーになります。これから、何回かに渡ってVisual Studio 2012に含まれる Windows ストア アプリケーションのグリッド アプリケーション テンプレートを紐解いて行きます。

グリッド アプリケーションのナビゲーションは、以下に示すように3階層になっています。
Grid Application Navigation

ナビゲーションに含まるXAMLページは、以下のようになっています。

  • GroupedItemsPage.xaml
    ハブとなるページで、GridViewコントロールとListViewコントロールを持ちます。
  • GroupDetailPage.xaml
    セクションとなるページで、GridViewコントロールとListViewコントロールを持ちます。
  • ItemDetailPage.xaml
    詳細となるページで、FlipViewコントロールを持ちます。

また、各ページとLayoutAwarePageクラスの関係は、以下のようになっています。
Page UML

各ページは、LayoutAwarePageという抽象クラスを継承した構造になっています。このことから、明確になることが1つあります。それは、新しいページを作成した場合は、必ずLayoutAwarePageクラスを継承するようにした方が良いということです。具体的には、検索コントラクトで追加されるSearchResultsPage.xamlなどです。この理由は、LayoutAwarePageを説明していくことで解説をしていきます。
それでは、GroupedItemPage.xaml にある、LayoutAwrePageの定義を見ていきます。

<common:LayoutAwarePage
    x:Name="pageRoot"
    x:Class="App6.GroupedItemsPage"
    DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App6"
    xmlns:data="using:App6.Data"
    xmlns:common="using:App6.Common"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"><Page.Resources><!--このページで表示されるグループ化されたアイテムのコレクションは、グループ内のアイテムを
            仮想化できないため、完全なアイテム リストのサブセットにバインドされます
        --><CollectionViewSource
            x:Name="groupedItemsViewSource"
            Source="{Binding Groups}"
            IsSourceGrouped="true"
            ItemsPath="TopItems"
            d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/></Page.Resources>

この定義で注目したいのが、「DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"」 です。ここで指定されているDefaultViewModel が何処で定義されているかといえば、LayoutAwarePage クラスになります。その定義を以下に示します。

/// <summary>
/// <see cref="DefaultViewModel"/> 依存関係プロパティを識別します。
/// </summary>
public static readonly DependencyProperty DefaultViewModelProperty =
         DependencyProperty.Register("DefaultViewModel", 
           typeof(IObservableMap<String, Object>),
           typeof(LayoutAwarePage), null);



/// <summary>
/// <see cref="IObservableMap<String, Object>"/> の実装です。これは、
/// 単純なビュー モデルとして使用されるように設計されています。
/// </summary>
protected IObservableMap<String, Object> DefaultViewModel
{
    get
    {
       return this.GetValue(DefaultViewModelProperty)
                 as IObservableMap<String, Object>;
    }

    set
    {
       this.SetValue(DefaultViewModelProperty, value);
    }
}


この定義をみれば理解できるように、DefaultViewModelは依存プロパティとして、IObservableMap<string, Object> 型として定義されています。そして、IObservableMap<string, Object>の実装として ObservableDictionary<K, V>クラスを定義しています。この型を簡単に説明するとすれば、ディクショナリー型と同じであり、キーに文字列でデータソースの名前を指定して、値にオブジェクトのコレクションを設定して使用します。DefaultViewModelが、Dictionary<string, object>を使って実装していない理由は、ディクショナリーに変更があった場合の変更通知にあります。変更通知を実装するために、 IObservableMap<string, Object>を継承してObservableDictionary<K, V>クラス を定義しています。GroupedItemPage.xamlのCollectionViewSource定義のSource属性に「{Binding Groups}」と定義されていたことを思い出してください。ここで定義されている「Groups」と文字列が、ObservableDictionary<K, V>クラスのキーになっています。このことから、理解できることは以下のようになります。

  • DataContextで定義しているDefaultViewModel は、依存プロパティであり、LayoutAwarePageクラスで定義している。
  • CollectionViewSourceのSource属性に、IObservableMap<string, Object>型のキーを指定する。
  • ページ定義XAMLのコードビハインドで、DefaultViewModel ["キー"] に対してオブジェクトコレクションを設定する必要がある

このように理解することができれば、次にLayoutAwarePageクラスのOnNavigatedToメソッドを読み解いてみましょう。このメソッドは、Frameを使ったナビゲーションによって呼び出されるメソッドになります。

/// <summary>
/// このページがフレームに表示されるときに呼び出されます。
/// </summary>
/// <param name="e">このページにどのように到達したかを説明するイベント データ。Parameter 
/// プロパティは、表示するグループを示します。</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // ナビゲーションを通じてキャッ��ュ ページに戻るときに、状態の読み込みが発生しないようにします
    if (this._pageKey != null) return;
    var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
    this._pageKey = "Page-" + this.Frame.BackStackDepth;

    if (e.NavigationMode == NavigationMode.New)
    {
        // 新しいページをナビゲーション スタックに追加するとき、次に進むナビゲーションの
        // 既存の状態をクリアします
        var nextPageKey = this._pageKey;
        int nextPageIndex = this.Frame.BackStackDepth;
        while (frameState.Remove(nextPageKey))
        {
            nextPageIndex++;
            nextPageKey = "Page-" + nextPageIndex;
        }
        // ナビゲーション パラメーターを新しいページに渡します
        this.LoadState(e.Parameter, null);
    }
    else
    {
        // ナビゲーション パラメーターおよび保存されたページの状態をページに渡します。
        // このとき、中断状態の読み込みや、キャッシュから破棄されたページの再作成と同じ対策を
        // 使用します
        this.LoadState(e.Parameter, 
            (Dictionary<String, Object>)frameState[this._pageKey]);
    }
}

/// <summary>
/// このページには、移動中に渡されるコンテンツを設定します。前のセッションからページを
/// 再作成する場合は、保存状態も指定されます。
/// </summary>
/// <param name="navigationParameter">このページが最初に要求されたときに
/// <see cref="Frame.Navigate(Type, Object)"/> に渡されたパラメーター値。
/// </param>
/// <param name="pageState">前のセッションでこのページによって保存された状態の
/// ディクショナリ。ページに初めてアクセスするとき、状態は null になります。</param>
protected virtual void LoadState(Object navigationParameter, 
                        Dictionary<String, Object> pageState)
{
}

OnNavigatedToメソッドで重要なことは、ページの状態をチェックすることとLoadStateメソッドを呼び出すことです。LoadStateメソッドの定義を見ると仮想メソッドとして定義されています。よって、LayoutAwarePageクラスを継承したクラスで実装する必要があります。GroupedItemPage.xaml.csのLoadStateメソッドを以下に示します。

/// <summary>
/// このページには、移動中に渡されるコンテンツを設定します。前のセッションからページを
/// 再作成する場合は、保存状態も指定されます。
/// </summary>
/// <param name="navigationParameter">このページが最初に要求されたときに
/// <see cref="Frame.Navigate(Type, Object)"/> に渡されたパラメーター値。
/// </param>
/// <param name="pageState">前のセッションでこのページによって保存された状態の
/// ディクショナリ。ページに初めてアクセスするとき、状態は null になります。</param>
protected override void LoadState(Object navigationParameter, 
                        Dictionary<String, Object> pageState)
{
    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
    var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
    this.DefaultViewModel["Groups"] = sampleDataGroups;
}

このコードでは、SampleDataSource.GetGroupsメソッドを使ってグループ コレクションを取得してから、DefaultViewModelにキーと値を設定することで、Groupsというキーのデータソースを設定していることを理解することができます。

  • LoadStateメソッドは、目的のページにおけるデータソースを作成する責務を持つ
  • OnNavigatedToメソッドをオーバーライドする場合は、LayoutAwarePageクラスのOnNavigatedToメソッドを呼び出すか、同等の処理を記述しなければならない

ここまでで、GroupedItemsPageにおけるXAMLとコードを使ったデータソースの設定に関することを説明しました。まだまだ、説明しきれていないことがありますので、その辺りは次回に説明する予定です。 

Windowsストアアプリにおける グリッドアプリケーションについて(2)

$
0
0

前回は、GroupedItemsPageのLoadStateメソッドまでを説明しました。今回は、GroupedItemsPageのビューに関して説明します。ビューとしては、前回に説明したようにGridViewコントロールとListViewコントロールの2つを用意しています。最初に、GridViewコントロールの定義とデザイン画面を以下に示します。

<!-- ほとんどのビューステートで使用される水平スクロール グリッド--><GridView
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemGridView"
    AutomationProperties.Name="Grouped Items"
    Grid.RowSpan="2"
    Padding="116,137,40,46"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick"><GridView.ItemsPanel><ItemsPanelTemplate>                        <VirtualizingStackPanel Orientation="Horizontal"/></ItemsPanelTemplate></GridView.ItemsPanel><GridView.GroupStyle><GroupStyle><GroupStyle.HeaderTemplate><DataTemplate><Grid Margin="1,0,0,6"><Button
                            AutomationProperties.Name="Group Title"
                            Click="Header_Click"
                            Style='{StaticResource TextPrimaryButtonStyle}' ><!-- 以下 省略 --></Grid></DataTemplate></GroupStyle.HeaderTemplate><GroupStyle.Panel><ItemsPanelTemplate><VariableSizedWrapGrid Orientation='Vertical' Margin='0,0,80,0'/></ItemsPanelTemplate></GroupStyle.Panel></GroupStyle></GridView.GroupStyle></GridView>


Grid Landscape

GridViewの定義で注目するのが、ItemsSourceとItemsTemplate属性の定義になります。

  • ItemSource : CollectionViewSourceの名前であるgroupedItemsViewSourceをデータソースとして指定しています。
  • ItemTemplate : StandardStyle.xamlで定義されているデータテンプレートである Standard250x250ItemTemplateを指定しています

今度は、ListViewコントロールの定義とデザインを以下に示します。

<!-- スナップの場合のみ使用される垂直スクロール リスト --><ListView
    x:Name="itemListView"
    AutomationProperties.AutomationId="ItemListView"
    AutomationProperties.Name="Grouped Items"
    Grid.Row="1"
    Visibility="Collapsed"
    Margin="0,-10,0,0"
    Padding="10,0,0,60"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplate="{StaticResource Standard80ItemTemplate}"
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick"><ListView.GroupStyle><GroupStyle><GroupStyle.HeaderTemplate><DataTemplate><Grid Margin="7,7,0,0"><Button
                            AutomationProperties.Name="Group Title"
                            Click="Header_Click"
                            Style='{StaticResource TextPrimaryButtonStyle}'><StackPanel Orientation='Horizontal'><TextBlock Text='{Binding Title}' Margin='3,-7,10,10' Style='{StaticResource GroupHeaderTextStyle}' /><!-- 以下省略 --/></StackPanel></Button></Grid></DataTemplate></GroupStyle.HeaderTemplate></GroupStyle></ListView.GroupStyle></ListView>


GridSnapped 

デザインを見れば理解できるようにListViewコントロールは、スナップビューとして使用するものになっています。従って、ListViewの定義で注目するのは、ItemSourceとItemTemplateとVisibility属性になります。

  • Visibility :Collapsedを指定して非表示にしています。
  • ItemSource :GridViewと同じgroupedItemsViewSourceをデータソースとして指定しています
  • ItemTemplate :StandardStyle.xamlで定義されているデータテンプレートである Standard80ItemTemplateを指定しています

GridViewコントロールとListViewコントロールに対して同じデータソースを指定して、ランドスケープとスナップにおいて表示・非表示を切り替えることで2つのレイアウトに対応しています。Visual Studio 2012 と Blend for Visual Studio のデバイスタブを使って、ランドスケープ、スナップなどを切り替える場合に重要な役割を持っているのが、VisualStateManagerになります。

<VisualStateManager.VisualStateGroups><!--表示状態には、アプリケーションのビューステートが反映されます --><VisualStateGroup x:Name="ApplicationViewStates"><VisualState x:Name="FullScreenLandscape"/><VisualState x:Name="Filled"/><!-- ページ全体では、縦方向に対して、より狭い 100 ピクセルの余白の規則を優先します --><VisualState x:Name="FullScreenPortrait"><Storyboard><!-- 省略 --></Storyboard></VisualState><!--スナップの場合、[戻る] ボタンとタイトルには異なるスタイルが使用され、
            他のすべてのビューステートで表示されるグリッドに対して一覧の表現が置き換えられます
        --><VisualState x:Name="Snapped"><Storyboard><!-- 省略 --><ObjectAnimationUsingKeyFrames 
                        Storyboard.TargetName="itemListView" 
                        Storyboard.TargetProperty="Visibility"><DiscreteObjectKeyFrame KeyTime="0" 
                        Value="Visible"/></ObjectAnimationUsingKeyFrames><ObjectAnimationUsingKeyFrames 
                        Storyboard.TargetName="itemGridView" 
                        Storyboard.TargetProperty="Visibility"><DiscreteObjectKeyFrame KeyTime="0" 
                        Value="Collapsed"/></ObjectAnimationUsingKeyFrames></Storyboard></VisualState></VisualStateGroup></VisualStateManager.VisualStateGroups>


VisualState の Snappedの定義をみれば、Visibilityを切り替えていることは明らかです。これらの表示状態に関わらずに一貫したデータを表示しているのが、同じデータソースにデータバインドして いる効果になります。実は、VisualStateManagerは Visual Studio 2012やBlend for Visual Studioのデザイナーがデバイスタブで使用しており、実行時にはユーザーコードでXAMLに定義した情報を呼び出す必要があります。この呼び出しを行っているのが、LayoutAwarePageクラスになります。LayoutAwarePageクラスのコンストラクタを以下に示します。

/// <summary>
/// <see cref="LayoutAwarePage"/> クラスの新しいインスタンスを初期化します。
/// </summary>
public LayoutAwarePage()
{
    if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return;

    // 空の既定のビュー モデルを作成します
    this.DefaultViewModel = new ObservableDictionary<String, Object>();

    // このページがビジュアル ツリーの一部である場合、次の 2 つの変更を行います:
    // 1) アプリケーションのビューステートをページの表示状態にマップする
    // 2) キーボードおよびマウスのナビゲーション要求を処理する
    this.Loaded += (sender, e) =>
    {
        this.StartLayoutUpdates(sender, e);

        // キーボードおよびマウスのナビゲーションは、ウィンドウ全体を使用する場合のみ適用されます
        if (this.ActualHeight == Window.Current.Bounds.Height &&
            this.ActualWidth == Window.Current.Bounds.Width)
        {
            // ウィンドウで直接待機するため、フォーカスは不要です
            Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
                CoreDispatcher_AcceleratorKeyActivated;
            Window.Current.CoreWindow.PointerPressed +=
                this.CoreWindow_PointerPressed;
        }
    };

    // ページが表示されない場合、同じ変更を元に戻します
    this.Unloaded += (sender, e) =>
    {
        this.StopLayoutUpdates(sender, e);
        Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
            CoreDispatcher_AcceleratorKeyActivated;
        Window.Current.CoreWindow.PointerPressed -=
            this.CoreWindow_PointerPressed;
    };
}


コンストラクタで Loadedイベントハンドラで StartLayoutUpdatesメソッドを呼び出しているのが確認できます。StartLayoutUpdatesメソッドを以下に示します。

/// <summary>
/// イベント ハンドラーとして呼び出されます。これは通常、ページ内の <see cref="Control"/> の
/// <see cref="FrameworkElement.Loaded"/> イベントで呼び出され、送信元が
/// アプリケーションのビューステートの変更に対応する表示状態管理の変更を受信開始する必要があることを
/// 示します。
/// </summary>
/// <param name="sender">ビューステートに対応する表示状態管理をサポートする 
/// <see cref="Control"/> のインスタンス。</param>
/// <param name="e">要求がどのように行われたかを説明するイベント データ。</param>
/// <remarks>現在のビューステートは、レイアウトの更新が要求されると、
/// 対応する表示状態を設定するためすぐに使用されます。対応する 
/// <see cref="FrameworkElement.Unloaded"/> イベント ハンドラーを
/// <see cref="StopLayoutUpdates"/> に接続しておくことを強くお勧めします。
/// <see cref="LayoutAwarePage"/> のインスタンスは、Loaded および Unloaded イベントでこれらのハンドラーを自動的に
/// 呼び出します。</remarks>
/// <seealso cref="DetermineVisualState"/>
/// <seealso cref="InvalidateVisualState"/>
public void StartLayoutUpdates(object sender, RoutedEventArgs e)
{
    var control = sender as Control;
    if (control == null) return;
    if (this._layoutAwareControls == null)
    {
        // 更新の対象となるコントロールがある場合、ビューステートの変更の待機を開始します
        Window.Current.SizeChanged += this.WindowSizeChanged;
        this._layoutAwareControls = new List<Control>();
    }
    this._layoutAwareControls.Add(control);

    // コントロールの最初の表示状態を設定します
    VisualStateManager.GoToState(
           control, 
           DetermineVisualState(ApplicationView.Value), 
           false);
}

private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
{
    this.InvalidateVisualState();
}


/// <summary>
/// 適切な表示状態を使用した表示状態の変更を待機しているすべてのコントロールを更新し
/// ます。
/// </summary>
/// <remarks>
/// 通常、ビューステートが変更されていない場合でも、別の値が返される可能性がある事を知らせるために
/// <see cref="DetermineVisualState"/> をオーバーライドすることで
/// 使用されます。
/// </remarks>
public void InvalidateVisualState()
{
    if (this._layoutAwareControls != null)
    {
        string visualState = DetermineVisualState(ApplicationView.Value);
        foreach (var layoutAwareControl in this._layoutAwareControls)
        {
            VisualStateManager.GoToState(
                   layoutAwareControl, visualState, false);
        }
    }
}


StartLayoutUpdatesメソッドを見ることで、VisualStateManager.GoToStateメソッドを呼び出しているのが、このメソッドとWindowSizeChangedイベントハンドラだということが理解できます。ApplicationViewStateが、現在のビューの状態(Snappedなど)を表しています。コメントにも記載されていますが、VisualStateManagerで切り替えるビューステートを変更したい場合は、DetermineVisualStateメソッドをオーバーライドして対象のビューステート名(VisualState のx:Name属性)を返すようにすれば良いということになります。ここまでの動きから、VisualStateManagerの動作をまとめると、以下のようになります。

  • デバイスタブは、XAML内に定義されたVisualStateManagerを使用する
  • 実行時は、自分でVisualStateManager.GoToStateメソッドを呼び出す必要がある。
  • LayoutAwarePagaeクラスは、Window.SizeChangedイベントハンドラーでVisualStateManager.GoToStateメソッドを呼び出す 
    (ポートレイトやスナップなどに切り替えると、Window.SizeChangedイベントが発生します)

このような仕組みで表示状態を切り替えていることから、グリッドアプリケーション テンプレートで作成したプロジェクトに新しいページ(xaml)を追加する時に「必ずLayoutAwarePageを継承してください」という説明をしていました。 当然のこととして、ここまでのビューの切替などの仕組みを理解して、自分でビューの切替を実装するのであれば、LayoutAwarePageクラスを継承する必要はありません。実際には、継承することを強くお勧めします。この理由は、次回以降で説明します。

最後に、VisualStateMamangerを使用したコントロールのカスタマイズに関して、簡単に説明します。VisualStateManagerは、最初にSilverlightに導入されて、WPF 4.0でデスクトップ環境にも導入されました。WPF4.0などのドキュメントを読めば理解することができますが、コントロールの外観をマウスポインターが入った時などをxamlだけでカスタマイズすることができます。コントロールの外観をカスタマイズするには、ControlTemplateを定義して、VisualStateManagerを定義する必要があります。またControlTemplate属性とは、Controlが持つプロパティなので、Controlを継承したコントロール(Buttonなど)がVisualStateManagerを使ったノンプログラミングの外観カスタマイズを行うことができます。これ以外のコントロール(たとえば、Gridなどのレイアウト系コントロール)は、何らかのコードを書かないと外観カスタマイズを行うことはできませんので、注意する必要があります。

Windowsストアアプリにおける グリッドアプリケーションについて(3)

$
0
0

前回にLayoutAwarePage.StartLayoutUpdateメソッドが、Loadedイベントで呼び出されることを説明しました。このメソッドは、以下の定義になっていました。。

public void StartLayoutUpdates(object sender, RoutedEventArgs e)
{
    var control = sender as Control;
    if (control == null) return;
    if (this._layoutAwareControls == null)
    {
        // 更新の対象となるコントロールがある場合、ビューステートの変更の待機を開始します
        Window.Current.SizeChanged += this.WindowSizeChanged;
        this._layoutAwareControls = new List(); 
}
this._layoutAwareControls.Add(control);
// コントロールの最初の表示状態を設定します
VisualStateManager.GoToState(control,
      DetermineVisualState(ApplicationView.Value), false);
}


ここで気を付けておくべきことは、「_layoutAwareControls」というメンバー変数です。この変数は「List<Control>」型になっており、LayoutAwarePageクラスがLoadedイベントにより登録されます。これは、LayoutAwarePageがPageクラスを継承しており、Pageクラスが UserControl を継承し、UserControlが Controlクラスから派生しているためです。この機能により、LayoutAwarePageクラスを継承したGroupedItemsPage.xamlで定義されたVisualStateManagerのVisualStateが呼び出されるようになっているのです。 この特徴を理解しておくことは、とても重要です。何故なら、ユーザーコントロールを自作して組み合わせる場合に、LoadedとUnLoadedイベントで「StartLayoutUpdatesメソッドとStopLayoutUpdatesメソッド」を呼び出すように設定することで、ユーザーコントロール内に記述したVisualStateManagerの定義が呼び出されるようになるからです。実際に、グリッドアプリケーションのItemDetailPage.xamlではUserControlを定義してLoadedとUnLoadedイベントハンドラーを設定しています。

GroupedItemsPageクラスで次に説明することは、データソースについてです。データソースの説明に入る前に、GroupedItemsPage.xamlのCollectionViewSourceの定義を以下に再掲します。

<CollectionViewSource
    x:Name="groupedItemsViewSource"
    Source="{Binding Groups}"
    IsSourceGrouped="true"
    ItemsPath="TopItems"
    d:Source=
       "{Binding AllGroups, 
         Source={d:DesignInstance Type=data:SampleDataSource, 
         IsDesignTimeCreatable=True}}"/>


d:Source」属性は、デザイン時のデータソースを指定しています(コード上は意図的に改行しています)。ここに指定されていることの意味は、以下のようになります。

  • Binding:AllGroupsというプロパティ名を指定しています。
  • Source:デザイン時のデータソースを指定しています。
    d:DesignInstance で、型を「SampleDataSource」クラスと指定し、IsDesignTimeCreatableをtrueと指定することで、デザイン時にデータソースのインスタンスを作成することを指示しています。

このことから理解できるのは、SampleDataSource.AllGroupsプロパティが返すコレクションをカスタマイズすることで、デザイン時に表示されるサンプルデータを変更することができるということです。それでは、SampleDataSourceクラスの構造を以下に示します。
SampleDataSource

d:Source属性で説明したように、SampleDataSourceクラスがAllGroupsプロパティを持っていることを確認することができます。また、ObservableCollection<SampleDataGroup>を返すこともクラス図から把握することができます。それでは、SampleDataSourceクラスのコードを以下に示します。

/// <summary>
/// ハードコーディングされたコンテンツを使用して、グループおよびアイテムのコレクションを作成します。
/// 
/// SampleDataSource はライブ プロダクション データではなくプレースホルダー データを使用して初期化するので
/// サンプル データは設計時と実行時の両方に用意されています。
/// </summary>
public sealed class SampleDataSource
{
    private static SampleDataSource _sampleDataSource = new SampleDataSource();

    private ObservableCollection<SampleDataGroup> _allGroups = 
             new ObservableCollection<SampleDataGroup>();
    public ObservableCollection<SampleDataGroup> AllGroups
    {
        get { return this._allGroups; }
    }

    public static IEnumerable<SampleDataGroup> GetGroups(string uniqueId)
    {
        if (!uniqueId.Equals("AllGroups")) throw 
             new ArgumentException("Only 'AllGroups' is supported as a collection of groups");
        return _sampleDataSource.AllGroups;
    }

    public static SampleDataGroup GetGroup(string uniqueId)
    {
        // サイズの小さいデータ セットでは単純な一方向の検索を実行できます
        var matches = _sampleDataSource.AllGroups.
                     Where((group) => group.UniqueId.Equals(uniqueId));
        if (matches.Count() == 1) return matches.First();
        return null;
    }

    public static SampleDataItem GetItem(string uniqueId)
    {
        // サイズの小さいデータ セットでは単純な一方向の検索を実行できます
        var matches = _sampleDataSource.AllGroups.
                      SelectMany(group => group.Items).
                      Where((item) => item.UniqueId.Equals(uniqueId));
        if (matches.Count() == 1) return matches.First();
        return null;
    }

    public SampleDataSource()
    {
        String ITEM_CONTENT = 
                String.Format(
                 "Item Content: {0}\n\n{0}\n\n{0}\n\n{0}\n\n{0}\n\n{0}\n\n{0}",
                    "コンテンツ文字列");

        var group1 = new SampleDataGroup("Group-1",
                "Group Title: 1",
                "Group Subtitle: 1",
                "Assets/DarkGray.png",
                "Group Description: グループ説明");
        group1.Items.Add(new SampleDataItem("Group-1-Item-1",
                "Item Title: 1",
                "Item Subtitle: 1",
                "Assets/LightGray.png",
                "Item Description: アイテム説明",
                ITEM_CONTENT,
                group1));
        // 以下省略
        this.AllGroups.Add(group1);
        // 以下省略
    }
}


コードを見れば理解が進むと思いますが、コンストラクタ内でサンプルデータを作成して、AllGroupsプロパティを使ってサンプルデータを追加しています。これが、ObservableCollection<SampleDataGroup>コレクションへの追加となっているのです。これが、d:Source属性でデザイン時のデータソースを指定していることの意味になります。また、SampleDataSourceクラスが提供する静的メソッドには、以下のものがあります。

  • GetGroups:引数は"AllGroups"のみで、全てのコレクションを取得します。
  • GetGroup:引数はグループのユニークIDとなり、1つのグループを取得します。
    matches.Count() == 1」は間違いで、正しくは「matches.Count() >= 1」となります。
  • GetItem:引数はアイテムのユニークIDとなり、1つのアイテムを取得します。
    matches.Count() == 1」は間違いで、正しくは「matches.Count() >= 1」となります。

静的メソッドは、_sampleDataSourceという静的なメンバー変数を使ってSampleDataSourceクラスのインスタンスへアクセスしています。現実的なアプリに改造するには、特に以下の点に注意します。

  • コンストラクタで作成しているサンプルデータをWindows.ApplicationModel.DesignMode.DesignModeEnsbled が trueの場合だけに作成するようにします。
  • アプローチは色々とありますが、実際のデータを読み込むメソッドを追加します。この時に、読み込みが終了したかどうかを判定するために「IsInitialized」などのプロパティを追加した方が良いでしょう。

開発体験テンプレートのNewsReaderでは、説明した意図でIsInitializedプロパティとLoadRemoteDataAsyncメソッドを用意しています。SampleDataGroupは、Itemsプロパティ(ObservableCollection<SampleDataItem>)を持っています。これが、グループが持つアイテムのコレクションとなっています。また、クラス図から理解できるように、SampleDataGroupクラスとSampleDataItemクラスは、SampleDataCommonクラスを継承しており、SampleDataCommonクラスはBindableBaseクラスを継承しています。BindableBaseクラスは、「INotifyPropertyChanged」インターフェースを実装しています。またクラス図から理解できますが、ObservableCollectionもINotifyPropertyChangedインターフェースを実装しています。XAMLとのデータバインディングを行う上で、INotifyPropertyChangedインターフェースを実装していることが重要なポイントなります。この理由は、一回限りのバインディング(OneTime)だけではなく、一方向(OneWay、これがデフォルト)、双方���(TwoWay)バインディングを実現するために必須となるからです。データバインドしたコレクションのメンバーに対して、データを変更したり、メンバーの追加や削除を行う場合に、XAMLに対して変更通知を行うことでビュー(XAMLの視覚要素で、表示コントロール)が更新されるようになるのです。この変更通知を実現するのが、 INotifyPropertyChangedインターフェースを実装するという意味になります。これが、MVVMパターンにおけるビューモデル(VM)の特徴になっています。

SampleDataGroup、SampleDataItemの関係は、循環参照となっています。具体的には、SampleDataItemのプロパティにGroupが定義されており、これがSampleDataGroupのインスタンスを示すようになっています。このような構造になっている理由は、ItemDetailPage.xamlからGroupDetailPage.xamlへのナビゲーションを実現するためです。データソースを現実のアプリに合わせて改造するには、SampleDataGroup、SampleDataItem、SampleDataCommonクラスなどに対して、メンバーの追加や変更を行うことが多いことでしょう。この場合の行うべき定石のコードを以下にしめします。

        private string _description = string.Empty;
        public string Description
        {
            get { return this._description; }
            set { this.SetProperty(ref this._description, value); }
        }


プロパティ セッターで、必ず「 this.SetProperty(ref メンバー変数, value)」を記述します。SetPropertyメソッドは、BindableBaseクラスで定義されており、OnPropertyChangedメソッドを使ってプロパティの変更イベントを呼び出します。この変更通知が行われることで、XAMLの視覚要素へ変更通知が送られるようになります。ですから、SetPropertyメソッドをセッターに記述することが重要なのです。また、SampleDataCommonクラスは、Imageプロパティを持っていますが、永続化を考えるとImageSourceのインスタンスをシリアライズすることはできません。よって、SetImage(イメージファイルへのパス)メソッドをデシリアイズでは呼び出すようにします。もしくは、ImagePathプロパティを追加して、セッターでImageプロパティに対してOnPropertyChangedを呼び出すようにしても良いでしょう。

Windowsストアアプリにおける グリッドアプリケーションについて(4)

$
0
0

前回にデータソースの説明をしました。SampleDataSourceの構造が理解できましたので、改めてGroupedItemPage.xaml.csのLoadStateのコードを振り返ります。

protected override void LoadState(
                        Object navigationParameter, 
                        Dictionary<String, Object> pageState)
{
    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
    var sampleDataGroups = SampleDataSource.GetGroups(
                               (String)navigationParameter);
    this.DefaultViewModel["Groups"] = sampleDataGroups;
}


SampleDataSource.GetGroupsメソッドは、静的メソッドになっていますから、呼び出されるとメンバー変数の_sampleDataSourceのAllGroupsプロパティの値を返します。そして、 _sampleDataSourceメンバー変数は静的変数となっており、SampleDataSourceクラスのインスタンスを保持しています。_sampleDataSourceメンバー変数が静的変数となっている点が重要です。何故なら、静的変数はアプリケーションドメイン単位に保持されますから、異なるページのインスタンスへナビゲーションしたとしても_sampleDataSourceは、同一のインスタンスを保持し続けるからです(要は、キャッシュの効果があるのです)。

また、AllGroupsメソッドは引数が"AllGroups"であることが要求されますので、このパラメータがどのように渡されるかを確認するためにApp.xaml.cs の OnLaunchedイベントハンドラーを以下に示します。

/// <summary>
/// アプリケーションがエンド ユーザーによって正常に起動されたときに呼び出されます。他のエントリ ポイントは、
/// アプリケーションが特定のファイルを開くために呼び出されたときに
/// 検索結果やその他の情報を表示するために使用されます。
/// </summary>
/// <param name="args">起動要求とプロセスの詳細を表示します。</param>
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
    Frame rootFrame = Window.Current.Content as Frame;

    // ウィンドウに既にコンテンツが表示されている場合は、アプリケーションの初期化を繰り返さずに、
    // ウィンドウがアクティブであることだけを確認してください
    
    if (rootFrame == null)
    {
        // ナビゲーション コンテキストとして動作するフレームを作成し、最初のページに移動します
        rootFrame = new Frame();
        //フレームを SuspensionManager キーに関連付けます                                
        SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

        if (args.PreviousExecutionState == 
                 ApplicationExecutionState.Terminated)
        {
            // 必要な場合のみ、保存されたセッション状態を復元します
            try
            {
                await SuspensionManager.RestoreAsync();
            }
            catch (SuspensionManagerException)
            {
                //状態の復元に何か問題があります。
                //状態がないものとして続行します
            }
        }

        // フレームを現在のウィンドウに配置します
        Window.Current.Content = rootFrame;
    }
    if (rootFrame.Content == null)
    {
        // ナビゲーション スタックが復元されていない場合、最初のページに移動します。
        // このとき、必要な情報をナビゲーション パラメーターとして渡して、新しいページを
        // を構成します
        if (!rootFrame.Navigate(typeof(GroupedItemsPage), "AllGroups"))
        {
            throw new Exception("Failed to create initial page");
        }
    }
    // 現在のウィンドウがアクティブであることを確認します
    Window.Current.Activate();
}

/// <summary>
/// アプリケーションの実行が中断されたときに呼び出されます。アプリケーションの状態は、
/// アプリケーションが終了されるのか、メモリの内容がそのままで再開されるのか
/// わからない状態で保存されます。
/// </summary>
/// <param name="sender">中断要求の送信元。</param>
/// <param name="e">中断要求の詳細。</param>
private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    var deferral = e.SuspendingOperation.GetDeferral();
    await SuspensionManager.SaveAsync();
    deferral.Complete();
}

OnLaunchedイベントハンドラーでは、「if (rootFrame == null)」 で2つのことをしています。

  • SuspensionManagerのRegisterFrameメソッドを呼び出している。
    Frame オブジェクトのナビゲーション履歴を保存(Frame.GetNavigationState)するためです。
  • 強制終了させられた場合の状態の復旧
    これは、強制終了させられた時に表示されていたページやナビゲーション履歴を復旧(Frame.SetNavigationState)しています。

SuspensionManagerのコードの提示を行いませんが、上記の処理とOnSuspendingイベントハンドラーのSuspensionManager.SaveAsyncメソッドにより、Frameオブジェクトのナビゲーション履歴などが保存されると理解しておいてください。このようになっている理由は、Windowsストアアプリの望ましい起動方法に起因しています。具体的には、以下のような動作が望まれます。

  • 以前に起動していない場合は、トップページが表示される。
  • 強制終了させられた場合は、ユーザーが作業を再開できるように最後に操作をしていたページが表示される。

OnLaunchedイベントハンドラーの後半の「if rootFrame.Content == null」で、通常起動(強制終了後の起動であれば、この条件は不成立)でナビゲーション パラメータとして「AllGroups」を指定してGroupedItemPageへナビゲートします。

残ったGroupedItemPageで説明していないことは、GroupDetailPageとItemDetailPageへのナビゲーションになります。ナビゲーションのイベントハンドラーを以下に示します。

/// <summary>
/// グループ ヘッダーがクリックされたときに呼び出されます。
/// </summary>
/// <param name="sender">ボタンは、選択されたグループのグループ ヘッダーとして使用されます。</param>
/// <param name="e">クリックがどのように開始されたかを説明するイベント データ。</param>
void Header_Click(object sender, RoutedEventArgs e)
{
    // ボタン インスタンスがどのグループを表すかを確認します
    var group = (sender as FrameworkElement).DataContext;

    // 適切な移動先のページに移動し、新しいページを構成します。
    // このとき、必要な情報をナビゲーション パラメーターとして渡します
    this.Frame.Navigate(typeof(GroupDetailPage), 
                        ((SampleDataGroup)group).UniqueId);
}

/// <summary>
/// グループ内のアイテムがクリックされたときに呼び出されます。
/// </summary>
/// <param name="sender">クリックされたアイテムを表示する GridView (アプリケーションがスナップ
/// されている場合は ListView) です。</param>
/// <param name="e">クリックされたアイテムを説明するイベント データ。</param>
void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
    // 適切な移動先のページに移動し、新しいページを構成します。
    // このとき、必要な情報をナビゲーション パラメーターとして渡します
    var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
    this.Frame.Navigate(typeof(ItemDetailPage),
                        itemId);
}


Header_Clickでは、グループヘッダーのDataContextがSampleGroupオブジェクトであることから、グループのIDを指定してGroupDetailPageへナビゲートしています。ItemView_ItemClickでは、ClickedItemがSampleDataItemオブジェクトであることから、アイテムのIDを指定してItemDetailPageへナビゲートしています。これは、データバインドしていることから、データバインドされた特徴を生かしたコードになっています。

ここまでで、GroupedDetailPageの説明ができました。説明してきたことから、カスタマイズする場合の特徴を以下の記載します。

  • 新しいページを作成する場合は、LayoutAwarePageを継承させる。
  • データソースをカスタマイズする場合は、SampleDataItem、SampleDataGroupへのプロパティの追加や修正と一緒に、本番データを読み込むメソッドを用意する。
  • データソースを変更すれば、ItemTemplateなどを変更内容に応じて変更する。
  • ビューの切替は、VisualStateManagerを使って行っている(実行時とVisual Studioのデバイスタブ)。
    ユーザーコントロールには、LoadedとUnloadedイベントハンドラーにLayoutAwarePageのイベントハンドラーを設定する。
  • SuspensionManager.RegisterFrameメソッドで、Frameのナビゲーション履歴が保存され、復旧される。
    復旧時はナビゲーション履歴だけなので、データソースは自分で復旧させる必要があることを意味しています。
    また、検索コントラクトのように新しいページで起動される場合は、SuspensionManager.RegisterFrameメソッドでFrameを登録する必要があることを意味しています。(認定要件 3.6 システムが提供するメカニズムを備えた機能をアプリがサポートする場合は、システムが提供するメカニズムを使用しなければならない)。

認定要件3.6は必須事項ではありませんが、グリッドアプリケーションがSuspensionManager.RegisterFrameメソッドでナビゲーション履歴を使った強制終了時の状態復旧に対応しているため、データソースの復旧をした方が良いでしょう。逆に、ナビゲーション履歴を復旧させなければ、データソースを復旧させる必要もありません。次回以降は、GroupDetailPageの解説を行う予定です。

Viewing all 163 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>