2010年8月29日日曜日

MVC Graffiti

宇宙兄弟面白いよね!ムッタと日々人の宇宙飛行士を目指す兄弟の話。

...。

去年に引き続き、今年もTechEdでBoFに呼ばれたので行ってきました。TechEd自体は3日間の開催期間があるんですけど、月曜から水曜まで会社で合宿状態で、初日は参加できず。2日目は現地入りしたものの、ずっと障害調査でパシフィコにWiFi借りに行っただけみたいな状態。ちなみにこの時点でBoFで使う予定のデモプログラムはまだちゃんと動かず。貝になりたい。

IMG_0057

去年はオープンしてなかったけど、今年はオープンしてるクリスピー・クリームにココロオドル。

今回のデモのテーマは「人類の進歩と調和」です。ウソですね。はいはい。テーマなんて特に無いです。

bof1

↑これです。多人数で同じキャンバスに絵を書くアプリで、一般的にはなんていうのかな、お絵描きチャットとでも言うんでしょうか。そんなようなものです。

仕組みとしてはいたってシンプルです。

WebアプリケーションとしてASP.NET MVC 3 Preview1をベースにプロジェクトを作成。もちろんViewはRazorです。ここは実質ほとんど処理もなく、Webアプリケーションの構造を制御するためと、サーバーサイドでサムネイル画像を生成するくらいのシンプルな構造です。

データ通信はWCF Data Servicesを使ってます。XMLではなくクライアントサイドのJavaScriptからJSONでデータの取得・更新・削除をRESTで行うのに、これより簡単に実装する方法はないんじゃないでしょうか。実質EFでモデルを定義し、標準テンプレートにジェネリッククラスに指定するくらいしか作業はない感じですよね。今回は少しWebGetで機能を追加してます。

残るはクライアントサイドのJavaScriptで、HTML5のCanvas要素にレンダリングしたりXHRを使った通信部の実装です。

「マイクロソフトのWeb テクノロジ最前線と現実解を語ろう」

が、BoFのタイトルでしたよね。覚えてますか?

ということで、今回のデモで使ったテクノロジは以下の通り。

  • ASP.NET MVC 3 Preview1(Razor)
  • WCF Data Services
  • HTML5(Canvas)
  • CSS3
  • jQuery

MVCの中ではViewに対してデータをほとんど送ってないです。これはつまりMVCでデータ制御をほとんど行ってないからです。データに関してはすべてWCF Data Servicesで通信してるので、クライアント部をSilverlightにしたとしても、そのままデータは利用できるので、もっとリッチなキャンバスを作るのも面白いかもです。

四の五の言わずにソースコードですよね。今回はCodePlexにTFSでアップしようと思って、いろいろ試したんですが...。ちょっとまだうまくできてないです。なのでとりあえずSkyDriveに置いておきます。

↑ここからダウンロードして展開してソース見てみてね!データベースのセットアップはスクリプトになってるので、MvcGraffitiという名前でデータベースを作成後(なんでもいいです)Databases\MvcGraffiti.sqlを実行してテーブルを作成してください。BoFで使ったデータもそれぞれスクリプトにしています。作ったデータベースに合わせてWeb.configのconnectionStringを適当にいじってください。これで動くはず。うまくいかない場合は連絡いただければできる範囲で対応していきます。このエントリのコメント欄によろしくです。

今回のサンプルはwebkit系ブラウザにのみ最適化して作ってます。なので、HTML5対応してるとうたっている他の実装ではちゃんと動きません。そこは手抜きです。座標の計算とかの部分がちょっとへんちくりん。PCでのお勧めはSafariではなくChrome。なぜかというとSafariではWheelイベントが発生しなくてマウスのコロコロをつかったズームが効かない(Zoom自体はCSSのZoomを使ってます)から。後はMobile SafariとしてiPhone4とiPadで動作確認してます。こちらはWheelイベントではなくTouch系イベントをハンドリングしてます。なのでJSのソースを見てもらうとわかるんですが、PC向けのMouseInputクラスとMobile向けのTouchPanelInputという2種類のクラスを用意して入力デバイスごとの処理を記述してます。といっても、イベントのハンドリングと座標の取得方法が違うくらいですが(そこが一番面倒...)。

Mobile Safariを使った場合、指2本でズームしたり、キャンバスをドラッグできるようになってるので試してみてください。この辺、意外と知られてないと思いますが、イベントハンドリングして自分ですべて実装する必要があります。ただのHTMLに対してはそんなことしなくてもいいんですけど、今回Canvasお絵かきなので楽はできないってことですね。

MVC3になって目に見えて違うのはViewDataのほかにViewModelプロパティ経由でViewにデータを渡せるようになってるところでしょうか。ModelBinderは今回のデモで使ってないので。ViewModelはdynamic型なので、データだけじゃなくFunc<T>渡せます。今回のデモの中でIPアドレスをViewで表示してるところが無駄にこの機能を使って、IPアドレスそのものではなく、IPアドレスを返すデリゲート経由でViewは表示してます。GraffitisControllerの36行目と、List.cshtmlの31行目。可能性は無限ですね。使い過ぎ注意ですけど(特にデータをどうのこうのするようなものには適用しないほうがいいかも)。

VS2010だとしても、まだRazorに対する対応はできていないので、コードハイライトもインテリセンスも効きません。なにも設定しないと、VS2010がただのメモ帳です。なので、コードハイライトだけでも行いたい場合は拡張しcshtmlにたいしてHTMLエディタを関連付けしてみましょう。

bof2 bof3

そうすると↑こんな感じにはなります。あとは拡張機能マネージャで"Razor Syntax Highlighter"を入れるとかでしょうか。黒バックの場合切ない表示になるので個人的には使ってないですが、白バックならきれいに表示されます。

あと、Razorの構文については英語の資料になりますが、以下のサイトからダウンロードできます。

Download details: ASP.NET Web Pages with Razor Syntax

200ページ越えです。読み応え抜群です。後はASP.NET MVC3 Preview1のソースをダウンロードするとRazorのパーサーとWebPage関連一式のソースも含まれてる(MVC専用ソースなのがPreview1のかわいげのあるところです)ので、パーサーマニアにはたまらないでしょう。

Tipsとしては困ったときの<text/>。これで囲むといったんパースのコンテキストがCodeParserからMarkupParserに切り替わるので、c#の終わりがわからない!と怒られた時には試してみましょう。

残るはWCF Data Services。かつてADO.NET Data Servicesと言われていたものです。大きなところではAtomPub/JSONだったのがAtomPub改であるODataに対応してるところですね。それがどううれしいかというと会場でも話しましたが、PowerPivotによるセルフサービスBIの促進による無駄エネルギー使用(レポート作成のエネルギーは顧客が使えばよろし)の撤廃です。この辺話し出すと長くなるのではしょります。デジタルナーバスシステムです。ね!ゲイツさん!

Microsoft Developer Network Weekly News Japan = Vol.46 = 2/ 25/98

ふるっ!

WCFのレイヤで介入するIDispatchMessageInspectorを使うと、Data Servicesの処理に到達する直前に介入できるのと、Data Servicesのレイヤで介入するQueryInterceptorやChangeInterceptorなんかを利用するとスマートにデータアクセスに対して機能拡張が施せます。

今回はInspectorの使用例としてJSONP and URL-controlled format support for ADO.NET Data Services - Homeで公開されているQueryStringによるformat指定Behaviorを使ってみました。プログラム中ではまったく有効利用しません。これってどういうことかというと、WCF Data Services自体が無効なQueryStringは例外を出してくるので、好き勝手なQueryStringを使うことはできない仕様なんですが、InspectorでData Servicesの処理が始まる前に$format指定をリクエストヘッダ"accept"に移動させるということをしています。そうするとData Servicesからすれば普通にContentTypeを指定してリクエストされたものだと判断して処理を進めてくれるので、自分でそれ以上の処理をする必要がなくなるわけですね。今のところAtom/JSONしかないですけど、多分うまいこと介入すればCSVをレンダリングとかもできるんじゃないかとにらんでます。どこかのProviderなんじゃないかな~。適当ですいません。

デモアプリケーションのJavaScriptの構造としては以下のような感じです。

bof5

network部はtimerでsend/receiveをくるくる回してます。あんまりリアルタイムにはしてないです。

サーバーサイドの構造としては以下のような感じです。

bof6

雑にもほどがある...。

あれですよ、デベロッパーたるもの「コードで語ってくれ」ですよ。九十九も「拳で語ってくれ」って言ってたじゃないですか。

BoF後に「WebSocketとかもいいですよね!」と声を掛けてくれた方がいましたが、まさにその通りで、今回のようなアプリケーションの場合XHRで処理するよりWebSocketを使ったほうがより効率のいい通信ができるでしょう。あと、コードの9割がJavaScriptだし、iPad持ち出してデモしたりとMSのテクノロジがあまり使われてないじゃないか!なんて野暮なことは言いっこなしです。ちゃんと使ってるんですよ。ただコード量が少なくて済むというだけです。なので全体比率としては少ないです。localStorageやsessionStorageなんかを使うと完全にスタンドアロンで動かすこともできるようになるので面白そうですね。

それと、今回EFを使ってWCF Data Servicesでアクセスするようにしてますが、リードオンリーなデータ公開ならLINQ to SQLをベースにするかPOCOテンプレートを使うほうが使い勝手はいいと思ってます。更新系はMVCでバリデーションかけてそっちから更新する感じです(更新はUIにフィードバックしやすいほうが開発が楽だと思いますがどうでしょう?)。バランスですね。

Ask the Speakerのほうでも話題になりましたが、SQLをジェネレート任せにせず、自分でチューニングしたものをEFなりLINQ to SQLで使いたい場合はどうするのさ、についてはストアドで書いて、DataContext/ObjectContextにはやす感じですね。カスタムなエンティティとして返すもよし、他のテーブルと同じ定義を戻すもよし、です。その後動的に条件を付ける場合はIQueryableにどんどこ追加してしまいましょう。最初のクエリが効率よく絞れて抽出できてればあとは、オンメモリのLINQ to Objectで!

だいたい、こんな感じです。わからない・わかりにくい点など多々あると思うので、そんな時には質問いただければと思います。

いきなり話は変わりますが、WebMatrixなどの他のツールについての話が出てましたが、最近読んだ本で「Amazon.co.jp: 続・ハイパフォーマンスWebサイト ―ウェブ高速化のベストプラクティス: Steve Souders, 武舎 広幸, 福地 太郎, 武舎 るみ: 本」っていうのがあるんですが、ちょっといい感じの記述があるので紹介。付録Bの"Yahoo!JAPANが実践するWebの高速化"のB.5.1役割分担。

大きく分けて3つの開発フェーズがあり

  • コンテンツの情報を設計する「インタラクションデザイン」フェーズ
  • コンテンツの見た目をデザインする「ビジュアルデザイン」フェーズ
  • コンテンツのHTML/CSSを実装する「ウェブデベロップメント」フェーズ

となります。

ここでWD(ウェブデベロッパー)とFEE(フロントエンドエンジニア)という役割がありまして、それぞれ担当するフェーズも違うわけですね。ここが難しいところなんですが、「デザインはデザイナー」というのが口癖のこの業界、デザインとは何を指しているのでしょう。見た目だけ?動きのある場合は動きもデザインするのがデザイナー?そもそもの情報設計はデザイナー関与せず?HTML/CSSの実装は?その辺のふわっとした垣根のおかげで、お互いが密に連携できず、特に会社が別々だったりすると誰がどこまで担当してるのかで、ごにょごにょな状態になりやすかったりね。WebMatrixはフェーズの違う部分の発生するロスを減らしていくためにフェーズをまたいだツールとしての意味もあったりするんじゃないかと勝手に妄想してます。

最後に、BoFに参加してくれたたくさんの開発者の方々、いきなりキャンバス作りまくったり削除しまくったりと、想定外の操作で会場を盛り上げてくれてありがとうございました!質疑応答があまりできなくてすいませんでした。

小野さん、ナオキさん、今回も声掛けてくれてありがとうございます。Techdaysのトラウマがはれそうです。

それとチャック。急きょWiFiルーターを貸してくれてほんとにありがとう。あれがなかったら会場に来てくれた方々は見るだけで、実際にキャンバスに描くこともできず、企画倒れになるところでした。

なんのその、男は裸百貫の波に立つ獅子であれ

2010年8月19日木曜日

MVC HTML5 Toolkit を MVC3 Preview1のRazorで使う

ASP.net MVC HTML5 Helpers Toolkit

ナオキさんに勧められたので調べてみました。

ナオキにASP.NET(仮) : MVC HTML5 Toolkit が CodePlex にて公開!

mvchtml5

まずは、上記サイトからソースとAssemblyをダウンロードしてみました。名前空間がSystem.Web.Mvc.Html5というところがムフフなポイントですね。

ソースがあまりにもシンプルで短いので見てみるのをお勧めします。で、見てみてサンプルを動かしてみました。

面白いね~。3大派手機能のうちVideo/Audioのタグには対応。あとはCanvasも同じように作ればいいね!

ベースとなるHtml5TextBoxとHtml5RangeNumberはModelStateを取り出してないから、値がバインドしてこないです。ちょっと残念。InputHelperを使ってるHtml5TextBoxForHelperはViewData.ModelState.TryGetValueでちゃんとバインドしてるから、いろいろ試しながら作ってるのか、意図的にバインドしないようにしてる感じです。input type="range"もバインドしてくれていいと思うけどな~。せっかくhtmlHelper.GetModelStateValueも用意してるんだし。

サンプルのMVCプロジェクトではSite.MasterでDOCTYPE htmlの出力じゃないけど、そんなの気にせずSafari,Chromeはちゃんとレンダリングしてくれるのがすばらしい。

Dean Hume - ASP.net MVC HTML5 Toolkit

ちなみにMVC2用だとうたってますが、そんなの気にせずMVC 3 Preview1で使ってみましょう。作者のサイトに書かれてる通り、参照設定に追加して、Global.asaxにおまじない。

mvchtml5_2

もちろんRazorなので、NamespaceをWebPageからHtmlHelperへの拡張メソッドが参照できるように、Global.asaxに以下のように追記。

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();
  CodeGeneratorSettings.AddGlobalImport("System.Web.Mvc.Html5");

  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);
}

ページに直接usingとして書いてもいいですが、すべてのページに書くのは馬鹿らしいじゃないですか。Tumblrにも書いたと思いますが、Preview1ではWeb.configのsystem.web/pages/namespacesはみてくれないです。そこはRTMまでには解決されることでしょう。

前回作ったサルベージ用アプリに追加してみる。

@using(Html.BeginForm("Index","Home",FormMethod.Get)) {

  @Html.TextBox("q")

  <input type="submit" value="検索" />

  @Html.Html5Range(1, 100 , 0, 23, null).AsHtml()
}

AsHtml拡張メソッドもがある前提です。

対応してるChromeだと↓こんな感じでちゃんとRangeコントローラが出てきます。

mvchtml5_3

Firefoxだとまだ対応してないので、type属性無視してTextboxがそのまま表示されます。

mvchtml5_4

お手軽でとてもいいじゃないですか!これは楽しい!

あと、個人的に面白いと思うのが↓。

aspnet - Release: Sprite and Image Optimization Framework

地味な感じがするけど、ASP.NETらしさ爆発じゃないですかね~。

2010年8月8日日曜日

サルベージ開始

最近だとMVC3 Preview1がとても興味深く、楽しそうなプロダクトで、あれやこれやいじり倒してみたいところですが、なんか忙しかったり、テンション上がらなかったりとエントリ書く気も起きないな~、なんてぼやく日曜日。

で、思い出したんですよ。昔々「オレがルールだ」なんていうブログを書いていたのを。その時のデータベースファイル(MDF)を発掘したので、そこからエントリをサルベージするってどうなんよ、と。データベースファイルをローカルのSQL Expressにアタッチしたのはいいけど、そのままテキストだけだと画像なんかも見れないし、なんか良く解らん状態。なので、せっかくなのでMVC3P1 Razorで参照専用のWebアプリを書いてみたわけです。書くと言っても参照専用なのでほとんどすることはないんですけどね。EF4CTP1でも使おうかと思ったけど、インストールしてないし、するのも面倒だから、既にインストール済みのPOCOテンプレートを使ってみました。これまた特にすることもなく、EDMX作ってカスタムツール外して、ファイル名をttに埋め込むだけなんですけどね。

なんかかんや、録画しておいてハゲタカTHE MOVIE見ながらチクチクと。

salvage1 salvage2

書いてて気がついたんですが、RazorってデフォルトHTMLEncodeかけるんですね。HTMLをそのまま出力したいときにはどうしましょう...。

とりあえず、以下のようなヘルパーを用意しておくことで簡単に出力できるとStackoverflow.com先生が教えてくれました。

namespace System.Web.Mvc
{
  public static class HtmlHelperExtensions
  {
    public static IHtmlString Literal(this HtmlHelper htmlHelper, string html)
    {
      return MvcHtmlString.Create(html);
    }

    public static IHtmlString AsHtml(this string html)
    {
      return MvcHtmlString.Create(html);
    }
  }
}

Emitting unencoded strings in a Razor view - Stack Overflow

↑これがあれば↓こうですね!

  <div class="entry_view">
    @Html.Literal(Model.contents)
  </div>
  <div class="entry_view">
    @Model.contents.AsHtml()
  </div>

グダグダ前置き長くてすいません。というわけで第1弾「もっと速く出来ると思う」です。


もっと早く出来ると思う

2009/01/16 18:52:29

ここ最近Philさんのブログでホットな話題になってる名前書式化スピード競争。

Fun With Named Formats, String Parsing, and Edge Cases

↑このエントリで始まって、Philさんが「オレのんが速え~」って事でコードを載せてたけど、ここにきて新たな刺客登場。

Named Formats Redux

正規表現だから遅いんじゃないぜ!といわんばかりにJamesFormatterを作成したJamesさん。
おいおいフィル、同じ会社の社員としてそれじゃいかんぜ、とHenriFomatterを作成したHenri Weichersさん。
いずれもPhilさんの速度を超えた。

ルールは簡単。
通常のstring.Format(”書式化文字列”,value)の書式化では引数のインデックスを埋め込むけど、匿名クラスのパラメータ名を渡して書式化出来るようにすること。んで、テストがちゃんと通ること。

例えば
var values = new { foo = 123, bar = 456.7, boo = "Hello!" };
string text = "{foo} {bar} {boo}.";
なら
text = "123 456.7 Hello!."
になる。

これは面白そう。
流石に、速いコードを書く事に慣れてないから苦戦すると思ったけど...。

str

誰よりも速えーじゃねーか。
※順番で不公平(なわけないけど)だ!なんて言われてもイヤなんで、最初と最後の2箇所で。
そんなに難しいコードじゃなくて、テストを通すことと、書式化文字列がもっと長い場合とかは考慮せず、シンプルに書いてみた。

str2

ほら。テストも全通過。
テストツールはNUnit入れてないからVisual Studioに入ってるやつで出来るようにコピペして作り直してるけど、やってるテスト内容は同じ。

コードは↓これ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;

namespace StringLib
{
  public static class TakeharaNamedFormatter
  {
    public static IEnumerable<KeyValuePair<string, object>> AnonProperties(string prefix, PropertyInfo pi, object inst)
    {
      var piVal = pi.GetValue(inst, null);
      if (!pi.PropertyType.IsValueType && pi.PropertyType.IsNotPublic)
      {
        foreach (var subpi in piVal.GetType().GetProperties())
          foreach (var kv in AnonProperties(pi.Name + ".", subpi, piVal))
            yield return kv;
      }
      else
        yield return new KeyValuePair<string, object>(prefix + pi.Name, piVal);
    }

    public static string TakeharaFormat(this string format, object values)
    {
      if (format == null)
        throw new ArgumentNullException("format");

      List<string> formats = new List<string>();
      Dictionary<string, object> vals = new Dictionary<string, object>();
      foreach (var pi in values.GetType().GetProperties())
        foreach(var kv in AnonProperties("", pi, values))
          vals.Add(kv.Key, kv.Value);

      var len = format.Length;
      string result = format;

      char atchar;
      int sbrace = 0, ebrace = 0;
      string replace_format = "";

      for (int i = 0; i < len; i++)
      {
        atchar = format[i];

        // start
        if (atchar == '{')
        {
          if (replace_format != "")
            sbrace = 0;

          sbrace++;
          ebrace = 0;
          replace_format = "";
        }
        else
        {
          // end?
          if (atchar == '}')
          {
            ebrace++;
            if (sbrace % 2 != 0 && replace_format != "")
            {
              //sbrace = 0;
              formats.Add(replace_format);
            }
          }
          // format string?
          else
            if (sbrace > 0 && ebrace == 0)
              replace_format += atchar;
            else if (((sbrace + ebrace) % 2 != 0) && ebrace != sbrace)
              throw new FormatException();
        }
      }

      if ((sbrace + ebrace) % 2 != 0)
        throw new FormatException();

      // replace!
      var value_format = "";
      foreach (var f in formats)
      {
        var fa = f.Split(':');

        if (!vals.ContainsKey(fa[0]))
          throw new FormatException();

        if(fa.Length == 2)
          value_format = "{0:" + fa[1] + "}";
        else
          value_format = "{0}";

        result = result.Replace(string.Format("{{{0}}}", f), 
                                string.Format(value_format, vals[fa[0]]));
      }

      result = result.Replace("{{", "{")
                     .Replace("}}", "}");

      return result;
    }
  }
}

 

リフレクション使ってるし、ジェネリックも使ってるけど、遅くないよね...。
もっと速いコードが出てきたら、また考える。


いや~、コピペって楽ですね!

ストックが1813件あるということは、しばらく...。そんなわけないですね。ほとんどがどうでもいい話だったりするので、これなら今公開しても、まぁいいかなというのを、ちょいちょい探してサルベージしていこうかと思う次第です。

2010年7月25日日曜日

Data ServicesのPOCO利用

ADO.NET Dataservice/WCF Data ServicesってそれぞれEntity Frameworkを使う場合とPOCOを使う場合と、それぞれを混在させたい場合とあるような気がするけどどうなんでしょう。Read Onlyなら普通に混在できていいんじゃないかと思うけど簡単に出来たりしないのかな~。

データ モデル (ADO.NET Data Services フレームワーク)

試しにNorthwindのProducts/Categories/Order/Order Detailsでやってみた。

まずはVS2008を使ってADO.NET Entity Data Modelを以下のように作成。

astoria1

続いてADO.NET Data Serviceを作成。

 

public class DataEF : DataService<NorthwindEntities>
{
  public static void InitializeService(IDataServiceConfiguration config)
  {
    config.UseVerboseErrors = true; // 追加

    config.SetEntitySetAccessRule("*", 
EntitySetRights.AllRead); config.SetServiceOperationAccessRule("*",
ServiceOperationRights.AllRead); } }

astoria2

普通ですね。

これに以下のようなPOCOクラスも混在させたい。

public class DailyOrderSummary
{
  public int Year { get; set; }
  public int Month { get; set; }
  public int Day { get; set; }
  public decimal Amount { get; set; }
}

このモデルを返すのは以下のようなクエリです。

from o in Context.Orders
where o.OrderDate.HasValue
group o by new 
{ 
  o.OrderDate.Value.Year, 
  o.OrderDate.Value.Month, 
  o.OrderDate.Value.Day 
} into daily
select new DailyOrderSummary
{
  Year = daily.Key.Year,
  Month = daily.Key.Month,
  Day = daily.Key.Day,
  Amount = daily.Sum(d => d.Order_Details
                           .Sum(od => (od.UnitPrice * 
                                       od.Quantity)))
};

がしかし、これを利用するためにDataEfクラスにIQueryableなメソッドを追加してもどうにもこうにも機能してくれないんですよ。Object Service層にはそんなのいないからってことでしょうね。んじゃどうしましょうか。

An ADO.NET Data Services Primer - O'Reilly Answers

ここにあるように一旦POCOだけで動かせる状態にして、そのクラスをプロキシとしてDataService<T>を作ってみます。

public class EfDataModels
{
  public NorthwindEntities Context { get; private set; }

  public EfDataModels()
  {
    Context = new NorthwindEntities();
  }

  public IQueryable<Categories> Categories
  {
    get { return Context.Categories.AsQueryable(); }
  }

  public IQueryable<Products> Products
  {
    get { return Context.Products.AsQueryable(); }
  }

  public IQueryable<Orders> Orders
  {
    get { return Context.Orders.AsQueryable(); }
  }

  public IQueryable<Order_Details> OrderDetails
  {
    get { return Context.Order_Details.AsQueryable(); }
  }

  public IQueryable<DailyOrderSummary> DailyOrderSummaries
  {
    get
    {
      return 
         from o in Context.Orders
         where o.OrderDate.HasValue
         group o by new
                      {
                        o.OrderDate.Value.Year, 
                o.OrderDate.Value.Month, 
                o.OrderDate.Value.Day
                      }
           into daily
           select new DailyOrderSummary
           {
             Year = daily.Key.Year,
             Month = daily.Key.Month,
             Day = daily.Key.Day,
             Amount = daily.Sum(d => d.Order_Details
                        .Sum(od => (od.UnitPrice * 
                          od.Quantity)))
           };
    }
  }
}

これを使ってDataEfを定義することにしてみたけど、これはこれでやっぱりエラー。

astoria3

でもね、上記EfDataModelクラスからEntity Modelで定義したものを除外してDailyOrderSummariesだけにするとうまく行くわけですよ。

astoria4 astoria5

となると、違いはDataService<T>に指定するクラスじゃなくて、モデルの基底クラスなのかなとなりますね。なりませんかね。ソースかドキュメントでもあれば自信を持って基底クラスがEntityObjectのものがいるとPOCO混在出来ないと言えるところなんですが、そのへんよくわからないです。教えてエライ人!

で、それならそれでLINQ to SQLのDataContextを使うという方法もありますね。こっちは基底クラスがいない(objectな)わけで。最初に戻ってLINQ to SQLのDataContextの作成からやり直してみます。

astoria6

EFと一緒です。あ、そうそうちなみにPOCOの場合、キー項目がわからないので、クラスにDataServiceKey属性を付けるのを忘れずに。

ADO.NET Data Services and LINQ-to-SQL - Sergey Zwezdin

なので、先のDailyOrderSummaryクラスは以下のように属性を付けておく必要あり。

[DataServiceKey("Year", "Month", "Day")]
public class DailyOrderSummary
{
  public int Year { get; set; }
  public int Month { get; set; }
  public int Day { get; set; }
  public decimal Amount { get; set; }
}

DataContextに追加したモデルたちもpartialなので同じように定義します。

[DataServiceKey("OrderID")]
partial class Order
{
}

[DataServiceKey("OrderID","ProductID")]
partial class Order_Detail
{
}

[DataServiceKey("ProductID")]
partial class Product
{
}

[DataServiceKey("CategoryID")]
partial class Category
{
}

これをプロキシとなるDataModelsにまるっといれこんで以下のようなクラスができました。

public class DataModels
{
  public DataL2SDataContext Context { get; private set; }

  public DataModels()
  {
    Context = new DataL2SDataContext();
  }

  public IQueryable<Category> Categories
  {
    get { return Context.Categories; }
  }

  public IQueryable<Product> Products
  {
    get { return Context.Products; }
  }

  public IQueryable<Order> Orders
  {
    get { return Context.Orders; }
  }

  public IQueryable<Order_Detail> OrderDetails
  {
    get { return Context.Order_Details; }
  }

  public IQueryable<DailyOrderSummary> DailyOrderSummaries
  {
    get
    {
      return 省略
    }
  }
}

なんか無駄にコードの量が増えてきて面倒になってきた...。

astoria7 astoria8 astoria9

ナイス!

ここまで来たら、VS2010でADO.NET POCO Entity Generatorを使って同じようにやりたくなるってもんですよね。

ADO.NET C# POCO Entity Generator

このジェネレータはT4になってて、EDMXファイルを指定すると、ObjectContex派生のコンテキストクラスと、エンティティPOCOクラスを生成してくれます。

もちろんPOCOエンティティなのでDataServiceKey属性定義は必要だね。やってみよう!

astoria10 astoria11

おぉ~、いいじゃ~ん。と思ったのもつかの間。

astoria12

ジェネレートしたエンティティを参照すると↑こんなの出てきてちゃんと動かない。なんでじゃ~。出力されたエラーを見てみると...。

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xml:base="http://localhost:29890/Data.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
  <title type="text">Categories</title>
  <id>http://localhost:29890/Data.svc/Categories</id>
  <updated>2010-07-25T06:29:58Z</updated>
  <link rel="self" title="Categories" href="Categories" />
  <m:error>
    <m:code></m:code>
    <m:message xml:lang="ja-JP">内部サーバー エラーです。型 'System.Data.Entity.DynamicProxies.Categories_549968D49334B0D9E524C2FC37C19156947873B86A46A78227AFE184CB25AA68' は複合型またはエンティティ型ではありません。</m:message>
  </m:error>

なんじゃこれ。いろいろ調べてみたらDynamicProxiesを使わないようにすればいいという記述を発見。

ADO.NET C# POCO Entity Generator and Data Services
ObjectContextOptions プロパティ (System.Data.Objects)

というわけで、POCO GeneratorのTTファイルをいじることにしてみます。

VSに生成されてる~.Context.ttファイルを開いて、Constructorsリージョンで隠れてる部分を開いて以下のように変更。

#region Constructors

public <#=code.Escape(container)#>()
  : base(ConnectionString, ContainerName)
{
  ContextOptions.ProxyCreationEnabled = false; 
<#
WriteLazyLoadingEnabled(container);
#>
}

public <#=code.Escape(container)#>(string connectionString)
: base(connectionString, ContainerName)
{
  ContextOptions.ProxyCreationEnabled = false; 
<#
WriteLazyLoadingEnabled(container);
#>
}

public <#=code.Escape(container)#>(EntityConnection connection)
: base(connection, ContainerName)
{
  ContextOptions.ProxyCreationEnabled = false; 
<#
WriteLazyLoadingEnabled(container);
#>
}

#endregion

この状態で動かしてみます。

astoria13

今度は通常のエンティティクラスの参照もうまくいきました。ちなみにttファイルをいじってしまえば、DataServiceKey属性もハナからくっついた状態でエンティティクラスを生成させることも出来ますね。

using System.Data.Services.Common;
<#
    BeginNamespace(namespaceName, code);
    bool entityHasNullableFKs = 
       entity.NavigationProperties
             .Any(np => np.GetDependentProperties()
                          .Any(p=>ef.IsNullable(p)));
#>
[DataServiceKey(<#= string.Join(",",entity.KeyMembers.Select(km=>"\""+km.Name+"\""))#>)]
<#=Accessibility.ForType(entity)#> 
 <#=code.SpaceAfter(code.AbstractOption(entity))#>
 partial class <#=code.Escape(entity)#>
 <#=code.StringBefore(" : ", code.Escape(entity.BaseType))#>
{
<#
    region.Begin("Primitive Properties");

    foreach (EdmProperty edmProperty in 
                           entity.Properties
                                 .Where(p => p.TypeUsage.EdmType is PrimitiveType && 
                                             p.DeclaringType == entity))

太字の1行。

ようはPOCOのみで構成してしまえば、Read OnlyのData Servicesは比較的簡単に公開できるね、っていう話。

なんだけど、本質的にはData ServiceのレイヤでごちゃごちゃせずにDBにViewを定義して、そのViewをそのまま公開するのが本筋だと思うわけです。Viewが集計に必要なRaw状態になってれば、あとはPivotTableなり使ってExcelでよしなにやってしまえよ、というのがセルフBIのスタートラインなんじゃないのかね。賢くないことをしようとすると無駄なことをたくさんしないといけなくなるから、そこんとこちゃんとやりたまえよ。

2010年7月18日日曜日

Programming Microsoft ASP.NET MVC

Amazon.co.jp: Programming Microsoft ASP.NET MVC: Dino Esposito: 洋書

先月6/3にこの本が届いてから、毎日通勤電車で少しずつだけど読み進め、やっと終わることが出来たっす。時間かかり過ぎ。

ASP.NET MVC 2に関する書籍は発売延期を繰り返しまだ全然出てきてないですが、7/21にはAmazon.co.jp: Pro ASP.NET MVC 2 Framework, Second Edition: Steven Sanderson: 洋書も届くので、その前に読み終えることができてちょっとホッとしてます。

3部構成になっていて、1部はMVCとはなんぞや、2部は内部構造、3部は機能紹介と結構豊富でAppendix含め全549ページ。読み応え抜群です。

1部のMVC、MVP、MVVPのアーキテクチャデザインの解説はかなり勉強になります。Original MVCとModel 2のMVCの違い、ASP.NET MVCはModel2なのでレイヤ的にModelはView Modelなんだよ、だったり(ヘルプ - IBM WebSphere Help Systemを見てもわかるとおりMVC Model 2でのModelとビジネスロジック+DALは別物)。Chapter 3は何度読んでも面白いです。それ以外のASP.NETとIISの関係なんかは知ってる人には面白味にかける部分かも。2部以降少し新鮮味に欠ける部分も多くなりますが、明確に書かれているのを見たことがないMVC1とMVC2の機能の違い(全然知らなかったのがTempDataの変化)がところどころ差し込まれてるので、これまた面白いです。3部のChapter 11はカスタマイズポイントが事細かに書かれているので、個人的には一番盛り上がりました。ValueProviderはあんまり取り上げられてなくてちょっと残念。というか、Providerに関してはそれほどかも。

Chapter2のNote枠に書かれてたことで、なぬ~と思ったところがあったので紹介しておくと「.NET4では直ってるけど、system.WebServer/handlersにUrlRouting.axdが無いとIIS7の統合モードで動かないバグがあるよ」っていうところ。ローカルIISにデプロイして確認してみたところ、404エラーが出てちゃんと機能させることが出来ませんでした。気をつけようね!

なんだかんだと、面白いことがたくさん書かれてて、読んで損することは絶対ないと言い切れる内容だと思います。英語でDinoさんの難しい言い回しの部分は多々あるけど、その辺は適当に辞書引いて関係ないと思ったら読み飛ばし、気になるところだけがっつり読み込む感じで十分楽しめます!

次の本は776ページらしいので、またのんびり2カ月くらいかけて読んみようと思います。

鼻唄三丁と401

401と言えばUnauthorizedなんて言う人はHTTP好きすぎ。今回は違ってAppleからリリースされたiOS4.0.1→401です。

アップル - iPhone - 新しいiOS 4ソフトウェアアップデートの機能をご紹介します。

昨日の夜に早速適用したら、それはそれは残念な結果に。なんとまぁ更新に失敗してしまいまして。原因はよくわからないけど、何度やっても途中で失敗する。ダウンロードファイル(iPhone3,1_4.0.1_8A306_Restore.ipsw)を消してやり直してもダメで、全く起動できなくなりました。泣ける。過去一度もこんなことになったことないのに~。

しょうがないから場所もよく覚えてない(一度行ったことあるはずなんだけど)銀座アップルストアに行ってきたですよ。そしたら、その場で更新適用したらすんなり出来たみたいで「普通にできましたよ~」って言われた。ギャフン。ちょっとキレ気味で「どうなんてんのさ、電話もできねーじゃん!」って文句言ってごめんね。帰ってから同期したら普通に元通りでホッとしました。

IMG_0002 IMG_0001

ちなみに写真は電車で銀座に行くのにつかった京急の八丁畷。のどかすぎです(ホネだけに)。

Bing翻訳(Bing Translator)のブックマークレット

違うウィンドウ(タブ)で開くとき。

javascript:window.open('http://www.microsofttranslator.com/bv.aspx?ref=Internal&from=&to=ja&a='+encodeURI(location.href),'_blank','');void(0);

Bing翻訳

同じウィンドウで翻訳するとき。

javascript:location.href='http://www.microsofttranslator.com/bv.aspx?ref=Internal&from=&to=ja&a='+encodeURI(location.href);

Bing翻訳

とりあえず、見つからなかったので。

dotnetConf2015 Japan

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