2011年5月4日水曜日

ぷっすぷすにしてやんよ、Glimpseで。

Glimpse - A client side look at whats going on in your server

なんと素敵なツールでしょうね!

glimpse1

↑ChromeでGlimpseをオンにしたうえに、デベロッパーツールもオンにした状態。かぶせまくり。

これ何?っていうのは上記Glimpseサイトのビデオか、MIX11でのHanselmanさんのセッションビデオ見れば一発でわかります。と言ってしまうとブログに書く必要が無くなってしまうので、少し説明すると、Trace.axdやelmah.axd、RouteDebuggerを全部ひっくるめて組み合わせてさらにサーバーサイドの情報をひと通り参照できるようにするツールデス。分かりにくいっすね。

とにかく、NuGetでInstall-Package Glimpseと入力してプロジェクトに入れて見ましょう。

その状態でデバッグを開始すると、おや?ナニも変化ないですね。なので、URLに/Glimpse/ConfigといれてGlimpse機能をOnにしまいしょう。Onにした後はURLをもとのページに自分で戻すことを忘れずに。

glimpse2

そうすると画面右下に変な目玉アイコン(Glimpseアイコンね)が表示されるのでそこをクリック!

glimpse3

そうすると出てきます。

glimpse4

すごいねー。ちなみにブラウザに表示されてるのはプラグインとかじゃなくて100%JavaScriptだけで作られたものです。

glimpse5 glimpse6

ソース表示すると</html>の後にデータがレンダリングされて、最後にglimpseClinet.jsがロードされてるのが確認できます。

あとは、見たまんまです。各タブにそれぞれ情報が表示されます。

  • Binding
    まだ開発中~。Binderの情報がでる予定(だと思います)
  • Config
    Web.Configの設定内容をセクション別に表示
    config
  • Environment
    サーバーの実行環境の情報
    Application Assemblies/System Assembliesが見れるのは嬉しいですね
    emvironment
  • Execution
    ActionInvokderの実行内容を表示(だと思う)
    Filterの実行も見れるよ!
    execution
  • Glimpse Warnings
    Glimpseの何か
  • MetaData
    どこぞのMetadata
  • Remote
    リクエスト履歴っぽい
  • Request
    HttpRequestBaseの中身からCookie,Form,QueryStringを表示
    request
  • Routes
    表示内容(アクション)に到達するルーティングの解決順とマッチしたルート
    route
  • Server
    ServerValiablesの内容
  • Session
    Sessionの内容
  • Trace
    System.Diagnostics.Traceに出力した内容
  • Views
    表示内容(View)の解決に至る過程
    view
  • XHRequest
    まだっぽい

全部スクリーンショットをとるのが面倒だったから動かして確認してみてね!

んじゃ、そもそもGlimpseの仕組みはどうなってるの?って気になるよね。NuGetからインストールすると使うのは楽チンだけどソースが確認できないから、改めてソースをダウンロードしましょう。

Glimpse/Glimpse - GitHub

真っ先に気になるのが、PreApplicationStartCode。当たり前のようにスタートアップコードが書かれてる。んで、その中身はMicrosoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModuleを使ったカスタムIHttpModuleの動的登録。その中で各種プラグイン(タブで表示する情報を取得したりするもの)をMEFで登録。

Moduleでハンドリングするタイミングは以下の4つ。

context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
context.PostRequestHandlerExecute += PostRequestHandlerExecute;
context.PreSendRequestHeaders += PreSendRequestHeaders;

ここから結構気になってるExecutionで表示してるのがホントにActionInvokerに介入した結果なのかチェックしてみよう。

ExecutionプラグインのSetupInitでいろいろ仕込んでますね。ControllerFactoryとDependencyResolverをGlimpseのものに置き換えてます。GlimpseControllerFactory.CreateControllerでIControllerExtentions.TrySetActionInvokerを呼んでCastleのInterceptorを利用して、プロキシを生成しそれをActionInvokerにセットしてました。これでプロキシが必ず呼び出されるから、実行をインターセプトして実行情報を取得できるということですね。うほ。

DependencyResolverを利用した場合にも大丈夫なようにGlimpseDependencyResolverが用意されてて、こっちでも同じくProxyを生成するように抜かりなし。

他のプラグインもIGlimpsePlugin実装になってて、SetupInitしてGetDataを呼ぶことで各種データをHttpContext.Itemsに入れることで、最終的にGlimpseRespondersがJsonにシリアライズ。まるっとApplication編集に保存してGlimpseResponders.StandardResponseがResponse.Writeしてる。

Glimpseの仕組み

MEFを使ったプラグイン構造でContext.ItemsからApplication変数にデータを保持(configのsaveRequestCountはこの履歴保持数ということみたい)し、それをレスポンス(リクエスト毎にGuidを発行してコンテキストがきちんと判定できるよう保持)。各種情報はプラグインが自立して取得するけど、実行状態のインターセプトはCastleがProxyを生成して介入。

イロイロなプラグインを作っていけば、データの取得と表示はベースの機能でまかなえる素敵設計であるのと、.NET4、MVC3の機能もめいいっぱい利用したお洒落実装。これは萌える。萌え萌え。ぷっすぷすにされちまったぜ!

2011年5月3日火曜日

Dapper.NET

dapper-dot-net - Simple SQL object mapper for SQL Server - Google Project Hosting

これ。Massiveと同じくらい短いコードのORM。WebMatrix.Data.Databaseみたいなものですね。

オープンソースとしてたくさん存在する軽量ORMのなかでもDapperがスゴイところがパフォーマンス。どのくらい早いのかは上記サイトを確認してみると各種ORMとの速度比較が出てます。

A day in the life of a slow page at Stack Overflow

ASP.NET MVC & SQLServer & LINQ to SQLのスタックで有名な大規模サイトといえばStackoverflow.comですね。なんとそのStackoverflow.comでパフォーマンスに問題が出てきたので解決するために、このDapperを利用したというじゃないですか。

そもそもORMでN+1問題が出やすいので、LINQならjoin使ってたくさんのSQLを発行しないようにするとか、DBとのあいだのやりとりもちゃんと確認する(パフォーマンスに問題があると認識したならの話です。問題になってないなら気にしなくていいですよ)必要がありましょう。その辺はちゃんと実装したとしてもDapperが早いということです。Emitしちゃってるし。

使い方も簡単でIDbConnectionの拡張メソッドとして実装してるので、DBコネクションさえあれば簡単に導入できます。もちろんモデルクラスをPOCOで定義しておくことが最速を維持する秘訣。

AdventureWorksLT2008R2をDBに利用するためのサンプル。

public class Product
{
  public int ProductId { get; set; }
  public string Name { get; set; }
  public string ProductNumber { get; set; }
}

using (var connection = connectionFactory())
{
  var result = connection.Query<int>("select count(*) from Product").Single();
  Console.WriteLine(result + " products.");
}

// 1テーブルを1クラスにマッピング
using (var connection = connectionFactory())
{
  var result =
      connection.Query<Product>(
          "select * from Product where ListPrice between @low and @high",
          new { low = 10.0, high = 100.0 });
  Console.WriteLine("-- simple mapping:" + result.Count());
  foreach (var p in result)
  {
      Console.WriteLine(string.Format("ID:{0},Name:{1}", p.ProductId, p.Name));
  }
}

// ジェネリック指定しないで1テーブルをマッピング
using (var connection = connectionFactory())
{
  var result =
      connection.Query(
          "select * from Product where ListPrice between @low and @high",
          new { low = 10.0, high = 100.0 });
  Console.WriteLine("-- dynamic mapping:" + result.Count());
  foreach (var p in result)
  {
      Console.WriteLine(string.Format("ID:{0},Name:{1},Price:{2}", p.ProductID, p.Name, p.ListPrice));
  }
}

dapper2

ここで、実際にテーブルにはたくさん項目あるけど、モデルには省略しちゃってるんですけど、このままSQLにjoin含めた場合、ちゃんとモデルにはマッピングできませんでした。dynamicだとうまくいくんだけど、その辺の整合性はちゃんととっておかないとダメなわけですね。でもdynamicな戻り値なら早いわけではない。

dapper1

var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < 100; i++)
{
  using (var connection = connectionFactory())
  {
    var result =
        connection.Query(
            @"select p.*, c.ProductCategoryId as CategoryId, c.Name as CategoryName
          from Product as p 
            inner join ProductCategory as c on 
              p.ProductCategoryID = c.ProductCategoryID 
          where p.ListPrice between @low and @high",
            new { low = 10.0, high = 100.0 });
    foreach (var p in result){}
  }
}
stopwatch.Stop();
Console.WriteLine("dapper time:" + stopwatch.ElapsedMilliseconds + " ms");

stopwatch.Reset();
stopwatch.Start();
for (var i = 0; i < 100; i++)
{
  using (var l2s = new AwDataContext(connectionFactory()))
  {
    var result = from p in l2s.Product
                 join c in l2s.ProductCategory on p.ProductCategoryID equals c.ProductCategoryID
                 select
                     new
                         {
                             ProductId = p.ProductID,
                             p.Name,
                             Category = c.Name
                         };
    foreach (var p in result) {}
  }
}
stopwatch.Stop();
Console.WriteLine("linq2sql time:" + stopwatch.ElapsedMilliseconds + " ms");

大きなサイトへ導入されてるというのは、興味深いじゃないですか。それだけの負荷で問題が起きないように丁寧な実装がされてるわけで(どっちもだけど)。

今後、より使いやすくなることを期待しつつ見守っていきたいプロジェクトです。

2011年4月24日日曜日

IIS ExpressでSSLを有効にしてデバッグ

Working with SSL at Development Time is easier with IISExpress - Scott Hanselman

Hanselmanさんのエントリに書かれてる通りデス!

IIS ExpressでSSLを有効にしたデバッグだと無効な証明書だと警告はでるけど、HTTPSでのテストはコレでできますよね。

だけど、名前はlocalhostに限定です。これはまぁ、そういうバインドしかapplicationHost.configに登録してないからなのと、アクセス許可をしてなからなんですが(詳しくはmvcConf 2 - Vaidy Gopalakrishnan: IIS Express | mvcConf | Channel 9)、その辺をちゃんと登録して気持ちよく使いましょうという内容ですね。途中URLRewriteでAccountControllerへのアクセスをHTTPSに書き換えるというのが挟まってるけど、コードで対応するならここは飛ばしてもOKです。

netshへのcerthashの登録、信頼されたルート証明機関の証明書として証明書を登録、最終的にIisExpressAdminCmdでACLの許可登録で気持よく、マシン名でもSSL有効な状態でアクセスできます。

というのを、自分の環境でも確認してみましょう。

1.ASP.NET MVCサイトを作成

もちろんVS2010で行うのが簡単ぽんなんですが、VS2008でも同じようにできるので(applicationHost.configとnetshなので)、そっちしかない場合はそっちで。プロジェクトの設定で、外部プログラムを起動するを利用しましょう(IIS ExpressのVS2008/VS2010利用)。

今回は簡単なVS2010で新しくなったMVC3 Toolsで作ります。

ssl1

いい感じにHTML5ですね!

2.プロジェクトのプロパティでSSL有効化

ssl2

trueにするだけの簡単な作業です。これでSSL URLが入ります。この例だと https://localhost:44301/ ですね。

この状態でまずは実行してみましょう。

ssl3

文句は言われるけど、普通に問題なくアクセスできますね。楽チンデス。IIS Express最高!

3.localhostじゃなく好きな名前でアクセス

マシン名でもいいんですが、そこはほら、恥ずかしい名前付けてる場合もあるじゃないですか。そんな状態で表示するのもどうかと思うわけですよ。なので、今回は完全カスタムな名前にしてしまいます。

takepara-local

という名前にします。これが恥ずかしい名前じゃないかと言われると、まぁ微妙ですが。あ、ちなみに自分のマシン名はそんなに恥ずかしい名前じゃないですよ。

IisExpressAdminCmdでアクセスできるようにしましょう。

※”.”でドメインな感じのマシン名だとsetupFriendlyHostnameUrlに失敗します。

ssl6

これでブラウザからアクセスしてみましょう!

ssl7-2

残念。エラーです。何か忘れてますね。そうですね、applocationHost.configにバインド登録してなかったですね。

<site name="SslTest" id="15">
    <application path="/" applicationPool="Clr4IntegratedAppPool">
        <virtualDirectory path="/" physicalPath="..." />
    </application>
    <bindings>
        <binding protocol="http" bindingInformation="*:51578:localhost" />
        <binding protocol="https" bindingInformation="*:44301:localhost" />
        <binding protocol="http" bindingInformation="*:80:takepara-local" />
        <binding protocol="https" bindingInformation="*:443:takepara-local" />
    </bindings>
</site>

ssl7

グレートですね。ちなみにhostsにtakepara-local登録して、netshでurlacl登録しても一緒です。

このまま、調子にのってHTTPSにしてみる。

ssl8

残念。ダメでした。

4.証明書を作成

takepara-localの証明書を作成してませんでしたね。hanselmanさんのエントリに書かれてるとおりです。makecertしましょう。CNだけ変えて後はそのままで。

ssl9

makecert -r -pe -n "CN=TAKEPARA-LOCAL" -b 01/01/2000 -e 01/01/2036 -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12

VSコマンドプロンプトから実行しましょう。パス通ってて確実。

ssl10

証明書をnetshで登録。

netsh http add sslcert ipport=0.0.0.0:443 appid={214124cd-d05b-4309-9af9-9caa44b2b74a} certhash=864759f75a04d030e243ecf1cfe19ce336653374

ssl11ssl12

警告でるけど、気にせず表示。

5.証明書を信頼

このままでもいいんですが、やっぱり気持よく。

ssl13

信頼しちゃいましょう。自分のマシンで、自分で作った証明書。信頼しちゃえばいいじゃないか!

ssl14

Chrome厳しす。まぁ、いいか。警告でなくなったしね!

6.Accountコントローラを厳しく

URLRewriteで同じようになってもいいんですが、あえてコードで対応しましょう。と、言っても難しいことはなく、AccountControllerにRequireHttps属性を付けるだけ。

ssl15

これで、AccountControllerへのアクセスがHTTPSになってなければ、リダイレクトされます。リダイレクトされるということはHTTPでのPOSTは無視される(リダイレクトはGETデス)ので、気をつけましょう。

ssl16

この動きが気持ち悪いときはViewでURLを埋め込むときにHTTPSにしてあげる必要があるので、修正箇所が随分増えちゃいますが、通常の開発であればViewでもちゃんと書いて、ControllerへのRequireHttpsも付ける2段構えになるのが王道でしょう。抜け道なし。ViewのURLなんて嘘つくからね。

例えば、_LogOnPartial.cshtmlを以下のようにする。

@if(Request.IsAuthenticated) {
    <text>Welcome <strong>@User.Identity.Name</strong>!
    [ @Html.ActionLink("Log Off", "LogOff", "Account", "https", "takepara-local", "", null, null) ]</text>
}
else {
    @:[ @Html.ActionLink("Log On", "LogOn", "Account", "https", "takepara-local", "", null, null) ]
}

これで、必ずHTTPSのURLが生成されます。

画像てんこ盛りですが、手順通りやればうまくいくので、HTTPSでのテストをやりたい場合は是非どうぞ。

なんか、うまくできない!という場合はタスクトレイのIIS Expressを終了させてやり直してみるといいかも。applicationHost.config書き換えしたときなんかは特に。

2011年4月17日日曜日

MIX11をChannel9からまとめどり

更新:2011年4月19日 4:07

残念なお知らせと、うれしいお知らせがあります。

Mix 11 Videos - Download them all with RSS - Scott Hanselman

残念なのからいくと、このエントリは用なしになりました。嬉しいのは公式にMP4 Highが配信されました。

ダウンロードしなおすぜ!頑張れオレ!

MIX11 | Channel 9

いっぱいですね~。120個くらいですかね。MP4だけダウンロードしてiPadに入れて通勤時に全部目を通しておくのが、ラスベガスに行けなかったマニアのおつな楽しみ方というものでしょう。

http://channel9.msdn.com/Events/MIX/MIX11/RSS

とはいえ、Feedから取り出せたりするほど、便利なわけでもなく。やり方あるのかな?

でもそんなこと気にしない。ListタブをクリックすればすべてのMP4動画へのリンクがありますね。ということは、HTMLにはちゃんと動画のURLが埋まってるわけですね。となれば、スクレイピング!

なんてね。今回はちょっと趣向を変えてIE9のコンソールでJavaScriptを実行して取り出してしまいましょう!と、思ったけど、なんかちょっと思ったような出力が得られなかったので、Chromeで取り出してしまいましょう!てへ。

まずはChromeでページにアクセス。

http://channel9.msdn.com/Events/MIX/MIX11?sort=status&direction=asc#tab_sortBy_status

mix11-1

Shift+Ctrl+iでコンソールをひらきましょう。

mix11-2

MIX11のページにはjQueryが読み込まれてるので、やりたい放題ですよ。まずは変数の初期化。

var list=[];

と入れて改行。

続いて、listにエレメントから取り出したMP4一覧をタイトル込みで入れる。

$(".entry-meta > .downloads a[href*='.mp4']").each(function(i,v){list.push("wget -O \""+$(v).parents(".entry-meta").find(".title").text().replace(":","-").replace("?","?") +".mp4\" "+ $(v).attr("href"));});

↑これをコピペして改行すると、ぞろぞろ表示されます。

mix11-3

が、結果はlistに格納されてるので、続けて

list

と、入力して改行。

mix11-4

あとはコレをさらにコピペして、都合のいいように編集してコンソールからwget!curlでもいいっす。ちなみに複数の解像度のファイルがあるので、タイトルだけでファイル名にすると重複しちゃうので、その辺は目視で変更。さらにファイルとして使えない文字もあったりするので、ちょこっと修正。お好みに合わせて、どーぞー。ただいまダウンロード中の図。

mix11-6

※パスは都合のいいように書き換えてね!

ちなみにIEで実行したのが↓こちら。

mix11-5

ちょっとコピペしにくい。というかできない。

これで見放題ですな。ぐへへ、ぐへへへ~。たまりませんな~。あ、でも解像度低いとiPadでちょっと見づらい。文字潰れ気味。スライドはギリセーフだけど、コードが見れるかどうかが大事なので、ダウンロード後にちょっと確認しないとね。

2011年4月9日土曜日

Heroku for .NET

昨年末でしたね。SalesforceがHerokuを買収しました。

セールスフォース・ドットコム、Heroku社買収の最終合意に署名 - salesforce.com 日本

Heroku自体興味はあったけど、Rubyistじゃないからな~。AppEngineもな~。とかとか。あ、Herokuってあれっす。あれあれ。Rubyのホスティング。PaaS。いいよね~。

来週からMIX11が始まるっていうのに、まだmvcConfのビデオを見てるのんびり屋さんですが、面白いのありました。

mvcConf 2 - Troels Thomsen: Deploy ASP.NET MVC with No Effort | mvcConf | Channel 9

EC2をプラットフォームにした.NET向けのPaaS「AppHarbor」。スゴイんすよ。リポジトリにgit使うんですけど(初めてのgit読まねば)、commitすると向こうでビルドが走って、テスト実行して、デプロイまでやってくれるんすよ。とにかく無料で1アプリ公開できるから試してみた。あ、アプリは作ってないです。MVCのテンプレートそのままです。

AppHarbor - AppHarbor

手順はナニも難しいことはなくてですね、AppHarborにアカウント作って、アプリ作ると最初の画面に「これで出来るぜ」っていうコマンドが一覧で表示されるから、そのまま入力するだけです。

準備としてはGit使えるようにmsysGitいれとくだけです!

Help.GitHub - Set Up Git (Windows)

それも、ダウンロードして後はデフォルトのままクリッククリック。Git Bashってなんかカッコイイ。ハッカーな雰囲気を味わえる。そんな雰囲気を味わいたいからTortoiseGitを使わないオレ。まさに海賊王にふさわしい男気を感じる。ちっちぇ。

ah

git push(知ったかぶりだぜ!)するとAppHarborの管理画面に状況が表示されます。.gitignoreはつくっといたほうがいいです。

ah2

プロジェクトじゃなくてソリューションを追加して、標準のテストプロジェクトも入ってるんだけど、そのテストも実行されて、OKだったらデプロイ。試しにテスト失敗させるためにテストに以下のコードを追加。

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AppHarbor.Takepara.Tests
{
    [TestClass]
    public class SomeTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            Assert.AreEqual(0, 1);
        }
    }
}

ah4

身も蓋もない...。

んで、push(そのまえにadd,commitも忘れずに)!

ah5

ビルド中~。

ah6

テスト失敗!

ah7

詳細見てみると確かにテスト失敗。コレを直して、再度push。

ah8

ちゃんと成功すればデプロイまで行ってアプリケーションが動作します。

動いてるのも見たいよね。どうぞ、こちらです。

ah9

http://takepara.apphb.com/

dyno(Herokuのコンピューティングパワーの単位)の追加とかどうするのかよくわかってないけど、そもそもそういう概念がないのかも?

Pricing – AppHarbor

Load barancingもサービス範囲内。今回DB使ってないけど、せっかくなので後で無料の20MB枠を使ってAccountコントローラも動作するようにしてみようかな。どうしようかな。環境設定を書き換えてくれる特殊なconfig変換があるので、connectionStringなんかはデプロイ時によしなになるようです。

パッと見ですよ、アプリケーション領域としての容量制限とか特に書かれてないわけですよ。DBは無料20MBだとしても。

ということはですね、あれです、単純にサイトを公開したいだけなら無料ですよ!gitとかVS2010でのプロジェクト形式というハードルがあるとはいえ、無料ですよ。後はAppHarborさんたちが頑張ってくれるんですよ。このエントリは中身が薄いけど、AppHarborは凄まじく濃くて、魅力的なサービスです。

AppHarbor(Azure以外の.NET PaaS、AppEngine for .NETでもよろし)がどこまで行けるのか目が離せないですね!

調子にのってvimで.gitignoreやろうとして全くコマンドが分からなかったのは恥ずかしくて言えね。

2011年4月7日木曜日

VS2010のCSSエディタでCSS3スキーマ対応

ASP.NET Wiki: Adding CSS 3.0 support to Visual Studio 2010

  1. スキーマをダウンロードしてインストール
    CSS 3 Intellisense Schema
    ↑ここからCSS3Setup.msiをダウンロードしてインストールしましょう。
  2. レジストリをいじる
    レジストリエディタをひらいて以下に移動してキーと文字列値を追加。
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Packages\{4A0C6509-BF90-43DA-ABEE-0ABA3A8527F1}
    Schemasキーを作成。その中に文字列値の”File”を作って”css30.xml”と入れる。さらに文字列値として”Friendly Name”を作って”CSS 3.0”と入れる。
    場所が9.0なのは間違いじゃないので気にしないでね!
  3. ファイルをコピーする
    1でインストールしたファイルが
    C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\Packages\1033\schemas\CSS
    に作成されてるので、日本語版で使うためにコピーして1041のフォルダにも作っておきましょう。
    C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\Packages\1041\schemas\CSS

    file

こうしておけば、VS2010でCSS3のスキーマが有効になるよ!スゴイね!

css21

CSS 2.1を指定したままだと”box-shadow”はウニョウニョが出るよね。

css3

でも、CSS 3.0を選んでおくとValidですよ!

is

Intellisenceもほらー。transitionとか~。ね~。

ブチザッキさんへ

お元気ですか。ボクは元気です。

OData and Authentication – Part 7 – Forms Authentication - WCF Data Services Team Blog - Site Home - MSDN Blogs

WCF Data Servicesをプロキシクラス経由で呼び出すときにForm認証してやんよ、の件についてなんですけども。

WCF Data Services で フォーム認証を利用する « ブチザッキ

ナニをナニする系で、ナニしてみたのでご覧いただけたら幸いです。

Custom Membership Providers - Task Manager - CodeProject

プロキシからの呼び出しに介入し、別リクエストでAuthentication_JSON_AppService.axdを呼び出すことでForm認証し、レスポンスからCookieを横取りすることで、認証チケット再利用する部分を、FormsAuthenticationクラスを使って認証チケットを発行してそちらを使うことで実現してみました。

とはいえ、まったく楽チン度と便利さが上がらなかったので、ボツにしようかと思ったのですが、それはそれで少し寂しくなっちゃったので、公開レターの形式にしてみました。

WCF DataServicesをホストするサーバーサイドプロジェクトをASP.NET MVCにして、HomeControllerに以下のアクションメソッドを追加します。

[HttpPost]
public ContentResult FormAuth(string userName, string password)
{
  if (Membership.ValidateUser(userName, password))
  {
    var authTicket = new FormsAuthenticationTicket(
       1,
       userName,
       DateTime.Now,
       DateTime.Now.AddMinutes(30),
       false,
       userName);
    var cookie = 
       FormsAuthentication.FormsCookieName + "=" +
       FormsAuthentication.Encrypt(authTicket) + "; path=" +
       FormsAuthentication.FormsCookiePath + "; " +
       (!FormsAuthentication.RequireSSL
         ? "HttpOnly" : "");
    return Content(cookie);
  }

  return null;
}

続いて、クライアントサイドのプロキシクラス用に実装しているGetCookieの代わりとなる部分を以下のように。

string GetTicket(string userName, string password)
{
  string loginUri = string.Format("{0}/{1}/{2}",
      ServiceUri,
      "Home",
      "FormAuth");
  WebRequest request = HttpWebRequest.Create(loginUri);
  request.ContentType = "application/x-www-form-urlencoded";
  request.Method = "POST";

  string authBody = String.Format(
      "userName={0}&password={1}",
      userName,
      password);
  request.ContentLength = authBody.Length;

  using (StreamWriter w = new StreamWriter(request.GetRequestStream()))
  {
      w.Write(authBody);
      w.Close();

      WebResponse res = request.GetResponse();
      var body = new StreamReader(res.GetResponseStream()).ReadToEnd();
      if (!string.IsNullOrEmpty(body))
      {
          Cookie = body;
      }
      else
      {
          throw new Exception("Invalid username and password");
      }
  }

  return Cookie;
}

GetCookie(userName,password)の部分を上記のGetTicket(userName,password)にすることで、Cookie横取りではなくなりますね。

formauth

だから言ったじゃないですか!ボツにしたかったって...。

2011年4月2日土曜日

万kw

東京電力 電力使用量グラフの数値データAPI

素晴らしいですね。データさえあれば自分の好きなように加工して、使い放題ですもんね。ピーク値も含まれてれば使用量じゃなく使用率にできるからより見やすいでしょうか。

会社の監視モニターにも電力使用率を表示するようにして、日頃から意識しておこうと思う今日この頃です。

マッシュアップです!

tepco

東京電力 電力使用量グラフ

みんな大好きjqPlotも1.0が間近です。

jqPlot Charts and Graphs for jQuery

使おうじゃないですか。どんどんプロットしちゃおうじゃないですか。クライアントサイドで。IE9ならCanvasも動くし。IE8以前でもexcanvas.jsで問題なしですよ。

これといって特別なことをする必要もなく。JSONPで取得して、jqPlotでレンダリング。サーバーサイドのコードは一切なし。

あと、MVPもらっちゃった。

@onosさん、@naoki0311さん、@kazukさん、@shibukiさん、@ailightさん、@jsakamotoさん、@chack411さん。たくさんの方に感謝です。

2011年3月19日土曜日

EF4.1 CodeFirstでenumを使いたい

最近、いろんなお誘いメールがケータイに舞い込んできて、いやもうマジクリックしちゃうぞコノヤロー。そんなに誘惑するんじゃないよ!

なんか迷惑メールが地震以降強烈に増えましたね。

結局enumはサポートされないことが決定してしまったCodeFirst。しょうがないですね。RCからRTMまでの間で機能追加はないので、ここは潔く諦めましょう。

でも、やっぱりenumを使いたいですよね。プロパティをパースするときにenumかenumのジェネリックは完全にスルーされてデータベーステーブルがScaffoldingされます。なのでComplex Typeとしてenumをラップしたクラスを用意し「オレenumじゃないよ、全然関係ないから!」とEFを騙す必要があります。

Tip 23 – How to fake Enums in EF 4 - Meta-Me - Site Home - MSDN Blogs

古のテクニックですね。2009年です。今もこれしか方法がないみたいです。

コンソールアプリを作りながら同じようにやってみよー!VS2010SP1を前提にしますよ?いいですか?だってSQLCE4使ってみたいでしょ。なのでSQLCE4も入ってる前提で行きます。なきゃないで問題ないので、SQLCE4関連の部分は読み替えてください。

ソリューション作ったらまずは、NuGet!NuGetで必要なパッケージを入れてしまいましょう。以下の2つ。

install-package EFCodeFirst
install-package EFCodeFirst.SqlServerCompact

↑こうです。

efenum1 efenum2

WebActivatorは不要なので、参照設定から削除しましょう。WebActivatorについては先日書いたので、気になる人はチェックしてみてね。

無聊を託つ: WebActivatorでお手軽Bootstrapper

App_Start/SQLCEEntityFramework.csもEFCodeFirst 0.8ベースなので少しいじっちゃいます。といっても、DbDatabaseをDatabaseにするのとWebActivatorの属性を削除。あと、SqlCe用のnamespace(System.Data.Entity.Infrastructure)が変わってるので、そこも変更。

efenum3

これで準備完了。ワクワクするね!ワクワクの強要。

最初にモデルクラスをつくって、次にDbContext派生。行きます。

using System.Data.Entity;

namespace EFEnum
{
    public enum Role
    {
        Unknown,
        Sniper,
        Captain
    }

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Role Role { get; set; }
    }

    public class EFEnumContext : DbContext
    {
        public DbSet<Person> People { get; set; }
    }
}

↑EFEnumContext.cs

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="SampleContext" connectionString="Data Source=|DataDirectory|db.sdf;" providerName="System.Data.SqlServerCE.4.0"/>
  </connectionStrings>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SqlServerCe.4.0" />
      <add name="Microsoft SQL Server Compact Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
    </DbProviderFactories>
  </system.data>
</configuration>

↑App.config。

とりあえず、enumのまま作ってどういう動作になるのかを見てみます。

using System;
using System.Linq;
using EFEnum.App_Start;

namespace EFEnum
{
    class Program
    {
        static void Main(string[] args)
        {
            SQLCEEntityFramework.Start();
            var db = new EFEnumContext();

            var query = from p in db.People
                        select p;
            var count = query.Count();
            Console.WriteLine("{0}人いるよ!", count);

            if (count == 0)
            {
                var person1 = new Person
                                  {
                                      Name = "ルフィー",
                                      Role = Role.Captain
                                  };
                var person2 = new Person
                                  {
                                      Name = "ウソップ",
                                      Role = Role.Sniper
                                  };
                db.People.Add(person1);
                db.People.Add(person2);
                db.SaveChanges();
            }

            count = query.Count();
            Console.WriteLine("{0}人いるよ!", count);
            foreach (var person in query)
            {
                Console.WriteLine(person.Name + string.Format("({0})", person.Role));
            }


            Console.ReadLine();
        }
    }
}

↑main.cs。

    public static class SQLCEEntityFramework {
        public static void Start() {
            Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");

            // Sets the default database initialization code for working with Sql Server Compact databases
            // Uncomment this line and replace CONTEXT_NAME with the name of your DbContext if you are 
            // using your DbContext to create and manage your database
            Database.SetInitializer(new DropCreateCeDatabaseIfModelChanges<EFEnumContext>());
        }
    }

SQLCEEntityFrameworkのStartメソッドにDbContextのクラスを指定するのも忘れずに。

ちょっと、コード長いけどドンマイ。行くぜ!

efenum4

クリックして拡大してみてね。ちゃんと名前の横にロールが表示されました。これはAddするインスタンスに設定したものが、そのまま表示されただけです。次に一度終了して、もう一度実行してみます。

efenum5

そうすると、今度は"Unknown"と出ました。なんでかというと、なんとテーブルには保存されてないからです!そういう仕様なので驚かないでね。VSから確認してみます。

efenum6

efenum7

カラム無いですね。ちょっと切ないですね。

さぁ本題です!ちょこちょこっとクラス用意しましょう。

    public abstract class EnumWrapper<T>
    {
        private T _value;
        public string Value
        {
            get
            {
                return _value.ToString();
            }
            set
            {
                _value = (T)Enum.Parse(typeof(T), value, true);
            }
        }

        public static implicit operator string (EnumWrapper<T> value)
        {
            return value.Value;
        }
    }

これを基底クラスとしてenumの型毎にクラスを用意していきます。

    public class RoleWrapper : EnumWrapper<Role>
    {
        public static implicit operator RoleWrapper(Role value)
        {
            return new RoleWrapper { Value = value.ToString() };
        }
    }

enum型1個しかないので、1個つくりましょう。ホントはimplicit operatorも書きたくないんだけど、こればっかりはしょうがない。何かいい方法ありますかね~?最終的にはT4で自動生成(プロジェクト内のenumを全部列挙してしまえばいいかな)させちゃえばいいでしょう。

そして、モデルクラスをこのクラスを使うように変更します。

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public RoleWrapper Role { get; set; }
    }

このままだと、mainのConsole.WriteLineでクラス名がでちゃうのでそこはRole.Valueに変えておきましょう。そうして実行してみると...。

efenum9 efenum8

タターン!左が1回目。0件で追加して2件。右が2回目。2件あるから2件表示。ちゃんとロールも表示されますね。テーブル見てみましょう。

efenum10

カラムも追加されてるし、値も入ってる~。文字列にしてるのはパースしやすいのと、パッと見データ見ただけでわかるからっていうだけの理由です。intがよければそのように。

いいんじゃないでしょうか。こんな感じで。

packages含んじゃってるのでちょっと大きいですがご容赦を。

dotnetConf2015 Japan

https://github.com/takepara/MvcVpl ↑こちらにいろいろ置いときました。 参加してくださった方々の温かい対応に感謝感謝です。