2011年12月12日月曜日

ある特定の要素の上に読み込み中とかのくるくる回るスピナーを簡単に表示できるjQueryのプラグイン

ある特定の要素をマスクしてその上に「読み込み中」とか表示したいという要件はよくあると思う。で、そんな要望に簡単に応えてくれるのが下記の素敵なプラグイン。

jquery-loadmask
こんな感じ。
デモはこちら

使い方はいたって簡単。
マスクしたい要素を選んでmaskするだけ。
$("#mydiv").mask("読み込み中...");

マスクをやめたいときは
$("#mydiv").unmask();

もちろんこんなこともできる。
$("body").mask("読み込み中...");

Introducing HTML5を読んでの感想



HTML5で何がどう変わってどういう機能が追加されたの?、というのを程よい解説とサンプルつきでほぼ網羅できる良書。

HTML5がモリモリと大人気なのでこのビッグウェーブに乗り遅れるわけにはいかない、というわけで本書を手に取ってから長いこと積んだままになってて結局乗り遅れた感じになっていたのだけれども、ついさっきざっと全編目を通したのでその感想を。手にしたのは第一版だったのだけれどSecond editionが出ていたのでそちらへのリンクとなっている。なので下記感想はFirst editionのもの。

Chapter 1~3まではHTML5は強烈にセマンティックWebをサポートするぜい!ということで導入された新しいタグとか使い方の説明でほーって感じ。
※セマンティックWebってのは簡単に言うとコンピューターから見て意味の分かるHTMLにしよう、ってことで、
<div id='footer'>フッター的な何か</div>
こんな風になんでもかんでもDivでやるのではなく

<footer>フッター的な何か</footer>
ちゃんとタグそのものに意味を持たせようという動き。

Chapter 4以降はVideoとAudio、Canvas、Data Storage、Offlineでの設定、Drag & Drop、Geolocation、Web socketとWorkerの説明とかなり盛りだくさん、かつ巷で話題のHTML5の新機能がサンプルコード付きでほぼ網羅されているので、Canvasでブラウザベースのゲームを作ったり、Web socketでブラウザベースのオンラインゲームを作ったりしたいと思っている人には、充分それのとっかかりになる部分は得られると思う。

JavaScriptで何かをエンコードしてサーバへ渡すときのエンコード方法

JavaScriptでサーバに値をPOSTするなりGETのパラメータとして渡すなりするときのエンコード方法として下記の3つがある。

encodeURI
encodeURIComponent
escape

で、よく何使えばいいんだっけ?と忘れるので備忘録。

encodeURI
URLをエンコードするのに使用する。
たとえば下記を実行すると
encodeURI("http://www.chinu-kakariduri.com/Shop/Details/小浜漁協釣り筏");
下記になる。
http://www.chinu-kakariduri.com/Shop/Details/%E5%B0%8F%E6%B5%9C%E6%BC%81%E5%8D%94%E9%87%A3%E3%82%8A%E7%AD%8F


encodeURIComponent
URLのパラメータをエンコードするのに使用する。
たとえば下記を実行すると
var result = encodeURIComponent("http://www.chinu-kakariduri.com?p1=chinu&p2=kakariduri");
下記になるので
http%3A%2F%2Fwww.chinu-kakariduri.com%3Fp1%3Dchinu%26p2%3Dkakariduri
次のようにしてURLをURLのパラメータとして渡すことが可能になる。
var url = "http://www.somewhereelse.com?externalsite=" + result + "&p2=chinu";


escape
つかわなーい。
というのもescapeが出力するエンコーディングはW3C標準ではないのでサーバ側で正しく処理できないことが多く苦労するので使わないほうがよい。クライアントだけでエンコード、デコードする用途があるならありだと思う。

2011年11月27日日曜日

ねじをなめてしまったときの対処法

ラップトップのメモリを換装しようと裏蓋をあけるためにグリグリとやっていたらねじをなめてしまい、まったくもって回らなくなったねじとこの世をはかなんでいたのだけれど、こんな便利グッズを使ったらあっさりと回って無事メモリの換装がうまくいったので紹介しておく。


つぶれてしまったねじに対してドライバーをあてて、そのお尻のほうをハンマーでコツコツとたたき、むりくりかませるという方法でまわす。今回はハンマーを使うまでもなく、普通に力をこめながらまわしたらあっさりととれた。すごい威力だ。サイズが複数あるので購入する際は注意していただきたい。

一応上記のドライバーで上手くいかなかったときように滑り止め液も購入していたけれど、今回はまったく使用する機会がなかった。

2011年11月18日金曜日

PythonプログラムからPOSTなりPUTしてみる

PythonプログラムからデータをPOSTなりPUTしたいときのコードスニペット。

import urllib2
opener = urllib2.build_opener(urllib2.HTTPHandler)
content = u'data1=2&data2=日本語です&data3=hello'
request = urllib2.Request('http://somewhere/putme', data=content.encode('utf-8'))
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
request.get_method = lambda: 'PUT'  # PUTするために強引にget_methodを上書き。POSTする場合はこの処理は不要
print result.code

C#プログラムからPOSTまたはPUTしてみる

C#のプログラムからデータをPOSTなりPUTしたいときのコードスニペット。

var request = (HttpWebRequest)WebRequest.Create("http://somewhere");
var buffer = Encoding.UTF8.GetBytes("data1=hello&data2=hi&data3=234");
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = buffer.Length;
request.CookieContainer = new CookieContainer();

var requestStream = request.GetRequestStream();
requestStream.Write(buffer, 0, buffer.Length);
requestStream.Flush();
requestStream.Close();

WebResponse response = null;
try
{
    response = request.GetResponse();
    var stream2 = response.GetResponseStream();
    var reader2 = new StreamReader(stream2);
    var resultText = reader2.ReadToEnd();
}
catch (Exception ex)
{
    if (response != null)
    {
        response.Close();
        response = null;
    }
}
finally
{
    request = null;
}

2011年10月12日水曜日

2011年9月の記事いろいろ 2

プログラミング
Every .NET Developer Should Know About the Database they are working with
SQL Serverを使う上で知っておくととても便利なコマンドやらクエリやらをとても分かりやすく大量に説明してくれている。

Create web apps in JavaScript right from your browser
ブラウザ上でJavascriptを使ってゴリゴリとWebアプリが作れちゃう。



ゲームプログラミング

Developing Multiplayer HTML5 Games with Node.js
Node.jsでブラウザベースのゲームを作ったお話。


Linear algebra for game developers ~ part 1

すごく分かりやすいゲームプログラミングにおける線形代数のお話。


環境
Who really needs Amazon cloud?
EC2にまつわるちょっとした考察。論点が簡潔で読みやすい。


デザイン
SASS FOR DESIGNERS
CSSをSassとCompass使ってコーディングしようぜー!っていうお話。興味があるのでCoffee Scriptと一緒に評価したいと思ってる。

2011年10月2日日曜日

Ubuntu 11.04をWindows 7上のVirtualBoxに入れたらUnityがなかった話

Ubuntu 11.04をWindows 7上のVirtualBoxにインストールしてしばらく使っていてとくに不便もなかったのだけれど、UbutuのWhat's newではLauncherやらBashやらの説明があるのに自分のUbuntu君 on VirtualBoxにはなかったので、今回はその設定方法の解説をする。

まずは、UbuntuをVirtualBoxにインストールする。
※UbuntuをVirtualBoxにインストールする方法は適当にGoogleしてください。すぐに見つかると思うし、至極簡単なので問題ないと思います。
下図のようにVirtualBoxの設定でディスプレイのビデオメモリをちょっと多めに設定しておくのと、拡張機能で3Dアクセラレーションを有効化しておくのを忘れないように(本当に必要なのか実は良く分かっていない)。


Unityが有効になっていない場合は、起動時に「ハードウェアの要件が満たされていないからUnity使えないよ」みたいな警告ダイアログが表示されると思う。その警告は無視してUbuntuにログイン後、Terminalを起動し、下記コマンドを実行する。
sudo apt-get update

次いで下記コマンドも実行する。
sudo apt-get install virtualbox-ose-guest-utils

これでUbuntuを再起動するとLauncherとBashが使えるようになっているはずだ。

2011年9月27日火曜日

2011年9月の記事いろいろ

プログラム
deck.js
新しい表現方法を提供してくれるフレームワーク。

TransformJS – Exposing Transforms to JavaScript
3Dっぽい動きをCSSに簡単に付与できるようにしてくれるjQueryプラグイン。デモを見てもらったほうが早い。

Disk space is cheap...
SQL ServerでなんでもかんでもGUIDを主キーにしてたらすぐにDBがメンテナンスできなくなるよ、って話を計測データ付きで説明してくれている。初期状態で主キーはクラスタ化インデックスになっちゃうし、非クラスタ化インデックスにもクラスタされたキーは付与されるので、GUIDのキーが当たり一面にひろがっていってレガシーデータが大量になってきた場合ににっちもさっちもいかなくなっちゃうぜー、という怖いお話。


Web
Make Games with Construct 2
Html5のゲームをプログラムを書くことなくさくさくと作れちゃうすげーツール。これならデザイナーの人とかほんとさくっとゲーム作れちゃうね。

The top 10 SEO myths
Google's head of Webspam and all-round search sage, Matt Cutts, says: "Google doesn't use the keywords meta tag in our scoring at all. It's just a waste of time to throw a lot of phrases into the keywords meta tag"
ワロタw昔メタタグがすごく重要だ!とか言って大量にキーワードを設定してる人がいたなぁ。


デザイン
Design for Software: Typography Part 1
文字サイズの取捨選択について。

This is genuinely Microsoft’s idea of a
クッソワロタwあんだけリボンUIを押してるMSなのに全然使われてないwwみんな大好きコンテキストメニュー。


ビジネス
Please confirm your email address
何か登録した後とかにメールで確認するシステムというのは多いけれど、その際に「メールアドレスの確認」みたいなSubjectにするよりも「メールアドレスを確認してください」と何をするかを明示的に説明されているSubjectのほうが登録完了まで行うユーザの割合が多いというお話。

The little-known secret of how to actually choose a web designer (in 5 easy steps)
ブランディングとか情熱的な~のようなあいまいとした言葉を多用して相手を煙に巻くデザイナーではなく、デザインの結果としてどのような効果があってそれがどのような成果に結びついたのかを明示しているデザイナーこそ雇うべきというお話。


一般
Ten ways to improve your technical blog writing
誰に向けて書いてるのか、ってのが一番重要なんだよね。うん、そうだよね。すいません、毎回書き散らしてました。

Should Your Employees Take Naps?
昼寝したほうがいいよねって記事。いつも昼食後30分ぐらい寝るようにしてる。仮眠をとると午後眠たくならないから健全な精神状態で働ける。

2011年9月18日日曜日

名前付き引数のすすめ

仕事でVB.NETとJavascriptを使い、自分のプロジェクトではC#とJavascriptにPythonと使っているのだけれど、プロジェクトをスイッチさせたときに頭の中のSyntaxがごちゃごちゃになっててスラスラと書き始めるまでしばらく時間がかかったりする。そのせいもあるのか自分のコードを忘れる時間も早くなっている気がする。

で、先日、自分の組んだJavascriptのコードに機能を追加しようと眺めていたら下記のようなコードがあってハタと手が止まってしまった。

$('.class').disable(true);

・・・disableにtrueってなんだろ?有効、非有効の表示を切り替えるためにjqueryのpluginを作ったのまでは覚えているけれど、その引数の意味が思い出せない。パッと見て類推できるのは、disableにする、しないのフラグかなと思ったのだが、enableというpluginも用意したはずなのでそれは当たらない。で、良く分からないのでpluginの実装を見たら下記のようだった。

disable:function(clearValue){
 if (clearValue){
  // code here
 }
}

ということであのbooleanはdisableついでに値も消去するかのフラグだった。これがC#、VB.NETだとまだ楽で優秀なVisual Studioのインテリセンスがすぐにどういう値なのか教えてくれるし、PythonにしてもPyDevなどがあればDocumentが表示されるので簡易に分かったりする。が、Visual StudioでJavascriptのインテリセンスを使用するためにはReferenceタグを各ファイルに追加しなければいけなかったりと若干面倒だったりするので、Syntaxとして名前付き引数が用意されていないJavascriptで、コードを用いて同じ機能を実現しようとするならば下記のようになる。

item.disable({clearValue:true});

disable:function(options){
 options = options || { clearValue:false};
 if ( options.clearValue){
  // code here
 }
}

これでコードの可読性はあがったけれど若干冗長なのは否めない。ただ冗長だからという理由だけで、常にBoolean、Integer、Stringなどのプリミティブな値を定数としてそのまま引数として渡すのは、コードを管理していく上で良い方法だとは思わない。なので、変数名などからその引数を類推できない場合などは極力名前付き引数を使用するほうが可読性の向上 → メンテナンス性の向上というコンボになるので、上記のような状況では名前付き引数を使用するのが良いだろう。


蛇足ながらそれぞれの言語で名前付き引数で上記のシグネチャの関数を呼び出すと以下のような感じになる。

// c#
item.Disable(clearValue:true);

' VB.NET
item.Disable(clearValue:=True);

# Python
item.disable(clearValue=True);

2011年9月4日日曜日

Webを支える技術 を読んでの感想



Windows Formの開発をやっててASP.NETの案件をやるようになっちゃって、そのままWindows FormのノリでイベントドリブンなWebアプリを作っちゃってWebの基礎を良く分からずに来た人などはとくに手にとっておいて損はない本。

HTTP, HTML, Atom, RESTなどなど現在のWebを取り巻く状況を満遍なく解説してくれている。なんでもかんでもGETとPOSTで行っている人(私含む)なんかは、RESTの説明周りでPUT, DELETEの存在意義だったり使用方法を見て「おぉ、そうなんだ」というAhaモーメントがあると思う。それと最後のほうのRESTFulなWebサービスをどのように設計するか、はRESTFul Webサービスを設計する場合のとても良い指針になってくれると思うので勉強になった。

2011年8月23日火曜日

2011年8月の記事いろいろ

プログラミング
Some People Understand REST and HTTP
RESTについて。

The Definitive Guide To Forms based Website Authentication
Webの認証についていろいろ。ざっと目を通しておくだけでも価値がある。

jQuery Performance Rules
jQueryのセレクターで最も速いのはid指定で、ついで速いのはタグ名指定。というのもそれぞれgetElementByIdとgetElementsByTagNameに対応しているからで、セレクターを記述するときはそのことを念頭に置いたほうが良い。その他jQueryを使い始める前に一度は目を通しておくべきパフォーマンスについてのTips多数。

box2dweb
すごすぎワロタw
Box2Dっていう物理エンジンをJavascriptにポートしたプロジェクト。

The Official C#.NET Alpha-Geek® Quiz
面白すぎwC#に腕に覚えがある人はやってみるべき。まじで!?なんで!?ってなる質問が一つはあると思う。

spin.js
スピナーを動的に作っちゃうJavascriptライブラリ。


開発
Finding memory leaks
Chromeを使ってのブラウザアプリのメモリリーク発見法。

What Does an Architect Do in an Agile Shop? An Agile Architect Explains All...
アーキテクトって職種にとくに興味はないんだけどアジャイル開発やるときのアーキテクトってなにやってんの?という質問の答え。下記の引用が好き。
"The agile architect must understand the current state of the software and know where it is going. He must spend his time with the team and be involved in all aspects of the project."

The quick website launch checklist
事前にこんだけチェックしとけばとりあえず大丈夫だよ、っていうWebサイトリリース前のチェック項目。バックアップテストとか耳が痛い。


デザイン
Six Key Lessons from a Design Legend (a before-and-after)
無難なデザインに落ち着いていたのがプロデザイナーをやとったらこんなにお洒落なデザインに変わったよ、見てよ。という6箇所のビフォーアフター解説。

Why Rounded Corners are Easier on the Eyes
丸っこいデザインがはやってる理由。


ビジネス
How to market your startup or new product without spending a penny
お金を使わずにスタートアップをどうやって宣伝するの?っていう方法。右も左も分からんときに役立ちそう。

2011年8月3日水曜日

2011年7月の記事いろいろ

Reading: My Standing Desk Experiment
立って仕事したら最初は疲れるけど2~3週間したらまじで元気になってくるよ、というお話。今のプロジェクトが終わってまた自宅作業するようになったらちょっとトライしてみようと思ってる。

Hotelling’s Game, or Why Gas Stations Have Competitors Nearby
なんでガススタってかたまってんの?というお話。かなり分かりやすくて、読んだ後でおぉそりゃそうだな、と合点がいく。

Understanding Linux CPU Load - when should you be worried?
画像の解説が異常に分かりやすくて、頭にスコーンと入る感じ。大規模サービス技術入門でも解説されてたけどね。

FIVE POPULAR WEB STRATEGIES THAT DON'T WORK
総合するとユーザセントリックであるべきだよね、ってことかな。競争相手とか、新しい技術とか、新しいものとか、使い勝手とかよりまずはユーザを見ようぜって。

Tilt: Visualize your Web page in 3D
なんか複雑そうなサイトとか見るときに使ったりするとむふふとなる。デザイン系のサイトで、なにこれ、どうなってんの?ってサイトに使うと、おぉってなる。

Why I Hate Frameworks
最近のフレームワークって複雑なのが多すぎるんだよね。もっとシンプルでいいのに。Google App Engineぐらいシンプルでよくて、なんでも乗っけられるようになってるのが一番いいよ。

You are not running out of time
好きなことを仕事にするのが一番幸せです、ということで。プログラムは好きだけど、今は糞つまらんプロジェクトに参画しててテンション下がるわー、って人は、そのプロジェクトは日銭を稼ぐためと割り切って、それとは別に余暇を使って自分のWeekend projectとかでやりたいことをがっつり試したりすると精神が昂揚しますよ。

Why We Threw out All Our Code (And Why You Should Too)
はじめにとにかく色々想定して作りこみすぎると無駄になることはままあることで。重要なことは作りこんでいたとしても明らかに「こいつぁ無駄だな」って分かった時点でばっさり切り捨てる決断ができることだと思うんだよね。

The Mother of All Interview Questions
面談時に「何か新しいことを思いついて開発に取り入れたことってありますかね?」という質問をしたり、されたりしたらその人やらチームやらの性質がよく分かるよ、というお話。で、自分にしてみたところ、ね(無)ぇ!!何か新しいこと発明したこと、ね(無)ぇっ!!何か新しいことを取り入れるのは大好きだけど、これのパテント(英語発音:パァトゥントッ)おれなんすよ、みたいな小話は、ね(無)ぇっ!!いやー、これは盲点でしたね。・・・ふむ。でも自分の開発サイクルをかんがみるに、やっぱ新しいものに常に触れていたいってのはあるから、そういう土壌のある場所に常にいたいなとは思う。

2011年7月3日日曜日

ソフトウェア見積り―人月の暗黙知を解き明かす を読んでの感想

見積りってどうやるのが業界標準なの?と長年疑問に思っていたので「ソフトウェア見積り―人月の暗黙知を解き明かす」を手にとって見た。



プロジェクトの工数見積りって要件もざっくりとしか決まっていない時点で6ヶ月で納品とかよく算出されるけれど、以前から「これってみんなは何を根拠に出しているのだろう」と前々から疑問に思っていた。自分の場合は、ほんとにざっくりとこんな機能がいるよね~、で、この機能は大体5日~、10日~、のような感じで算出するのだけれど、これってほんとあいまいだなぁ、と常々考えていた。

で、本書。そんな不安な思いを打ち消してくれる役立つ内容てんこ盛りだった。中でも自分的になるほど~と感心した点を以下にまとめておく。

不確実性のコーン
プロジェクトの初期では要求さえ明確ではないので正確な見積りを行うことがほぼ不可能で、その振れ幅は4倍~0.25倍にもなる。ただ、詳細設計が完了する段階までくるとその振れ幅は1.25倍~0.80倍になり、それ以降もプロジェクトの収束に向かって見積りの正確性は向上していく。

判断するな。数えろ。
恣意的な判断で、これは5日ぐらい、という判断をするのではなく、判断の基準となる過去のデータを調べて予測すること。その参考とする過去のデータってなんなのよ?という詳しい説明は本書でなされている。

範囲での見積り
シングルポイントで5ヶ月です、というような見積りをするのではなく、7ヶ月から+-2ヶ月です。というような範囲で見積りを出すようにし、プロジェクトの進行とともにその範囲が狭まっていくようにする。またそれぞれの見積りごとに正確性の割合を出すと良い。5ヶ月でリリースできる確立は15%ですが9ヶ月でリリースできる確立は90%です、のような感じ。

上記のほかにも、ステークホルダーにどのように見積りを認めさせるのか、など交渉のヒントにも言及されている。

本書を読むといくつかのすぐに使える技法を学ぶことは可能だが、だからと言って翌日からすぐに正確な見積りができるようになるわけではない。というのも、基本的に正確な見積りを行うようになるには組織全体として取り組む必要があるので、中長期的な視野にたってそのことを煮詰めていく必要がある(過去のデータを収集するあたりなどとくに)。ただ、正確な見積りのためには開発者個々人の見積りに対する高い意識も必要とされるので、そういうのを根付かせるための材料として本書を使うこともできるなと感じた。


--------------------
以下、本書を読んでいないと意味不明だと思うけれど、自分的メモ用に気になった公式をいくつか抜粋。
最良ケースと最悪ケースの見積りを考えるようにする。何も言わないと開発者は大体最良ケースに近い一点見積りを行う。
期待ケース1=[最良ケース + (4*最有力ケース) +最悪ケース]/6
期待ケース2=[最良ケース + (3*最有力ケース) +(2*最悪ケース)]/6
MRE(Magnitude of relative error)=[(実際の結果-見積り結果)/実際の結果]の絶対値
標準偏差=(最悪ケースの見積りの和 - 最良ケースの見積りの和)/6

2011年7月2日土曜日

LambdaExpressionで動的な検索条件を作る

最近の仕事で固定の検索条件ではなく動的に検索条件を作りたいという仕様があり、それもあってExpression周りをいじくりまわしていて楽しかったので解説する。

今回のサンプルコードはこちらからダウンロードできる。


Linqを使用する場合に動的に検索条件を作るためにはExpression Treeと戯れる必要がある。Linq To SqlであればDynamic Linqという文字列を解析してExpression Treeに変換してくれる拡張関数群が公開されているので、文字列をいじくるのを苦にしなければかなり簡単に動的な検索条件を作成することが可能だ。しかし、Linq To Entityだとそうはいかないのでちょっと込み入ったことをしなくてはならない。

動的検索条件と言っても、すべての検索条件がAnd条件であるならば、単純にWhereメソッドを下記のように順々に呼び出していけばよいだろう。

if ( 条件1 )
  query = query.Where(c => c.Name == "四郎" );

if ( 条件2)
  query = query.Where(c => c.Age > 15 );

※今回の解説の仕様として、等号などのオペレーターや対象プロパティは事前に決まっていてどの条件を組み合わせるかのみ動的に設定できる、という設定とする。オペレーターやプロパティもすべてを動的に作る場合にはParameterExpressionなどを使ってさらに深くExpression Treeをいじる必要があるのでその解説は次回以降行う、かもしれない。

しかし、Or検索が間に入ってくると話が変わってくる。前述の方法ではWhereメソッド間はAnd条件になるので動的なOr検索を行うことはできない。というわけで、LambdaExpressionの登場だ。こいつを上手に使ってやると下記のような条件を簡単に動的に組み立てることができる。

query.Where(c => (c.Name.Contains("白") || c.Bushous.Any(b => b.Age > 36)) && c.Name.EndsWith("城"));

今回使用するモデルは下記。DBの作成はEF Code First4.1に全部任せている。
public class Bushou
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public int CastleId { get; set; }
    public Castle Castle { get; set; }
}

public class Castle
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual IList<Bushou> Bushous { get; set; }
}
    
public class Ikusa : DbContext
{
    public DbSet<Bushou> Bushous { get; set; }
    public DbSet<Castle> Castles { get; set; }
}

前述の各条件を表すLambdaExpressionは下記になる。

Expression<Func<Castle, bool>> condition1 = c => c.Name.Contains("白");
Expression<Func<Castle, bool>> condition2 = c => c.Bushous.Any(b => b.Age > 36);
Expression<Func<Castle, bool>> condition3 = c => c.Name.EndsWith("城");

このままでは各LambdaExpressionはばらばらなので一つにまとめる必要がある。その際に注意が必要なのだが、LambdaExpressionを統合するには同一の引数を使用するように変更してやらなければならない。上記の定義を見てもらえば分かると思うけれど、各LambdaExpressionにはcという引数が定義されている。Expression Tree的にはcondition1のcとcondition2、condition3のcはまったくの別物なので、こいつらを一つにまとめる必要がある。

で、そんな面倒なことを行う際に便利なクラスがC#4.0から提供されている。それがExpressionVisitorクラスだ。このクラスの使いどころは次のような場合だ。Expressionには多種多様なタイプが用意されている。go toステートメントを表すGoToExpression、ループを表すLoopExpression、メンバーアクセスを表すMemberExpressionなどなど数十種類に及ぶ。で、今回は引数をいじくる必要があるのでParameterExpressionが対象になる。しかしExpression Treeを順々にたどっていって任意のExpressionを探すのは骨が折れる。なのでそんなときはExpressionVisitorクラスを使用する。

使い方はExpressionVisitorクラスを継承し、自分が気になるタイプのExpressionを引数としているメソッドをオーバーライドするだけだ。今回はParameterExpressionをいじくるのでVisitParameterがオーバーライドするメソッドになる。

class ParameterVisitor : ExpressionVisitor
{
    private ReadOnlyCollection<ParameterExpression> _fromParams, _toParams;
    public ParameterVisitor(ReadOnlyCollection<ParameterExpression> from, ReadOnlyCollection<ParameterExpression> to)
    {
        _fromParams = from;
        _toParams = to;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        for (var i = 0; i < _fromParams.Count; i++)
        {
            if (node == _fromParams[i])
                return _toParams[i];
        }
        return node;
    }
}

ParameterVisitorクラスは変更元の引数コレクションと変更先の引数コレクションを保持し、ParameterExpressionが見つかるたびに、その見つかった引数(上記で言うところのnode)が変更元の引数と同一かをチェックし、同一であれば、変更先の引数を返却している。変更先の引数を返却することで変更元のExpression Treeは差し替えられている。つまり、変更元の引数の数と、変更先の引数の数が異なる場合はアボンするということなので注意が必要だ。ParameterVisitorクラスのコンストラクタにそれようのチェックコードを入れるのも良いだろう。

実際にParameterVisitorクラスの使用方法は下記になる。

class Helper
{
    public static Expression<Func<T, bool>> AndAlso<T>(Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
    {
        var newExp = new ParameterVisitor(right.Parameters, left.Parameters).Visit(right.Body);
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body, newExp), left.Parameters);
    }

    public static Expression<Func<T, bool>> OrElse<T>(Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
    {
        var newExp = new ParameterVisitor(right.Parameters, left.Parameters).Visit(right.Body);
        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(left.Body, newExp), left.Parameters);
    }
}

各メソッドともに、まずはVisitメソッドを呼び出し、二個目の引数のLambdaExpressionの引数を一個目の引数のLambdaExpressionの引数で差し替えている。その後にAndAlso、またはOrElseを呼び出しExpressionの統合を行っている。

Helperクラスの使い方は下記になる。

var condition = Helper.AndAlso(Helper.OrElse(condition1, condition2), condition3);

そうしてできたLambdaExpressionをWhereメソッドに渡してやれば下記と同等の記述になる。

query.Where(c => (c.Name.Contains("白") || c.Bushous.Any(b => b.Age > 36)) && c.Name.EndsWith("城"));

query = query.Where(condition);

かなり冗長になるけれど、上記の方法以外にも下記のようにWhereメソッドを呼び出すこともできる。

var resultExp = Expression.Call(typeof(Queryable),
                                "Where",
                                new Type[] { typeof(Castle) },
                                query.Expression,
                                Expression.Quote(condition));
query = query.Provider.CreateQuery<Castle>(resultExp);

今回の例では下のほうの呼び出し方をする必要はまったくないけれど、オペレーターやプロパティも含めてすべてを動的で行う場合などで、Anyやら何やらを動的に呼び出す必要がある場合は、最後に解説したような呼び出し方をしなければならない。

2011年6月5日日曜日

ピープルウエアを読んでの感想

デッドラインを読んでもいまいちピンと来なかったので補足として『ピープルウエア 第2版 - ヤル気こそプロジェクト成功の鍵』を読んでみた。



デッドラインは小説仕立てで読みやすいのは良かったけれど、たまに得心のいかない法則が出てくるときもあり「?」となる場合があったけれど、ピープルウェアはデッドラインと同じような素材を扱いながらも違う視点から切り出してあるのでデッドラインで学んだ法則の理解を補強するのに役立った。それに各章ごとに内容にそった小話的な実話がちょこちょこ挿入されているので読んでいて飽きない工夫がされているのも良かった。

全6部構成で各部それぞれ下記のようなことが学べる。

第1部 管理するとは
やってはいけない管理方法がわかる。

第2部 オフィス環境を整えることの重要性
思考を電話などによって頻繁に中断されることがいかに頭脳労働にたいして悪影響かがわかる。
オフィスのレイアウト周りの話なんかはあまり興味がないけれど、オフィス移転を考えているような人には良い参考になると思う。

第3部 人材をそろえる、維持する方法
人材をそろえる効果的な方法や退職に伴うコストなどが解説されている。
下記の企業側からみた退職のコストの考え方は意識したことも無かったので目からうろこで面白かった。
--------------
退職のコスト=4~5月分の人件費
内訳:人材紹介会社への報酬、人事部門の費用、プロジェクトになれるための教育コスト(本人が学ぶ分とその他の教える人達の分)などなど
--------------

第4部 生産性の高いチームを作る方法
チームの効果を高める方法について、良い方法、悪い方法が解説されている。
要はやる気ということで、それをどのように高めるかというお話。

第5部 仕事を楽しくするには~
とくに感想なし。

第6部
1~5部それぞれの小さな続編。

2011年6月4日土曜日

デッドラインを読んでの感想

手を動かすほうが好きなので管理とかにはあんまり興味がないのだけれど、後学のために『デッドライン―ソフト開発を成功に導く101の法則』を読んだので感想とか内容の覚書とか。



技術系の本は読むけれど、こういうノウハウ系の本はあまり手にとったことがなかった私にとってこの本の内容は「ものすごくためになった!」とか「明日から即実践しよう!」とかそういう感動系のたぐいではなく、「あぁ管理ってこういうものなんだ、へー」という感じに曖昧模糊としていたものの理解を助けてくれたり、「あぁこういう問題が起こりえてそういうときはこういう風に対処すれば良いのね」という先駆者の知恵を授けてくれた。

内容は小説仕立てで、各章の最後にその章で主人公が学んだことを法則として小説のなかから抜き出してまとめてくれるので、さくさくと読んでいけてあっという間に読み終わる。そのままにしてしまうと脳みそからすぐ抜け落ちてしまうことは確実なので、101個の法則のなかから気にいった法則を下記にまとめておいた(結構な数になったけど)。法則だけ読んでも意味不明なところが多々あると感じるかもしれないけれど小説を通して読めば前後の文脈があるので理解できる。

基本
  • 適材適所
  • 士気を高める
  • Teamの結束を強め維持する
  • 変更はプロジェクト成功のために必要不可欠である
  • 人は安全が保証されないと変更を受け入れない
  • リスクをさけるとそれに伴う利益も逃がす
  • どれほど強く脅しても最初に割り当てた時間が足りなければ仕事は完成しない
  • 管理は、心、腹、魂、鼻でやるもの。心で指揮をとり、自分の腹(直感)を信じ、組織に魂をふきこみ、くだらないものをかぎ分ける鼻
  • 戦闘が始まるときには管理者のほんとうの仕事はすでに終わっている

採用
  • 採用には心、魂、腹、鼻すべてを使う(腹が大部分)
  • 一人でやろうとするな。二つの腹には一つの腹の2倍以上の力がある
  • 新しく採用した人材は、能力実証済みレベルのプロジェクトを任せ目標を拡大するのは次回以降とする
  • 意見をもとめよ。優れた人材は優れた人材を知っている可能性が高い
  • 話すよりも聞け

生産性
  • 短期的に生産性を高める方法などない。生産性は長期的な投資によって向上する
  • 短期的な効果を約束するものはいんちきである可能性が高い

リスク管理
  • リスクを管理してプロジェクトを管理せよ
  • プロジェクトごとにリスク調査の方法を確立して継続せよ
  • 最終的な望まざる結果だけでなく、日常のリスクに注意せよ
  • それぞれのリスクの実現する確率と予想コストを評価せよ
  • リスクが現実になる前の初期の兆候を予測せよ
  • 悪い話が上層部に伝わりやすい経路を作っておくこと(匿名メールなど)

人材管理
  • 成功を最大化するより失敗を抑えることによって全体的な成績を高めることができる
  • 失敗した作業は早く打ち切る勇気をもつ
  • チームの結束については既存のチームを探して利用したほうが楽だったりする
  • 結束力のあるチームはばらさずに維持する(本人たちが望めば)。そうすると後継者たちが困らない
  • 新しい仕事を引き受ける意欲のある結束の固いチームはプロジェクトの成果の一つとみなす
  • プロジェクト初期に無駄にする一日も末期に無駄にする一日も等しく打撃になる
  • 一日をむだにする方法はいくらでもあるが、一日を取り戻す方法はひとつもない

開発プロセスのモデリングとシミュレーション
  • 仕事を完成させるプロセスに関する直感をモデル化する
  • 仲間と対話しつつプロセスの進行に関する考えを伝えたり修正したりするためにモデルを使う
  • モデルを使って結果をシミュレートする
  • 実際の結果と照らし合わせてモデルを調整する

理想と現実
  • 病んだ政治=政治的な理由からくるむちゃくちゃな要求(機能はそのままで納期を半分にしろなど)
  • いつでもクビをかける覚悟が必要である
  • それでも病んだ政治の影響を受けないとは限らない
  • 病んだ政治はもっとも健全な組織にも出現する可能性がある

プロジェクトの数量化
  • すべての製品のサイズを測定せよ
  • 単位は気にするな。客観的な尺度ができるまで主観的な単位を使えばよい
  • 手に入るすべての基本要素(ソフトウェアの数量化可能な特徴)をもとに合成尺度を作成する
    -この法則のトレンドライン周りの話はピンとこなかった(最近の開発にはあてはまらない気がする)

プロセスとプロセス改良
  • 優れたプロセスとプロセスを絶えず改良することは立派な目標である。それらはまたごく自然な目標でもある。すぐれた技術労働者は指示があろうとなかろうとそれらに焦点をあてる
  • 形式的なプロセス改良プログラムには時間と金がかかる。一つのプロセス改良プログラムのためにプロジェクトが後退することもありうる。生産性の向上が実現したとしても、そのプログラムを受け入れたプロジェクトでプロセス改良のために費やされた時間を相殺できる可能性は低い(次以降に効力を発揮する)
  • プロセスは注意深く選んだひとつの手順改良によってその変更に投資した時間と金に報いるだけの利益を期待できることがある
  • プロジェクトの期間中に二つ以上の手順改良に順応することは現実には期待できない
  • 標準的なプロセスの危険な点は人々が懸命な省略を行う機会を失わせることである。とくに、人員過剰のプロジェクトの場合、標準的なプロセスによって全員に行き渡るだけの仕事が発生するなら(役にたとうがたつまいが)、標準的なプロセスが厳密に守られてしまう

設計とデバッグの関係
  • デバッグの時間を大幅に減らさなければ、プロジェクトの成績を通常より大幅に高める方法はない
  • 優れたプロジェクトはデバッグに費やす時間の割合がはるかに低い
  • 優れたプロジェクトは設計に費やす時間の割合がはるかに高い

プレッシャーの効果
  • プレッシャーをかけても思考は速くならない
  • 残業時間を増やすのは生産性を落とす方法である
  • 一時的なプレッシャーや残業は人々の焦点を定めその仕事が重要であるという認識を高めるには有効な方法かもしれないが、プレッシャーをかけすぎるとかならず失敗する

いかれる管理者
  • 管理者の怒りと侮辱は伝染する。上の管理者が怒鳴ると下の管理者も同様の行動をとるようになる(虐待された子供が自分の子供を虐待するようなもの)
  • 管理者が部下を侮辱してだれかの業績がよくなるという証拠はあるの?
  • 管理者が部下を刺激するのに侮辱を使うことは部下ではなく管理者の能力不足のしるしである

あいまいな仕様書
  • 仕様書があいまいなのはシステムの利害関係者の間で対立が解決されていないしるしである
  • 入出力の完全なリストのない仕様書は見込みなしである。使用を明確にする最初の一歩にもならない

対立
  • 開発に複数の当事者がかかわっている限り、利害の対立は避けられない
  • システムの構築と導入の事業にはとくに対立が多い
  • システム開発組織のほとんどは対立解決能力に乏しい
  • 対立は尊重すべきである
  • 全員の勝利条件を尊重することをあらかじめ宣言しておく。あらゆるレベルで勝利条件を引き出すようにする
  • 交渉は難しいが、仲裁は簡単だ
  • 勝利条件が相容れないか、または部分的に相容れない場合でも関係者が対立解決のために仲裁に移行するようにあらかじめ準備しておく
  • われわれは味方同士である。敵は問題そのものだ
  • 触媒のような人格というものがある。そのような人はチームがまとまって結束し、なおかつ健全性と生産性を維持できるようにすることでプロジェクトに貢献する。触媒の役割は重要で貴重である
  • 仲裁は触媒の役割の特殊なケースである。仲裁はわざうかな投資で学習できる
  • 「あなたたちの仲裁をさせてもらえますか」というささやかな儀式の開始が対立解決の本質的な第一歩になることがある

スタッフの人数
  • 初期に人数が多すぎるとプロジェクトは重要な設計作業を省略せざるをえない(全員に仕事を与えるため)。設計が完成する前に大勢に仕事を割り当てると、人や作業グループの間のインタフェースを最小化できない
  • このため相互依存性が高まり、会議が増え、やり直しがふえ、フラストレーションがたまる
  • 理想の人数配分はプロジェクト期間の大部分を少人数のコアチームで行い、プロジェクトの終盤(最後の6ぶんの1ぐらい)に人数を大幅に増やすというものである

プロジェクトの社会学
  • 会議は重要ではない人物が出席しなくても心配のないように小さくする必要がある。欠席者が安心するための最も簡単な方法は、議事予定表を発行しそれに厳密に従うことである
  • 罵倒などの怒りから人々を守るために手を打つ
  • 怒りは恐怖である。部下に対して罵倒などの怒りの行動をとる管理者は、ほとんどの場合、怖いからそうしているのである(失敗するのではないか、期待に背くのではないか、などなど)
  • 怒りが恐怖であることをすべての人が理解すれば、その人が怒っているのは怖がっているからだと周りの人間は理解でき、周りの人間の悩みは軽減できるだろう

病んだ政治
  • 病んだ政治を下から治療することはできない。むだな努力で時間を浪費したり、自分の立場を危険にさらす必要はない
  • 問題が自然に解決するか、行動するチャンスが来るのを待つしかない場合もある

倹約精神
  • 倹約精神とは、失敗した企業の中でその失敗の責任者が作った公式である

2011年5月20日金曜日

Google App EngineにC#プログラムからログインする方法

今回はGoogle App Engine(GAE)にC#プログラムからログインする方法を解説する。

GAEで特定のUrlに対して、特定のユーザのみにアクセスを許可する場合はapp.yamlで下記のように設定をすればよい。

app.yaml
- url: /do/something/
  script: something.py
  login: required

これでGAEアプリのユーザのみが上記Urlへのアクセスが可能となる。ユーザではない、または未ログインのものが上記UrlにアクセスするとGoogleアカウント(またはOpen Id)のログインページへとリダイレクトされる。そこでユーザ名とパスワードを入力してログインすれば上記のUrlへとリダイレクトバックされる。これがブラウザ上の動作であれば問題はないが、プログラム上から認証を必要とするUrlへとアクセスする場合は困る。

認証を必要とするGAEアプリのUrlに対してリクエストを行う場合は下記の手順で行う必要がある。

  1. ClientLoginサービスにログインしAuthトークンを取得する
  2. 取得したAuthトークンを利用しGAEアプリにログインしCookieを取得する
  3. 取得したCookieとともにHttpGETやらHttpPOSTを行う

各項目を順を追って説明する。

ClientLoginサービスにログインしAuthトークンを取得する
string GetAuth()
{
    var request = (HttpWebRequest)WebRequest.Create("http://www.google.com/accounts/ClientLogin");
    var content = "Email=test@gmail.com&Passwd=testpass&service=ah&accountType=HOSTED_OR_GOOGLE";
    var byteArray = Encoding.UTF8.GetBytes(content);
    request.ContentLength = byteArray.Length;
    request.ContentType = "application/x-www-form-urlencoded";
    request.Method = "POST";
    var dataStream = request.GetRequestStream();
    dataStream.Write(byteArray, 0, byteArray.Length);
    dataStream.Close();
    var response = (HttpWebResponse)request.GetResponse();
    var stream = response.GetResponseStream();
    var reader = new StreamReader(stream);
    var loginStuff = reader.ReadToEnd();
    reader.Close();

    var auth = loginStuff.Substring(loginStuff.IndexOf("Auth")).Replace("Auth=", "").TrimEnd('\n');
    return auth;
}
まずhttp://www.google.com/accounts/ClientLoginへと必要な情報をポストする。各項目の詳細な説明は下記を参照して欲しい。
ClientLogin for Installed Applications

今回は最終的にGAEアプリへとログインしたいのでserviceはahとなる。これがカレンダーであればclなどそれぞれに対応したものへと変わる。ログインに成功するとSID、LSIDとともにAuthが返却されるのでAuthのみ取得する。他の二つは不要。

取得したAuthトークンを利用しGAEアプリにログインしCookieを取得する
CookieContainer GetCookies(string auth)
{
    var cookies = new CookieContainer();
    var url = string.Format("http://test.appspot.com/_ah/login?auth={0}",
                            System.Web.HttpUtility.UrlEncode(auth));
    var request = (HttpWebRequest)WebRequest.Create(url);
    request.AllowAutoRedirect = false;
    request.CookieContainer = cookies;
    request.ContentType = "application/x-www-form-urlencoded";
    request.Method = "GET";
    var response = (HttpWebResponse)request.GetResponse();
    var stream = response.GetResponseStream();
    var reader = new StreamReader(stream);
    var result = reader.ReadToEnd(); 
    reader.Close();
    return cookies;
}
先ほど取得したAuthトークンを使用しGAEアプリへとログインする。実際にはhttp://test.appspot.comの部分をアクセスしたいGAEアプリのUrlへと変更して欲しい。ログインに成功するとACSIDというクッキーが返却されているはずだ。余談ながらGAEアプリへのログイン後、POSTではなくGETでよい場合は、下記のようにリクエストすれば指定のUrlへとリダイレクトされる。

http://test.appspot.com/_ah/login?auth={0}&continue=http://test.appspot.com/do/something/

ただ今回はPOSTでリクエストしたいのでAllowAutoRedirect=falseで自動リダイレクトを無効化している。


取得したCookieとともにHttpGETやらHttpPOSTを行う
void PostToGAE()
{
    var auth = GetAuth();
    var cookies = GetCookies(auth);

    var url = string.Format("http://test.appspot.com/do/something/");
    var content = "testvalue=test";
    var request = (HttpWebRequest)WebRequest.Create(url);
    request.KeepAlive = false;
    request.CookieContainer = cookies;
    var byteArray = Encoding.UTF8.GetBytes(content);
    request.ContentLength = byteArray.Length;
    request.ContentType = "application/x-www-form-urlencoded";
    request.Method = "POST";
    var dataStream = request.GetRequestStream();
    dataStream.Write(byteArray, 0, byteArray.Length);
    dataStream.Close();
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    var stream = response.GetResponseStream();
    var reader = new StreamReader(stream);
    var result = reader.ReadToEnd();
    reader.Close();
}
取得したCookieをそれ以降のリクエストと一緒に送ってやれば認証されたユーザとしてサーバサイドで認識される。

※見て分かると思うけれど、上記方法は中間でトラフィックを監視している人物がいた場合に、その人物がクッキーを使いまわすだけで簡単になりすましが可能となる。そのため繊細な情報などを扱う場合はhttpsを使用するのが望ましい。httpsを使用した場合は返却されるクッキー名がSACSIDとなる。


仕組みが分かれば至極単純なのが理解できたと思う。上記のコードは動作テスト用に組んだものなので例外処理などまったくの手付かずなので使用される場合は随時手を加えてもらいたい。


余談
私がこの動作テストを行ったときに正常に動作させるまで二日ほどかかってしまった。その要因はサーバサイドのハンドラーをDjango-nonrelで実装していたのだが、POSTリクエストがcsrf protectionに引っかかっていたせいだった。ずっとGAEの認証側の問題だと考えてそちらばかり追っかけていたので、Djangoのほうまで頭が回らなかった。気付いてしまえば単純な問題だけれど、はまるときははまる。仮にDjango-nonrelを使用する場合は忘れずにハンドラーへ@csrf_exemptを付与するようにしてほしい。

下記、はまっているときにstackoverflowへ質問した内容。
Programmatically login to google app engine c#

2011年5月19日木曜日

Google App Engineで開発してみた感想とか環境とか

Google App Engine(GAE)でガリガリと開発し始めて2週間ぐらいたったので、今回は開発の感想とか現在の開発環境とか。

発端~準備
チヌかかり釣りMEGAのSNS要素としてコミュニティ機能が欲しかったので、どうせデータのスケーリングを視野にいれるならGAEでやってみんべ、と思い立って始めたのが3月末。以前からPythonには興味があったのでついでに勉強しようとPythonを言語として選択。Pythonの勉強用にDive Into Python 3という大変すばらしい資料がネットに転がっていたのでそれでPython的なものを理解した。ただPythonは3と2で言語仕様の互換性が途絶しているため実際にGAEで開発するときはそこらへんを考慮しなくてはいけなかった(まぁ今までのところほとんど意識することはなかったのだけれども)。

で、Pythonをぼんやりと理解したので次いで背景も含めたGAEの理解に努めた。資料的にはGetting Started: PythonからApp Engine Python Overviewまわりの全部、それと内部的な動作を理解するのにMastering the datastoreもすべて目を通した。あとIndexまわりを理解するためにもBigtable: A Distributed Storage System for Structured Dataの出だし数ページには目を通しておいたほうが良いと感じた。

開発はじめ
開発環境はEclipse 3.6 Heliosを採用。GAEのページにEclipse用のプラグインがあるのでそれを使えば簡単に開発環境が構築できる。ただ、GAEそのままで開発を始めるとすぐに不便なことに突き当たる。GAEはDjangoのテンプレート機能を標準でサポートしてくれているが、それ以外の機能はない(それ以外にももしかしたらあるのかもしれないけれど知らない)。たとえばユーザ機能(GAE標準としてはGoogle AccountとOpen Idが用意されている)だったり、管理者機能、セッション機能なんかも当然ない。セッション機能とかはGAE用にgae-sessionsなどを開発してくれている人がいるけれどもどうせならここらへんの機能はフレームワークとして用意されているほうが好ましい。また生のGAEで困ったのがフォームの検証機能なんかも全然サポートされていない。これはかなり困った。ということでGAE上でDjangoを使用できるようにポートしてくれているDjango-nonrelを使用することにした。

Django-nonrelを使うことでModelFormやUser機能などDjangoの恩恵をすべて享受しているのに、さらにGAEのデータストアをラップしてくれているModel機能のお陰でBigtableも意識せずに使えちゃうというヘブン状態に突入できた。ただ当たり前なのだけれどもデータのjoinができないなどのGAEのデータ特性はそのままなのでそこらへんは注意しなければならない。

環境とか
Django-nonrelはほとんど素のDjangoと変わらずに開発できる(素のDjangoを触ったことがないので実際には分からないけど)のでストレスなくサクサク開発できるようになった。そこで更なる開発効率を達成するために下記のプラグインやらフレームワークやらを導入した。

Aptana Studio 3
これを入れるとPyDevが手に入り、さらにEclipse用のDjangoプラグインが手に入る。さらにさらにDjango Template Editorも手に入るのでマストゲットな一品。

Lettuce
Behavior Driven Development用のテストフレームワーク。これはかなり良い。C#用のMSpecとかよりも断然開発しやすい。MSpecが駄目というよりもDjango自体が自動テストで開発するのに向いてる環境だと実感した。

Selenium
ブラウザの挙動をそのままトレースできるツール。かなりのポテンシャルを感じるけれど、Lettuceとの相性がいまいちなのでいまいち把握していない。複雑なUIをぐりぐり作り始めたらちゃんと調査するつもり。

現在の開発環境は下記
Eclipse 3.6
PyDev
Django-nonrel
Lettuce

感想
GAEで開発しているという感じはほぼなく、Djangoで開発していると感じる。それと環境をしっかりと整えればかなり開発しやすいと感じた。まだまだTransaction周りなど、Django-nonrelがどのようにラップしているか正確に把握していないので手探りしながらなのは否めないけれど、大枠問題がない。あとPythonでの開発は楽しい。コンパイラが無いのはうっとうしいけれど、その分しっかりとテストコードを書いておけばいいのだし、もっとしっかりやるならコードカバレッジも併用すればよいんでしょ(まだ使ったことないけど)。

2011年5月5日木曜日

pipとかLettuceとかのセットアップ覚書 for Windows 7

Djangoアプリ用にBDDスタイルのテストフレームワークを探していたらLettuceがよさげだったのでインストールしてみた。その際に色々苦労したので今後のためのメモ。

まずpipをインストールする。
手順は下記を参考。
How to install pip on windows?

ついでLettuce。このTutorialどおりにpipを使ってのインストールから最初のテスト実行までを行う。
が、Lettuceを実行してみると表示がおかしい。本文以外に変な文字がたくさん出力される。


LettuceはWindowsに配慮されていないのでこういう悲しいことになっているようなのだが、それを解決する方法は下記の3. Fixing the Outputを参照のこと。
How to Install Lettuce in Windows

上記の方法を適用すると下記のように正しく表示される。


ここまででやっとスタートラインに立てた。でもまだDjangoと統合したり、Django-nonrelと一緒に使ってみたり、splinterも評価してみようかなーとか思ってるので道半ば。

2011年4月28日木曜日

Action、RenderAction、Partial、RenderPartialの違い

Action、RenderAction、Partial、RenderPartialの違いをよく忘れるので覚書。

まずActionとPartialの違い
Action は MVC のサイクルそのまま。つまり Controller の作成し Action を呼び出し、View の返却という流れ。Partial は View を返却するだけ。

ActionとRenderActionの違い
Action は 結果を文字列として返却するだけ。RenderAction は結果を Response にそのまま書き込んでくれる。それなので大量の Html を出力する場合などは RenderAction を使用するほうが効率的になる。

PartialとRenderPartialの違い
Partial は MvcHtmlString を返却するだけ。RenderPartial は呼び出し元の View にそのまま書き込んでくれる。それなので生成された Html をいじりたい場合などは Partial を使用する。言わずもがなパフォーマンス的には RenderPartial の方が良い。

2011年4月25日月曜日

複数選択を行える便利なjQueryのリストプラグイン その2

以前「複数選択を行える便利なjQueryのリストプラグイン ~ multiselect」という題で複数選択を行えるjQueryのプラグインを解説したけれど、先日これを使う機会がまたあり、触ってみたらEncoding周りで苦労したので解説しておく。


前回解説したときは英語を使用していたので気付かなかったのだが、日本語で使用するとプラグインそのままだと不便な箇所がいくつかあったのでそれの修正点もあわせて解説していく。ちなみに今回の環境はGoogle App Engine PythonをサーバとしフレームワークにはDjango-nonrelを使用している。

まずこちらのgithubからソースコードをダウンロードしよう。git cloneでダウンロードするとajax対応ではないソースコードが落ちてくるので画面右上のDownloadボタンから取得するように。ZIPファイルを解凍すると色々とファイルが入っているけれど、今回は下図のようなフォルダ構造にした。


実際のHtmlとプラグインを使用するJavascriptコードは下記のようになる。
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.5.1.min.js" type="text/javascript"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.12/jquery-ui.min.js" type="text/javascript"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/multiselect/plugins/localisation/jquery.localisation-min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/multiselect/plugins/tmpl/jquery.tmpl.1.1.1.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/multiselect/plugins/blockUI/jquery.blockUI.js"></script>
<script src="{{ STATIC_URL }}js/multiselect/ui.multiselect.js" type="text/javascript"></script>

<script type="text/javascript">
 $(function(){
  $.localise('ui.multiselect', {language: 'ja', path: '{{ STATIC_URL }}js/multiselect/locale/'});
  $("#groups").multiselect({
   remoteUrl: "/community_web/group/search"
  });
 });
</script> 

<select id="groups" class="multiselect" multiple="multiple" name="groups">
</select>

{{ STATIC_URL }}という記述はDjangoのテンプレート機能なので他のフレームワークを使用している場合は適切な形に修正して欲しい。前回解説したときと比べて変わったのはlocalizationプラグインを使用してローカライズを行っている箇所だ。下記にui.multiselect-ja.jsの内容を示す。

$.extend($.ui.multiselect.locale, {
 addAll:'すべて追加',
 removeAll:'すべて削除',
 itemsCount:'#{count}個選択',
 itemsTotal:'#{count}個',
 busy:'処理中...',
 errorDataFormat:"データ形式が予期しない形です。処理を続行できません",
 errorInsertNode:"選択肢を追加するときに問題が発生しました:\n\n\t[#{key}] => #{value}\n\n処理は中止されました。",
 errorReadonly:"選択肢 #{option}は読み取り専用です",
 errorRequest:"データ取得中にエラーが発生しました。 (Type: #{status})"
});

これで上記の内容で日本語表示される。ついでui.multiselect.jsのほうに手を加える。

// _registerSearchEvents内の$.ajaxを下記のように修正する
$.ajax({
 url: that.options.remoteUrl,
 data: $.extend(params, {q:encodeURIComponent(value)}),  // ここ重要
 success: function(data) { 
  that.addOptions(data);
  that._setBusy(false); 
},

デフォルトではサーバに投げるクエリ文字列をescape()でエンコーディングしているけれど、escapeが出力するエンコーディングはW3C標準ではないので、Django側で受けたときに正しくデコードできない。そのためescapeに変わってencodeURIComponent()を使用する。

それに併せて使い勝手の上でmultiselectのそのままの仕様だと日本語入力しづらいことこの上ないので、下記のように入力にあわせた自動検索を無効化しておく。

switch (e.which) {
 case 13:   // enter
  _searchNow(true);
  return false;

 //default:  // これをコメントアウト
 // timer = setTimeout(function() { _searchNow(); }, Math.max(that.options.searchDelay,1));
}

上記に併せてsearchDelayオプションも不要になったので気になる人は削除しておこう。これでクライアント側の準備は整ったのでサーバ側を見てみよう。リクエストハンドラーとして下記の一文をurlpatternsに追加しておく。

//...省略
(r'^community_web/group/search', 'community_web.views.group.search'),
//...省略

実際のリクエストハンドラーは下記。
group.py
def search(request):
    query = request.GET.get('q', None)    
    if query:
        query = urllib2.unquote(query.encode('ascii')).decode('utf8')
        list = ['%s=%s' % (group.id, group.title) for group in Group.objects.filter(title__startswith=query)]
        results = '\n'.join(list)
    else:
        results = ''        
    return HttpResponse(results)

検索文字列を取得するところでごにゃごにゃとやっている解説は下記の通り。
まず、query.encode('ascii')でobjectから文字列に変換している。ついで、urllib2.unquote()でエスケープ文字を普通の文字に変換している。そして最後にUTF8でデコードして完了。

これで日本語でも複数選択のプラグインが正常に動作するはずだ。

2011年4月4日月曜日

Google App Engine 開発サーバをコマンドラインから起動する

Windows 7 上で Google App Engine の Python をいじり始めたので今後のための備忘録とか、いまさらの内容とか。

手始めに Getting Started をざっと読んでみて、実際にちょこちょこいじり始めたら、しょっぱなの開発サーバをコマンドラインから起動するところではまったので今回はそれを。ランチャーを使えばこんなところ問題ないんだろうけど、やっぱりこういうのを見せられるとコマンドをぺこぺこ打つのをまねしたくなっちゃうわけで。

Hello, world! の Testing the Application の項でコマンドラインからの起動は helloworld フォルダで下記のコマンドを入力してね、となっているけれど動かない。

google_appengine/dev_appserver.py helloworld/

まず dev_appserver.py 自体が認識されない。GAE Python SDK をインストールするときに環境変数の Path へ下記の値が追加されているのだけれど、それが認識されない。

C:\Program Files\Google\google_appengine

※環境変数の確認方法
コンピュータ→プロパティ→システムの詳細設定→環境変数

よくよく見ると Path が追加されているのはユーザー環境変数のほう。それなのでシステムの環境変数の Path へ先ほどのパスを追加する(;で他のパスと区切るのを忘れないように)。これでコマンドラインで dev_appserver.py と入力すると GAE の開発サーバ用スクリプトが認識されているはずだ。

ついでアプリケーションのフォルダへ移動するのだが、そのまま helloworld に行くと都合が悪い。実際には helloworld の親フォルダへ移動する。helloworld フォルダが下記パスだとする。

c:\gae\helloworld

上記の場合は下記フォルダへ移動する。

c:\gae

この状態で下記コマンドを実行すると開発サーバが正常に起動される。

dev_appserver.py helloworld/

と、まぁ大した内容ではないのだけれど、ドキュメントのままだと動かんね、ということで。

2011年3月23日水曜日

ASP.NET MVCでCaptcha(キャプチャ)を使う

My Clipでもキャプチャを実装していたのだが、今回チヌかかり釣りMEGAでも同様のことをやりたかったので、昔のソースコードを引っ張り出してきて再確認したので、それをまとめておく。

使用しているのは下記ブログポストで紹介されているライブラリ。
ASP.NET MVC CAPTCHA

※最新のソースコードは下記ブログポストらしいのだが、ダウンロードしたバイナリをMVC 3の環境に組み込んでも参照しているアセンブリバージョンの兼ね合いでコンパイルができないので、今回はMy Clip実装時の上記ブログポストのソースコードで実装方法を紹介する。
MVC CAPTCHA for Preview Release 3

  1. 上記のリンクからソースコードをダウンロードし、ManagedFusion.Web のプロジェクトをコンパイルする
  2. コンパイルしてできた ManagedFusion.dll と ManagedFusion.Web.dll を ASP.NET MVC 3 のプロジェクトで参照する

3, 下記を web.config に追加する
<httpHandlers>
 <add verb="GET" path="captcha.ashx" validate="false" type="ManagedFusion.Web.Mvc.Handlers.CaptchaImageHandler, ManagedFusion.Web"/>
</httpHandlers>

4, 下記を Global.asax に追加する
public static void RegisterRoutes(RouteCollection routes)
{
 routes.IgnoreRoute("{handler}.ashx");
}

5, cshtml に下記を追加する
<div>
    @Html.Raw(Html.CaptchaImage(50, 180))
</div>
<div>
    @Html.Raw(Html.CaptchaTextBox("captcha"))
</div>
Html.Raw で囲んでいるのは CaptchaImage, CaptchaTextBox ともに文字列を返却するようになっているため。ソースコードを書き換えて各関数ともに文字列ではなく MvcHtmlString を返却するようにすれば Raw で囲む必要はなくなる。

6, コントローラーアクションに下記属性を追加する
 [CaptchaValidation("captcha")]
 [HttpPost]
 public ActionResult Register(RegisterModel model, bool captchaValid)
 {
   // Do something
 }
captchaValid にキャプチャでの検証結果が代入されている。

以上で5でコードを追加した cshtml を表示するばキャプチャが表示されているはずだ。

2011年3月8日火曜日

HttpWebRequestで画像をASP.NET MVCに送ってみる

当ブログ100回目のポストはHttpWebRequestを使って画像をASP.NET MVCで稼動しているサイトへアップロードする方法を解説する(元ネタはStackOverflow)。

クライアント - アップロード
static void UploadFile(string url, string file, string paramName, string contentType)
{
    var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
    var boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");

    var wr = (HttpWebRequest)WebRequest.Create(url);
    wr.ContentType = "multipart/form-data; boundary=" + boundary;
    wr.Method = "POST";
    wr.KeepAlive = true;
    wr.Credentials = System.Net.CredentialCache.DefaultCredentials;

    var rs = wr.GetRequestStream();
    var headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
    var header = string.Format(headerTemplate, paramName, file, contentType);
    var headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
    rs.Write(headerbytes, 0, headerbytes.Length);

    var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
    var buffer = new byte[4096];
    var bytesRead = 0;
    while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
    {
        rs.Write(buffer, 0, bytesRead);
    }
    fileStream.Close();

    var trailer = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
    rs.Write(trailer, 0, trailer.Length);
    rs.Close();

    WebResponse wresp = null;
    try
    {
        wresp = wr.GetResponse();
        var stream2 = wresp.GetResponseStream();
        var reader2 = new StreamReader(stream2);
        var result = reader2.ReadToEnd();
        Console.WriteLine(string.Format("File uploaded. Server response is: {0}", result));
    }
    catch (Exception ex)
    {
        if (wresp != null)
        {
            wresp.Close();
            wresp = null;
        }
        Console.WriteLine("Exception occurred!! " + ex.ToString());
    }
    finally
    {
        wr = null;
    }
}
データをポストするときのフォーマットを正確に作ってやって、そこに送りたいデータをはめ込むだけ。

クライアント - 使い方
UploadFile("http://localhost:55778/Image/PostImage",
          @"C:\test.JPG",
          "file", "image/jpeg");

ASP.NET MVC側 - ImageController
[HttpPost]
public int PostImage()
{
    var request = base.HttpContext.Request;
    if (request.Files.Count == 0)
        return 0;

    var httpPostedFile = request.Files[0] as HttpPostedFileBase;
    httpPostedFile.SaveAs("tekitouna_path.jpg");
    return 1;
}

上記のコードをASP.NET MVCサイトを動作させた状態でクライアントを実行すればサーバ側でファイルを受け取り保存することが可能なはずだ。

2011年2月20日日曜日

Gmailの内容をIMAPで取得する LumiSoft.Net編

前回のGmailの内容をIMAPで取得する ImapX編に引き続き、今回は下記ライブラリを使用してGmailの内容を取得する方法を解説する。

LumiSoft.Net
LumiSoft Forum
Source code
Examples

ImapXに比べてこなれていないObject Modelなのでとっつきにくいけれど、慣れればImapX以上の柔軟さがあるし、何よりOSSなので自分で問題解決できるのが良い。

コードはこんな感じ。
// すべてのメールを取得する
try
{
    using (var imap = new IMAP_Client())
    {
        imap.Logger = new Logger();
        imap.Logger.WriteLog += (s, e) => Console.WriteLine(e.LogEntry.Text);
        imap.Connect("imap.gmail.com", 993, true);
        imap.Login("username", "password");
        imap.SelectFolder("INBOX");

        var fetchHandler = new IMAP_Client_FetchHandler();
        fetchHandler.NextMessage += (s, e) =>
        {
            // 次のメッセージ
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("New message");
        };
        fetchHandler.Envelope += (s, e) =>
        {
            // To, Fromとか
            Console.WriteLine("Envelope");
            var envelope = e.Value;
            var from = "";
            if (envelope.From != null)
                from = envelope.From.ToList().Select(x => x.ToString()).Aggregate((x, y) => string.Format("{0};{1}", x, y));
            else
                from = "none";

            Console.WriteLine(from);
            var subject = envelope.Subject ?? "none";
            Console.WriteLine(subject);
        };
        fetchHandler.Flags += (s, e) =>
        {
            // フラグ(SEEN, UNSEENとか)
            e.Value.ToList().ForEach(f => Console.WriteLine(f));
        };
        fetchHandler.InternalDate += (s, e) =>
        {
            // 日付
            Console.WriteLine(e.Value.ToString());
        };
        fetchHandler.Rfc822Size += (s, e) =>
        {
            // サイズ
            Console.WriteLine(((decimal)(e.Value / (decimal)1000)).ToString("f2") + " kb");
        };
        fetchHandler.UID += (s, e) =>
        {
            // メールID
            Console.WriteLine(e.Value);
        };

        var sequence = new IMAP_SequenceSet();
        sequence.Parse("1:*");  // 取得する範囲を指定する
        // ※メールが15通あるとして
        // 2,4:7,9,12:*を指定すると
        // 2,4,5,6,7,9,12,13,14,15の順番のメールが取得できる
        // 詳細はIMAP_SequenceSetを参照のこと

        // 第一引数は指定のsequenceがUIDかどうかの判定用。UIDを指定する場合もIMAP_SequenceSetの記述は変わらない
        // 第三引数に指定したものがサーバーから取得され、値の解析後、fetchHandlerがコールバックされる
        imap.Fetch(false, sequence, new IMAP_Fetch_DataItem[]
                                                            {
                                                                new IMAP_Fetch_DataItem_Envelope(),
                                                                new IMAP_Fetch_DataItem_Flags(),
                                                                new IMAP_Fetch_DataItem_InternalDate(),
                                                                new IMAP_Fetch_DataItem_Rfc822Size(),
                                                                new IMAP_Fetch_DataItem_Uid()
                                                            }, fetchHandler);
    }
}
catch (Exception x)
{
    Console.WriteLine(string.Format("IMAP server returned : {0}", x.Message));
}

一見して分かる通りかなり癖があるのでとっつきにくい部分もあるが、慣れてしまえば細かいところまで制御できるので非常に有用だ。

もうひとつサンプルとして未読メッセージのみ取得する方法を紹介しておく。
try
{
    using (var imap = new IMAP_Client())
    {
        imap.Logger = new Logger();
        imap.Logger.WriteLog += (s, e) => Console.WriteLine(e.LogEntry.Text);
        imap.Connect("imap.gmail.com", 993, true);
        imap.Login("username", "password");
        imap.SelectFolder("INBOX");
        
        // 未読のものだけ取得
        var unseenMessages = imap.Search(false, "", "unseen");
        unseenMessages.ToList().ForEach(uid =>
        {
            var seqSet = new IMAP_SequenceSet();
            seqSet.Parse(uid.ToString());

            // Bodyのみハンドル
            var fetchHandler = new IMAP_Client_FetchHandler();
            fetchHandler.Rfc822 += (s, e) =>
            {
                var storeStream = new MemoryStream();
                e.Stream = storeStream;
                e.StoringCompleted += (s2, e2) =>
                {
                    storeStream.Position = 0;
                    var mime = Mail_Message.ParseFromStream(storeStream);

                    Console.WriteLine("Attachment");
                    foreach (var entity in mime.Attachments)
                    {
                        if (entity.ContentDisposition != null && entity.ContentDisposition.Param_FileName != null)
                        {
                            // 添付ファイルの保存
                            var path = Path.Combine(@"C:\", entity.ContentDisposition.Param_FileName);
                            File.WriteAllBytes(path,((MIME_b_SinglepartBase)entity.Body).Data);
                            Console.WriteLine(string.Format("{0} saved", entity.ContentDisposition.Param_FileName));
                        }
                        else
                        {
                            Console.WriteLine("untitled");
                        }
                    }

                    if (mime.BodyText != null)
                        Console.WriteLine(mime.BodyText);

                     // おまけ
                     // テスト用に今回の処理で既読メールになったのを未読メールに戻す
                     imap.StoreMessageFlags(true, seqSet, IMAP_Flags_SetType.Replace, IMAP_MessageFlags.Recent);
                };                
            };

            imap.Fetch(true, seqSet,
                new IMAP_Fetch_DataItem[]
                    {
                        new IMAP_Fetch_DataItem_Rfc822()
                    },fetchHandler);
        });
    }
}
catch (Exception x)
{
    Console.WriteLine(string.Format("IMAP server returned : {0}", x.Message));
}

Gmailの内容をIMAPで取得する ImapX編

送信されてきたメールをIMAPで取得、解析して内容をDBに突っ込もう、というツールが必要だったので良いライブラリが無いかと探して見つけたのが下記。

ImapX – free for use .NET library
数あるライブラリの中でもObject Modelがよく設計されているので至極簡単に使えて便利。ただOpen Sourceではないので何か問題があったときに何も対処できないし、作者のレスポンスもかなり遅いので対応は期待できないので注意が必要だ。

今回の要件は下記。
  • Gmailを使う
  • 画像の添付がある
  • IMAPを使う(未読メールのみ取得したいため)

GmailのアカウントでIMAPを使えるようにする方法は下記を参照のこと。
Enabling IMAP

で、実際のソースコードはこんな感じ。
var client = new ImapX.ImapClient("imap.gmail.com", 993, true);
var result = false;

result = client.Connection();
if (result)
    Console.WriteLine("@Connected");

result = client.LogIn("username", "password");
if (result)
    Console.WriteLine("@Logged in");

// 未読のものだけ取得
var messages = client.Folders["INBOX"].Search("UNSEEN", true);  // Searchの第二引数をfalseにする場合は、後でMessage.Processを呼び出す必要がある
foreach (var m in messages)
{
    //m.Process();  // Searchの第二引数がfalseの場合はこの処理が必要
    m.SetFlag(ImapX.ImapFlags.SEEN);    // 既読フラグを設定
    foreach (var attachment in m.Attachments)
    {
        var location = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
        attachment.SaveFile(Path.Combine(location, m.From[0].Address));     // SaveFileを呼び出すと添付ファイルのファイル名で指定の場所に保存してくれる
    }

    // テキストをデコードする
    var encoding = Encoding.GetEncoding("iso-2022-jp");  // 実際にはContentTypeのCharsetからエンコード名を取得しよう
    var text = encoding.GetString(encoding.GetBytes(m.TextBody.TextData));

    // Do someting...
}

Searchメソッドに使えるコマンドは、ALL、ANSWERE、BCC、BEFORE、BODY、CC、DELETED、DRAFT、FLAGGED、SEEN、UNSEENなどのほかにもかなりあるのでImapX.dllと一緒に配布されるimap search commands.txtを参照して欲しい。

POP3で良いのならOpenPop.NETもかなり使いやすい。サンプルコード集も分かりやすい。

2011年1月18日火曜日

ASP.NET MVC 3とEntity Framework Code Firstを触ってみた MySQL編

前回というかさっきなのだけどASP.NET MVC 3とEntity Framework Code Firstを触ってみた SQL Server編でSQL ServerをデータストレージとしたEF Code Firstの実装方法を解説した。今回はMySQLを使用する方法を解説する。

前回のソースコードをそのまま流用するのでこちらからダウンロードして欲しい。

・セットアップ
下記からそれぞれ自分の環境のものをダウンロードして欲しい。

MySQL Database
Download MySQL Community Server

MySQLのGUIツール
Download MySQL Workbench

MySQLのデータプロバイダー
Download Connector/Net

・MySQLのセットアップ
MySQLをはじめてインストールしたのだが「How to install MySQL on Windows」を参考に下記のようにセットアップした。

  • Typical Setup
  • スキップSign-Up
  • "Configure the MySQL Server now"をチェックする
  • "Detailed Configuration"
  • "Developer Machine"
  • "Multifunctional Database"
  • "InnoDB Tablespace Settings" 初期状態のままにする
  • "Decision Support (DSS)/OLAP"
  • "Enable TCP/IP Networking"をチェックする。またポート番号3306のままにする。"Enable Strict Mode"をチェックする
  • "Standard Character Set"
  • "Install As Windows Service"をチェックする。また"Launch the MySQL Server automatically"をチェックする
  • ルートパスワードを入力する。忘れないように!理由が無い限り"Enable root access from remote machines"はチェックしないほうが良いよ
  • "execute"をクリックしてインストール終了まで待機する


・接続文字列
ついでWeb.configの接続文字列を下記のように変更する。

<add name="Sengoku" connectionString="Server=localhost;Database=Sengoku;Uid=username;Pwd=yourpassword;" providerName="MySql.Data.MySqlClient" />

providerNameがMySqlClientになっているのに注目して欲しい。前述のConnector/Netがインストールされているとmachine.config(C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config)のDbProviderFactoriesセクションにMySql.Data.MySqlClientが登録されているので特に他の記述は必要ない。

・実行 with Code First
WindowsサービスでMySQLが動作しているのを確認し実行してみよう。これがSQL ServerならばEF Code FirstがDatabaseを作成してくれて正常動作するはずだ。しかし案に相違して「データベースSengokuが見つからない云々」という例外が発生する。

正直、Connector/NetがサポートしていないのかEF Code FirstがCTP5のためサポートされていないのかどちらなのかよく分からない。

・Sengoku Database作成
仕方が無いのでMySQL Administratorにログインし、Sengoku Databaseを作成した。
※ログイン時にUsernameとPasswordが尋ねられる。セットアップ時にPasswordの設定はするけれどUsernameの設定は無かったので面食らうと思うがここではrootと入力すれば良い。


また下記クエリをMySQL Query Browserで実行してTableをSengoku Database上に作成した。

DROP TABLE IF EXISTS `sengoku`.`bushous`;
CREATE TABLE  `sengoku`.`bushous` (
  `bushouid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` nvarchar(10) NOT NULL,
  `address` nvarchar(10) NOT NULL,
  PRIMARY KEY (`bushouid`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

DROP TABLE IF EXISTS `sengoku`.`comments`;
CREATE TABLE  `sengoku`.`comments` (

  `commentid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `commentedbushouid` int(10) unsigned NOT NULL,
  `commentbushouid` int(10) unsigned NOT NULL,
  `text` nvarchar(30) NOT NULL,
  PRIMARY KEY (`commentid`),
  FOREIGN KEY (commentedbushouid) REFERENCES bushous(bushouid),
  FOREIGN KEY (commentbushouid) REFERENCES bushous(bushouid)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

これで再度MySQL AdministratorでSengoku Databaseを確認するとTableが2つできているのが確認できると思う。

・実行
この状態で実行すると下図のように正常動作する。武将登録やコメント追加も行える。


・まとめ
MySQLの場合はDatabaseを事前に用意しておかないといけない、という違いはあるもののおおむねSQL Serverと同じ挙動だった。EF Code Firstが正式リリースされたときにここらへんの修正が入るのか、それともConnector/Netのほうの修正が必要なのかは不明だが、現状さしたる不都合は無さそうだ。

ASP.NET MVC 3とEntity Framework Code Firstを触ってみた SQL Server編

ASP.NET MVC 3がリリースされたので、Entity Framework Code First:CTP5と一緒に評価してみた。今回のデータストレージはSQL Server 2008を使用するがそのうちMySQLを使って解説したい。

・セットアップ
それぞれ下記からインストールして欲しい。
ASP.NET MVC 3
EF Code First CTP5

EF Code FirstはNuGetを使用したほうがより簡単に導入できるのでそちらの方法をお勧めする。NuGetを使用しての詳細な説明はこちら。NuGetでインストールする方法は、View->Other Windows->Package Manager Consoleで“Install-Package EFCodeFirst”と入力するだけだ。

はじめてASP.NET MVCを触る人は下記を参考にしてもらいたい。
Intro to ASP.NET MVC 3
ASP.NET MVC3の基礎を知ることができるので有用だ。これと合わせてユニットテスト用にRepositoryパターンなども勉強すると良いだろう。

・今回のアプリ
今回は次のようなWebアプリを作成する。武将の一覧があり、武将の登録、編集、削除が行え、かつ武将から武将へのコメントも行える。もちろんコメントの削除、編集も行える。

Index.cshtml

Create.cshtml - 検証(StringLength)

Create.cshtml - 検証(Required)

AddComment.cshtml

・EF Code First
今回はEntity Framework Code Firstでデータストレージを作成するので前もってDatabaseやTableを用意しておく必要がない。Data Modelは下記になる。

public class Bushou
    {
        public int BushouID { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }

        public virtual ICollection<Comment> Comments { get; set; }
    }

    public class Comment
    {
        public int CommentID { get; set; }
        public int CommentedBushouID { get; set; }
        public int CommentBushouID { get; set; }
        public string Text { get; set; }
   
        public virtual Bushou CommentedBushou { get; set; }
        public virtual Bushou CommentBushou { get; set; }
    }

    public class Sengoku : DbContext
    {
        public DbSet<Bushou> Bushous { get; set; }
        public DbSet<Comment> Comments { get; set; }

        protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Comment>().HasRequired(c => c.CommentedBushou)
                                  .WithMany(m => m.Comments)
                                  .HasForeignKey(c => c.CommentedBushouID)
                                  .WillCascadeOnDelete();

            modelBuilder.Entity<Comment>().HasRequired(c => c.CommentBushou)
                                          .WithMany()
                                          .HasForeignKey(c => c.CommentBushouID)
                                          .WillCascadeOnDelete(false);
        }
    }

Bushouクラス、Commentクラスともにvirtual属性以外のものがいずれTableの各列となる。またEF Code FirstはConvention(規約、協定みたいなもの)があり、たとえばBushouクラスであればID、またはBushouIDとなっているものを主キーとみなし自動的に設定を行ってくれる。また外部キーも同様に、今回の例ではないがCommentクラスにBushouIDなどがあればそれを外部キーとして設定を行ってくれる。ここでは、コメントされる人、コメントする人、と2つの外部キーがあるためConventionの法則に則っていないのでその設定は自動で行われない。そういう場合はDbContextのOnModelCreatingを使い自前で設定を行う。OnModelCreatingで行っている処理はFluent APIと呼ばれるもので、大抵の設定はこれで行えてしまう。Fluent APIの詳細な資料はこちらを参照してほしい。

他にも[Key]属性や[ForeignKey]属性などプロパティに設定することでFluent APIを使用せずとも明示的にフレームワークに通知することができる。

・Error Validation
EF Code FirstはData Annotationもサポートされている。つまり[Required]属性や[MaxLength]属性などでデータの説明を行えるようになっている。さらにData AnnotationはASP.NET MVC3と完璧に協調するのでクライアントサイド検証や、サーバサイド検証をフレームワークが自動で行ってくれる。しかしここに一つ問題がある。というのもData Annotationのデフォルト言語は英語のためエラーメッセージはすべて英語になってしまう。ここで前述のData Modelを検証と日本語に対応したものに更新しよう。

public class Bushou
    {
        public int BushouID { get; set; }

        [Required(ErrorMessageResourceType = typeof(ErrorMessages), ErrorMessageResourceName = "Required")]
        [StringLength(10, ErrorMessageResourceType = typeof(ErrorMessages), ErrorMessageResourceName = "StringLength")]
        [Display(Name="名称")]
        public string Name { get; set; }

        [Required(ErrorMessageResourceType = typeof(ErrorMessages), ErrorMessageResourceName = "Required")]
        [StringLength(10, ErrorMessageResourceType = typeof(ErrorMessages), ErrorMessageResourceName = "StringLength")]
        [Display(Name = "拠点")]
        public string Address { get; set; }

        public virtual ICollection<Comment> Comments { get; set; }
    }

    public class Comment
    {
        public int CommentID { get; set; }

        [Display(Name = "コメントされる人")]
        public int CommentedBushouID { get; set; }

        [Display(Name = "コメントする人")]
        public int CommentBushouID { get; set; }

        [Required(ErrorMessageResourceType = typeof(ErrorMessages), ErrorMessageResourceName = "Required")]
        [StringLength(30, ErrorMessageResourceType = typeof(ErrorMessages), ErrorMessageResourceName = "StringLength")]
        [Display(Name = "コメント")]
        public string Text { get; set; }

        public virtual Bushou CommentedBushou { get; set; }
        public virtual Bushou CommentBushou { get; set; }
    }

BushouクラスのNameプロパティやAddressプロパティにそれぞれRequired属性とStringLength属性が付与されている。またErrorMessageResourceTypeでリソースクラスの指定、ErrorMessageResourceNameでリソースキーの指定を行っている。リソースクラスを使う方法以外にもErrorMessageを直接指定する方法があるが、ここでは前者を採用している。ちなみにこのリソースクラスを使用する方法は多言語対応と同じ方法だ。

リソースクラスの追加方法を説明する。まずは下図のようにASP.NETフォルダの追加からApp_GlobalResourcesフォルダを追加し、ErrorMessages.ja.resxとErrorMessages.resxを追加しよう。


ついで、ErrorMessages.ja.resxに下図の内容を追加する。その際にAccess ModifierをPublicにすることを忘れないように注意しよう。


またErrorMessages.resxにも同様のキーを追加しておこう。ここにキーを追加しておかないと実行時にリソースキーが見つからないというエラーになる。

※Data Annotationのデフォルト言語を英語以外にする方法は現状無い
日本語のみを対象とするアプリケーションでは今回のような多言語対応の方法だとかなり冗長になる。そのためそれぞれリソースクラスを指定する方法ではなく、Data Annotationのデフォルトエラーメッセージ自体を変更できないかと色々と調べたけれどその方法はなさそうだ。というのもReflectorでSystem.ComponentModel.DataAnnotations.dllの中身を調べたけれど、エラーメッセージ表示部分で内部リソースを直接参照しており、そのリソースをアセンブリ外部から操作できないようになっていた。

・ASP.NET MVC3
ここからASP.NET MVC3のアプリケーションの説明をする。新規プロジェクトからASP.NET MVC 3 Web Applicationを選択し、Emptyプロジェクトを選択しよう。名前は適当につけてほしい。

まず、ControllersフォルダにHomeControllerを追加しよう。なぜHomeかというとデフォルトRouteを変更するのが面倒だからだ。デフォルトRouteを変更したい場合はGlobal.asax.csでRouteのマッピングを行っているのでそこで変更すればよい。

まず最初にIndexを追加する。

Sengoku db = new Sengoku();
        public ActionResult Index()
        {
            return View(db.Bushous.ToList());
        }

ここでは面倒なのでSengokuはHomeControllerのフィールド変数としているけれど、実際のプロジェクトではユニットテストでMockオブジェクトを使用するためにデータアクセス層を分けるのでこのような実装をすることはない。詳しくはRepositoryパターンを調べ欲しい。

またSengokuクラスをインスタンス化しているが、EF Code FirstのConventionの一つでクラス名と同一のコンフィグキーを探し、その接続文字列でDatabaseに接続するというのがある。そのため下記の接続文字列をWeb.configに追加しておこう。

<add name="Sengoku" connectionString="Data Source=.;Initial Catalog=Sengoku;Persist Security Info=True;User ID=yoo;Password=matsuosoftwareisgreat" providerName="System.Data.SqlClient" />

また最初のほうで言及したように接続時にSengoku Databaseが無ければEF Code Firstは定義されている内容でDatabaseを作成してくれる。今回はSQL Server 2008を使っているがExpressでも同様の動作をする。

ついで、Index.cshtmlを追加する。Index()で右クリック→Add ViewからIndex.cshtmlを追加しよう。その際にCreate a strongly-typed viewにチェックをして型を指定するとScaffold templateを指定できる。ちなみにここではIListを指定しているのでtemplateの選択はできない。


追加されたIndex.cshtmlを編集したもの。View追加時に指定したようにView EngineにRazorを使用している。Razorの文法についてはこちらを参照してほしい。

@model IList<EFCodeFirst.Models.Bushou>
@{
    ViewBag.Title = "武将一覧";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
    一覧</h2>
@Html.ActionLink("武将登録", "Create")
<div style="margin-top:20px;">
    @foreach (var bushou in Model)
    { 
        <div style="padding:10px;border:1px dashed gray">
            <div style="width:320px;float:left;">
                @Html.ActionLink(bushou.Name, "Edit", new { id = bushou.BushouID }) @string.Format("({0}在住)", bushou.Address)
            </div>
            <div style="float:left;">
                @Html.ActionLink("削除", "Delete", new { id = bushou.BushouID }) |
                @Html.ActionLink("コメント追加", "AddComment", new { id = bushou.BushouID })
            </div>
            <div class="clear"></div>
        @foreach (var comment in bushou.Comments)
        {
            <div style="width:300px;float:left;padding-left:20px;">
                @Html.ActionLink(comment.Text, "EditComment", new { id = comment.CommentID }) by @comment.CommentBushou.Name
            </div>
            <div style="float:left;">
                @Html.ActionLink("削除", "DeleteComment", new { id = comment.CommentID })
            </div>
            <div class="clear"></div>
        }
        </div>
    }
</div>

@modelでこのページのModelの型を指定している。またLayoutで使用するASP.NETで言うところのマスターページを指定している。内部的には何も難しいことはしておらず、Modelをforeachでぐるぐると回してタグを作成している。ここで一点注目して欲しいのはコメントタグ作成部分だ。ちゃんとcomment.CommentBushou.Nameが取得されているのが分かる。

次にCreateを追加する。

public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Create(Bushou bushou)
        {
            if (ModelState.IsValid)
            {
                db.Bushous.Add(bushou);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            else
                return View(bushou);
        }

とくに難しいことも無いので説明は省略する。Viewの追加からBushouクラスをModelとしたCreate.cshtmlを追加する。

@model EFCodeFirst.Models.Bushou

@{
    ViewBag.Title = "武将登録";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>武将登録</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>武将</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Address)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Address)
            @Html.ValidationMessageFor(model => model.Address)
        </div>

        <div style="clear:both"></div>
        <p>
            <input type="submit" value="登録" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("一覧へ戻る", "Index")
</div>

クライアントサイドの検証を有効にするためにはWeb.configのClientValidationEnabledとUnobtrusiveJavaScriptEnabledをTrueに設定しておく必要がある。ASP.NET MVC3ではデフォルトでTrueになっている。また_Layout.cshtmlで下記スクリプトが指定されていることを確認しよう。

<script src="@Url.Content("~/Scripts/jquery-1.4.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

これでStringLengthで指定されている文字数以上を入力したり、Required指定されている項目を未入力にしたりするとエラーメッセージが表示される。

・まとめ
ここまで見てきて分かると思うがかなり開発しやすくなっている。RazorがデフォルトのView Engineとして提供されたおかげでプログラムコードとHtmlの分離が簡単になった。またData Annotation機能がさらに進化したおかげで検証の組み込みも簡易に行えるし、一つの設定でViewからDatabaseまで有機的に協調できているのが素晴らしい。何よりEF Code FirstはDatabaseの更新を容易に行えるという以上にユニットテストにおいて同一のData Modelが使えるというのが良い。実際に触ってもらうと分かると思うがより開発速度が高まったのを実感できると思う。

他にも今回のアプリではEditやAddCommentといった機能があるが大体ここまでで説明してきたことと一緒なので省略する。興味がある人はソースコードを添付しておくので下記から取得してほしい。


ソースコード

次回はデータストレージをMySQLにした場合の解説をする予定だ。

2011年1月6日木曜日

Canvasにリストの中身をBindingする方法

ObservableCollectionでもなんでもいいのだが、リストで保持している内容をCanvasに表示したいことはままある。しかしCanvas.Childrenに対して単純にBindingしても予期した動作にはならない。そんな場合はItemsControlのItemsPanelTemplateを使用する。

こんな風にデータをCanvas上に表示したい。

public ObservableCollection<TextData> TextList { get; set; }
TextList = new ObservableCollection<TextData>(
                new List<TextData>
                {
                    new TextData{ Left=110, Top=110, Text="嘘だと" },
                    new TextData{ Left=210, Top=210, Text="言ってよ" },
                    new TextData{ Left=310, Top=310, Text="バーニィ" },
                });
各TextDataクラスはLeft, TopでCanvas上の位置を保持している。

では、上記のリストをCanvas上に配置するためのXamlを見てみよう。
<ItemsControl ItemsSource="{Binding Path=TextList}"
                      VerticalAlignment="Stretch"
                      HorizontalAlignment="Stretch">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemContainerStyle>
                <Style>
                    <Setter Property="Canvas.Top" Value="{Binding Path=Top}" />
                    <Setter Property="Canvas.Left" Value="{Binding Path=Left}" />
                </Style>
            </ItemsControl.ItemContainerStyle>
        </ItemsControl>
ポイントはItemsControl.ItemsPanelのItemsPanelTemplateを使用している箇所。そこでアイテム配置用のパネルにCanvasを指定しているので、下のほうのItemsControl.ItemContainerStyleで各アイテムのLeft, Topを指定して任意の位置にデータを表示させることが可能となる。

サンプルコードはこちら。
(Chromeだと下記iframeが表示されない模様。FireFoxでは表示確認済み)

2011年1月5日水曜日

SQL Serverのクエリプランをクエリと一緒に取得する便利スクリプト

元ネタはこちら:SQL SERVER – Get Query Plan Along with Query Text and Execution Count

稼働中のSQL Serverの状態をチェックしたくなることはままあることで、パフォーマンスを計る上でも各オブジェクトのクエリプランやら実行回数を知っておくのは悪くない。というわけで下記クエリを実行すると下図の結果が得られる。
(Where句をいじることで必要なオブジェクトのみ参照もできる)

SELECT cp.objtype AS ObjectType,
OBJECT_NAME(st.objectid,st.dbid) AS ObjectName,
cp.usecounts AS ExecutionCount,
st.TEXT AS QueryText,
qp.query_plan AS QueryPlan
FROM sys.dm_exec_cached_plans AS cp
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) AS st
--WHERE OBJECT_NAME(st.objectid,st.dbid) = 'YourObjectName'

上記のクエリからこういう結果がでる。

上図でQueryPlanのリンクをクリックすると下図のクエリプランが表示される。

型付けされたページでModelがいつもオブジェクトになってしまう場合 ASP.NET MVC 2

以前とあるASP.NET MVCのプロジェクトをMVC2に変換をし、とあるページを追加したところ、そのページはある型(便宜上TestModelとする)を使用していたのだが、どうにもこうにもその型を認識してくれない。何をやってもObjectとしてしか認識されず非常にイライラしたのでここに解決方法を紹介しておく。

解決方法と言ってもViewsフォルダー配下のWeb.configを開き、下記部分を修正するだけだ。

これを
<pages
    validateRequest="false"
    pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
    pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
    userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
  <controls>
    <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
  </controls>
</pages>

こちらに変更する。
<pages
    validateRequest="false"
    pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
    pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
    userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
  <controls>
    <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
  </controls>
</pages>

これで正しく型が認識されるはずだ。

RelayCommandの実装いろいろ MVVM Light Toolkit

MVVM Light ToolkitのRelayCommandの実装方法を色々と紹介する。今回はWPFで実装している。


1、コマンドパラメータなし
何も特別なことはなし
Xaml
<Button Command="{Binding ClickCommand}" 
        Content="Click Me" />

ViewModel
public RelayCommand ClickCommand { get; private set; }
ClickCommand = new RelayCommand(() =>
{
  // Do something...
});


2、コマンドパラメータあり
ItemsControlのDataTemplate内要素のイベントをフックしている。ここでのDataTemplateのDataContextはDataItemになるので、単純にClickCommandをBindingしても期待した動作にはならない。そのためRelativeSrouceで一番上の親要素(Window)までたどってViewModelを取得している。CommandParameterにDataTemplateのDataContextをBindingしている。

Xaml
<ItemsControl ItemsSource="{Binding DataList}">
 <ItemsControol.ItemTemplate>
  <DataTemplate>
   <Button Command="{Binding DataContext.ClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
       CommandParameter="{Binding}"
           Content="Click Me" />
   </DataTemplate> 
 </ItemsControol.ItemTemplate>
</ItemsControl>

ViewModel
public ObservableCollection<DataItem> DataList { get; set; }
public RelayCommand<DataItem> ClickCommand { get; private set; }
ClickCommand = new RelayCommand(x =>
{
  // Do something...
});


3、ICommand以外をBinding
WindowのLoadedイベントなどをBindingしたい場合はEventToCommandを使用する。
・まずXAMLのネームスペースに下記を追加する:
 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
 xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
・後はフックしたいイベントを下記の要領で記述する

Xaml
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
        DataContext="{Binding ViewModel, Source={StaticResource Locator}}">
  <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <cmd:EventToCommand Command="{Binding WindowLoadedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

ViewModel
public RelayCommand WindowLoadedCommand{ get; private set; }
WindowLoadedCommand= new RelayCommand(() =>
{
  // Do something...
});


4、ICommand以外をBinding パラメータあり
WindowのClosingイベントなどでパラメータを渡したい場合はPassEventArgsToCommandをTrueに設定する。

Xaml
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
        DataContext="{Binding ViewModel, Source={StaticResource Locator}}">
  <i:Interaction.Triggers>
        <i:EventTrigger EventName="Closing">
            <cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
    </i:Interaction.Triggers>

ViewModel
public RelayCommand WindowClosingCommand<System.ComponentModel.CancelEventArgs>{ get; private set; }
WindowClosingCommand= new RelayCommand(x =>
{
  // Do something...
});

今回解説したようにEventToCommandを使用するとICommandに対応していないイベントもBinding可能となる。Window要素のイベントに限らずどのような要素にも適用できるのでEventToCommandを使用すればコードビハインドを経由してイベントをリレーする必要がなくなる。