2009年1月28日水曜日

待ちに待ったASP.NET MVC RCリリース

なんかもうお祭り状態でフィードの嵐。海外ばっかりだけど 国内だとナオキさん小野さんくらい?

ASP.NET MVC 1.0 Release Candidate Now Available - ScottGu's Blog

とにもかくにもスコガルブログ。

ASP.NET MVC Release Candidate Controls Collection Cannot Be Modified Issue with ASP.NET MVC RC1

Philさんとこ2つ。

Download details: ASP.NET MVC RC 1

何はともあれダウンロードしてインストール! リリースノートも忘れずに。

細かい話はリリースノートに全部書いてる。 スクリプトが用意されててIIS6/7Classicで動かすときの設定が自動化されてるとか。 で、とりあえずスコガルブログの面白ポイントピックアップ。

便利なAdd Controller/Add Viewコマンド

それぞれT4テンプレートエンジンで生成。 マシンレベルならC:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC\CodeTemplatesフォルダを書き換え。 プロジェクトレベルならプロジェクト直下にCodeTemplatesフォルダを作ってそのなかに入れる。ControllerテンプレートはAddController、ViewテンプレートはAddView。 ・Add Controllerの内容 単純にIndexアクションを定義するのみ。 ・AddViewの内容 こっちは登録テンプレートリストから生成(Scaffolding)するViewの選択が出来る。 型付けViewPageを選択しないとモデルが分からないから生成する項目が不明になるから、ちゃんと型を指定するんだけど、POCO(Plain Old CLR Object:単純なモデルクラス)を推奨。もちろんいろんな(LINQ to SQLもLINQ to Entityも)のが使えるけど、基本的にはView専用のモデル(ViewData.Modelに入れるモデル)を定義してそっちを使いましょう。

デフォルトで生成出来るタイプは以下の5通り。 ・Create(新規登録) ・Details(詳細表示) ・Edit(編集) ・List(一覧):普通にモデルを指定するとIEnumerableとして生成。賢い。 ・Empty(なんも無し)

便利なGo to Controller/Go to View

アクションのViewResultを返すのところでコンテキストメニューから対象ビューを開いたり、ViewのASPXからコントローラを開いたり。

自動コンパイル

コンパイル時にデフォルトではコードしかコンパイルしないから、View(ASPX)内のインラインコードのエラーは実行時にしか分からなかった (MSBuildの設定変えればBetaでもViewのコンパイルは可能)けど、RCでは最初からそれが出来るコンパイルようなオプションあり。 もちろん毎回そんなことしたらコンパイルに時間がかかり過ぎちゃうから普段使いではViewのコンパイルはしない方がいいかもね。 設定はcsprojの以下の箇所をtrueすればよろし。テキストエディタでよろしく!

<PropertyGroup>
 <MvcBuildViews>true</MvcBuildViews>
</PropertyGroup>

地味にリファクタリング機能追加

Controllerのクラス名を変えるとViewのフォルダ名が変わって、ControllerのActionメソッド名を変えるとViewのファイル名が変わるようにリファクタリング機能も追加。けっこう便利なはず。

これは強烈、Viewのaspxにコードビハインドファイルを全く無しに出来る。

事前アナウンスがあった待望の機能。 型付けViewPageのタメだけのコードビハインドなんていらない! そんな思いを実現するために、Viewフォルダ内のweb.configに不思議な設定が追加されて、Pageディレクティブのinheritsに直接ジェネリックViewPageを定義可能に。 これはもう最高だよね!

ページでのModel参照

ViewPage内でViewData.Modelを参照するときはViewPage.ViewDataプロパティを参照するためにViewDataから書く必要があったけど、これからはViewPage.Modelを参照するからModelから書けばいいさ。 ViewData.Model.HogeをModel.Hogeって書ける。少しだけどコード量が減るね。 もちろん今まで通りViewDataから参照することも可能。

ページタイトルの変更も簡単に

Site.Masterも変更になって、ページタイトル(head内のtitleタグ)がページから簡単 に変更できるようにContentPlaceHolderを2つ(headとMainContent)用意。これで、 ViewData["PageTitle"]とかに入れて、Site.Masterでtitleにセットする必要が無くなってちょっと綺麗に書けそう。 なにげにページタイトルって面倒だもんね。 共通処理にしようと思うと、ページタイトルを取得するためにController/Actionの全組み合わせのデータを内部で保持とかしなきゃだし。

ただPhilさんも指摘の通り、Head自体は相変わらずrunat="server"がついてるからSite.Masterのhead内に直接<%= ~ %>を書くとエラーになりんす。 回避するために<%= ~ %>はContentPlaceHolderをもう一つ作ってその中に入れましょう。 これまでは速攻でrunat="server"を消してたんだけど、ContentPlaceHolderを使いたい場合はでもなくていいんだから、パスの解決にUrl.Contentヘルパーとか使えば、サーバーコントロールにする必要全く無いし。

Futuresの機能だけどテキストフォーム拡張

HtmlHelperを拡張してフォームタグ出力の時に、テキストで項目名を指定(この名前をさらに内部でViewData.ModelやModelStateのリフレクションに利用)してたのを、ラムダ渡しで型付プロパティを直接渡せるようになりました。 ただ、コレクションや配列でプレフィックス使いたいときの指定はどうするのか気になるところ。プレフィックスを指定できるオーバーロードはなさげ。

Bind[Prefix=""]でもインスタンス化

フォームポスト時にBind属性を指定しなくても、ちゃんとデシリアライズしてオブジェクトが復元出来るようになりました。ポストする項目をちゃんと Domain Objectだけにしておけばわざわざプレフィックスをつける必要無いと。ModelBinder関係は凄く変わってるみたい。リリースノートにもなんか書いてある。

IDataErrorInfo

Validationがらみで追加されたのがIDataErrorInfo(もともとSystem.ComponentModelに持ってる)。いまいちよくわかんない。自動でリフレクションでチェックしていってくれるのかな~?だとしたらどのタイミングで?? Item実装時に項目毎のチェック処理を書くのかな~。 それだけだとDataAnnotationsとなにが違うんだろうってことになるから、やっぱりソース見ていつ実行されるのかが分かれば、見えてくる気がする。

Controller.ControllerContextがRequestContextから派生しなくなった

これまで、コントローラのテストが結構面倒だった。 それもこれもControllerContextが今までRequestContextから派生してたからなんだけど、今回から派生しなくなった(プロパティで保持)。 Moqは使ったことない(RhinoMock)んだけど、サンプルの通りこれだけのコードで済むなら乗り換えしようと思えるね。AccountControllerのテストが実際に見れるけど、Moq(じゃなくてもいいけど)使えばもっと簡単にできるような内容だね。

AntiForgeryの標準取り込み

CSRF(Cross Site Request Forgery)を防ぐための、AntiForgeryToken()とValidateAntiForgeryTokenフィルターがとうとう組み込まれた。これまでFuturesで別アセンブリだったのがこれからは標準装備だね。

FileResultクラスとController.File()メソッドでファイルの出力とダウンロードが簡単に

これまたFuturesだったBinaryResultとBinaryStreamResultを一個にまとめて、簡単に処理出来るようになってる。ファイル名を渡さなければ、バイナリストリームを直接レスポンスするし、渡せばattachmentで保存できるように。 と、簡単に書かれてるけど、中身を見てみるとController.File()は用途に合わせて FileContentResult/FileStreamResult/FilePathResult(いずれもFileResultの派生クラス)と3つのActionResultを返すようになってる。FileContentResultがbyte配列、FileStreamResultが Stream、FilePathResultがファイルのパスを指定。 これらはController.File()のオーバーロードだからあんまり意識しなくてもいいのかな。FilePathResultがResponse.TransmitFileで、他2つはResponse.OutputStream.Write。 グッジョブ!

アップロードも簡単に

ファイル出力だけじゃなくてアップロードも簡単にできるようにHttpPostedFileBaseに直接バインドされるようになってる。Request.Filesから取らなくても良くなりました。 ちなみにFuturesには複数ファイルアップロード時のBindも定義されてたよ。 非同期アップロード(iframe/Flash/Silverlight)なら個別アップロードでもいいから使わないかもしれないね。

Ajaxも改善。jQueryのインテリセンスがデフォで

1.3系じゃなくて1.2.6のまま。 なにより、これまでのIsMvcAjaxRequestプロパティはX-Requested-Withヘッダを見るだけだったけど、 IsAjaxRequestプロパティに名前が変わって各種ライブラリ(Prototype.jsも!!あとjQueryとDojo)で使ってるヘッダの解析も実装したので、自分で解析を実装しなくて良くなりました。超嬉しい!! JavaScriptResultの追加も。JavaScriptをそのままレスポンスするものみたいね。 サーバーサイドにJavaScriptコード書くのはちょっと...。

Futuresの中身を見てみた。 ソースがまだ公開されてないけど、そこはそれReflectorで。 なんと待望の非同期コントローラが!! 非同期で処理するには、IHttpHandlerじゃなくIAsyncHttpHandlerが必要になるんで、MvcAsyncHandlerももちろん含まれる。一式ちゃんとある感じ。 もちろんルートに登録するためのMapAsyncRouteもあるよ! Improve scalability in ASP.NET MVC using Asynchronous requests « Steve Sanderson’s blog ↑こんな感じなのかな? 使い方は...。ちょっと試す。 LinqBinaryModelBinderなんてものも。Base64のデータをSystem.Data.Linq.Binaryクラスにバインド。 キャッシュされてても強制書き換えが出来るHtml.Substitute(httpContext => ~)が入ってる。前からあった??

もちろんリリースノートもチェケラッチョ

・これまでformタグを出力するときにHtml.BegineForm/BeginAjaxFormだったからルート名を指定したアクションにポストするときなんかは直接formタグを書かなきゃいけなかったけど、BeginRouteFromが追加されて、そんな苦労ともおさらばだ! ・DropDownList/ListBoxが変更になったみたい。 今まではSelectList/MultiSelectListをViewDataに入れてたけど、これからはIEnumerable<ListItem>でいいんだって。

・沢山のバグフィックス。 "Html.BeginForm and Ajax.BeginForm have been fixed to not render a fully qualified URL." ここが凄く気になる。 Adding HTTPS/SSL support to ASP.NET MVC routing « Steve Sanderson’s blog ↑関係ないかもしれないけど、ここの問題ってコレ系だったりするんじゃないのかと。違うかもしれないけど要チェックや。 ・ベータのコードをRCに移行する方法 1.まずはアセンブリ参照の変更。 2.コンパイルが通るまでがんばってコード直す。 3.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>

※ViewTypeParserFilter入れとくと、型付ViewPageをinherits指定だけで出来るようになる。 That's it! って、Viewのinherits書き換えたいから、やっぱゴッソリ書き直したくなるね。 最終リリースが来月と思いのほか早い展開になってきた楽しみのつきないASP.NET MVC。

2009年1月11日日曜日

xVal

まだバージョンも0.5だし、これからなプロジェクトなんだろうけど、これはちょっと目が離せないかも。

ASP.NET MVCでサーバーサイドのバリデーション(入力検証)を行うとき、DynamicDataで導入されたDataAnnotationsを使うのが、現時点ではベストな選択なんじゃないかと思うんだけど、いかんせんサーバーサイドでの属性ベースのテクノロジなので、クライアントサイドでの検証は別途実装しなきゃなところ。 クライアントサイドでの検証があるだけで、入力をいちいちサーバーにポストしなくても何がエラーなのか、入力と同時に分かるから便利だけど、その為に同じルールを何度もコーディングするのは面倒だよね。面倒だけど仕方なく実装する感じ。

そこを橋渡しするのがこのxVal。

xVal – Home

DataAnnotationsで属性に指定した検証ルールをJson形式に変換して、クライアントに書き出すことで、ルール定義は1箇所で済ませようという、楽したいがタメに生まれたナイスなプロジェクト。 サーバーサイドの属性の抽出用プロバイダとして、System.ComponentModel.DataAnnotationsだけじゃなく、Castle.Components.Validatorも実装。

クライアントサイドの検証にはjQuery.validateの他にも、ASP.NET標準の検証コントロールを使った実装もある(けど、こっちはあんまり興味なし)。xVal.ClientSidePluginsに入ってるこの2つのクライアントサイドの実装は、たぶんこれ以外にも例えば prototype.js版とか作れるってことだよね。AllPossibleRulesを全部書けば...。誰か作ってくれるんじゃないかな~。作ってくれないかな~。

xVal - a validation framework for ASP.NET MVC « Steve Sanderson’s blog ↑ここで、どんな物なのか書かれてる。

サンプルプロジェクトもあるので、実際に動かしてみるのが分かりやすいね。 って、ことで、早速ダウンロード。

BookingsDemoにはxValのアセンブリは含まれてるけど、ソースは含まれてないのでソースはCodePlexから。

xVal - Source Code

なるほど~。すばらしいくらいリフレクション。 DataTypeRule/RangeRule/RegularExpressionRule/RequiredRule/StringLengthRuleの5つのルールに属性を変換するんだね。

サンプルでレンダリングされるJsonは↓。

<script type="text/javascript">
 xVal.AttachValidator("booking",
   {"Fields":[
       {"FieldName":"ClientName","FieldRules":[
           {"RuleName":"StringLength","RuleParameters":{"MaxLength":"15"}},
           {"RuleName":"Required","RuleParameters":{}}]
       },
       {"FieldName":"NumberOfGuests","FieldRules":[
           {"RuleName":"Range","RuleParameters":{"Min":"1","Max":"20","Type":"decimal"}},
           {"RuleName":"DataType","RuleParameters":{"Type":"Integer"}}]
       },
       {"FieldName":"ArrivalDate","FieldRules":[
           {"RuleName":"DataType","RuleParameters":{"Type":"Date"}},
           {"RuleName":"Required","RuleParameters":{}}]
       }]
   })
</script>

見ての通り、入力値の単純な検証は変換するけど、ビジネスルールはサーバーサイドのみで実行。 それが、どこにあるのか探してみると、BookingManager.PlaceBooking。 その中で、属性ベースの検証の実行と、ビジネスルールの検証の両方を実装。

アクションはどうやってるのか見てみると

        [AcceptVerbs(HttpVerbs.Post)]
       public ActionResult CreateBooking(Booking booking)
       {
           try {
               BookingManager.PlaceBooking(booking);              
           }
           catch(RulesException ex) {
               ex.AddModelStateErrors(ModelState, "booking");
           }

           return ModelState.IsValid ? RedirectToAction("Completed")
                                     : (ActionResult) View();
       } 

RuleException が発生(PlaceBooking内でエラーがあったらスロー)したら、ModelStateにエラーを入れる。これで、JavaScriptオフでも入力検証はきちんと実行されるて、エラーフィールドにはinput-validation-error、エラーメッセージも表示(<%= Html.ValidationMessage(モデル名)%>)されるっていうすばらしさ(ModelState.AddModelErrorでエラーを入れれば、Htmlヘルパー経由の場合、ちゃんと反映されるっていうASP.NET MVCの設計がこういうとき威力を発揮するね)。 RCがなかなかリリースされないけど、この辺見てるだけでも楽しいかも~。 ※RCでは動かなかったりするかもしれないけどね。

2009年1月2日金曜日

ちょっとしたTips

ASP.NET MVCの情報源として外せないStackOverflowで↓こんな質問があがってたんです。

Compile Views in ASP.NET MVC - Stack Overflow

ViewPageのコンパイルって先送りされるじゃないですか。 んで、ページを表示させたときに、エラーが出て「あ、そっか」って気付くことも多いと思うんですよね。 対応として、プロジェクトファイルに以下の設定を書いておけばいいって、ことなんだけど面倒じゃないですか。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <Target Name="PrecompileWeb">
       <AspNetCompiler
           VirtualPath="/MyWebSite"
           PhysicalPath="c:\inetpub\wwwroot\MyWebSite\"
           TargetPath="c:\precompiledweb\MyWebSite\"
           Force="true"
           Debug="true"
       />
   </Target>
</Project>

もっと簡単にできる方法があってですね。 Code rant: Compiling aspx templates using aspnet_compiler

1.ウソコード書いてみる。

img.aspx

ViewData[a+b]なんて書いてるけど、そんな変数宣言もしてないし。 でも、ビルドしてもステータスバーみて分かるとおり「ビルド正常終了」する。

2.プロジェクトプロパティで設定する。

「ビルド後に実行するコマンドライン」に以下を書いておく。 C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v / -p "$(ProjectDir)\" ※環境によっては違うよ~。

img.aspx2

3.もう一度ビルドする。

img.aspx3

ちゃんと「ビルド失敗」するよ~。 ViewPage多いとちょっと時間かかるけど、ページみていきなりビックリしたり、ページ見るまでエラーに気がつかないなんてことが無くて、安心だね!

ちなみに、ASP.NET MVCの次のリリース(RC)では「ASP.NET MVC RC版では、プロジェクトのコンパイル・プロセスの一部としてビューを含めたい場合に使用できるビルトインのMSBuildタスクがあります。これにより、アプリケーションにあるすべてのビューやマスターページに含まれる構文やコードが検証され、もし問題があればビルド・エラーが発生します。」(ASP.NET MVCデザイン・ギャラリーとASP.NET MVC RC版におけるビューの改善 - @IT)ってことで、こんな事しなくてもいいみたいだけどね。

2008年12月31日水曜日

Blogsvc.net

これまたASP.NET MVCをつかったCMS。バージョンが0.8でずいぶん進んでるのに今まで全然知らなかったです。

BlogService – Home

同じジャンル・ターゲットのプロダクトとして最近Oxiteが出てきたけど、OxiteがMetaWeblogAPIなのに対して、こっちはAtomPub API。

機能的にはほぼ互角な感じだけど、動かすと最初にウィザード形式で設定する分、使う人にとってはすこしとっつきやすいかも。

API 実装してるからか、編集機能はWebアプリケーションとして実装しない(してても適当、Oxiteもそうだけど)で、Live Writerとかの外部エディタに任せるスタイル。これはコレでいい割り切りだと思うな。プロバイダサービスみたいに沢山の人にたいして、使ってもらうんじゃなくて会社のサイトや個人のサイトをターゲットプラットフォームとして考えた場合、妥当な選択。編集機能にリソースをさくより、API実装に専念しておけばいいよね。

初期のデータプロバイダがDBじゃなくてファイルベースのXMLだったりする。でも、もちろんLINQ to SQLのプロバイダもあって、テーブル構築用のSQLも用意されてます。でも、コードを追っかけるとRepositoryを直接インスタンス化してる箇所がWebMvcプロジェクト(Webアプリ)にもあるから、ファイルストレージを前提として開発してるんだろうね。開発チーム内で分業されてるからかな?

ファイルストレージで気になるのはファイルフォーマット。どういう形式でファイルに保持してるのかなと思ってみてみたら、Atom形式のXml。全部が全部 Atom。で、Atom関係のモデルクラスはDomainプロジェクトに集約されてて、そのモデルを使うので、DBのモデルを直接使うなんて事はしてないんですな。AtomPub APIをとことん使いこなす設計ですばらしい。

んで、ファイルだからIO負荷が気になるところだけど、そこはシンプルにRepository実装内で、staticなDictionaryを持ってて、そこで保持。ファイルが書き換わったときにDictionary内のデータを破棄するために、ちょっとオシャレな作り方してて参考になるので、ここで引用。

RepositoryプロジェクトのFile/XmlCache.csの最後の所。

        public void CacheItem(string filename, T item)
        {
            if (!objs.ContainsKey(filename)) objs.Add(filename, item);
            //items aren't actually put in http cache, but an object handle is
            HttpRuntime.Cache.Insert(filename, new object(), new CacheDependency(filename),
                Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(30) /*TODO: make configurable*/, CacheItemPriority.Normal,
                //remove from cache
                new CacheItemRemovedCallback((key, value, reason) =>  objs.Remove(key))
            );
        } 

実体をキャッシュに入れちゃえばいいじゃんと思うけど、なんかあるのかな。CacheDependencyの削除コールバックがラムダ式で書かれてて、コードがあっちこっち行かなくて素敵でしょ? 中でTidy.NETっていう別のオープンソースプロダクトを使ってて、HTMLの整形をしてるのかな? 同じく別のライブラリでSgmlReaderっていうのも使ってる。

それぞれ用途が違うのかな。どっちかだけでいいって物でもないの?整形するのとパースするのとで使い分けてるのかな。

SourceForge.net: Tidy.NET SgmlReader – Home

WebMvc プロジェクトにASP.NET MVCの実装が入ってるけど、テーマをサポートするために(ASP.NETのテーマとは違う)、独自のViewEngineとして ThemeViewEngineを使ってる。といっても、View/MasterPartialViewそれぞれのLocationFormatをセットして、Find系を実装するのみ。

フォルダ構成のなかにViewがなくて、themesっていうのがあるけど、そこにすべてのViewPageが入ってます。で、defaultとの差分のみをそれぞれのテーマフォルダに入れておけば、無いものはdefaultフォルダのものを使うように LocationFormatに登録されてるから問題なし。 ※default以外にはテーマ毎のイメージとCSS、テーマ説明のAtomEntry XMLとサムネイル画像が入ってるくらい。

URIで各ページの拡張子にフォーマット指定でxhtmlってつけるのをルールにしてるのは、RESTfulを意識してるからなのかな。

ソースをダウンロードして、動かそうとするといきなりコンパイルエラーがでるのはご愛敬。

単純に文字列閉じてないだけ。なのでDomainプロジェクトのAtomText.cs 行50の部分を↓変更。 デフォルトのtrailerがなんなのかオンラインでソースを確認しても化けててわかんないので、とりあえずなんか入れとくってことで"..."を使う。 ※trailerは文字列を指定の長さに切り抜いて、元文字列が指定長以上なら後ろにくっつける文字列。

        public string ToStringPreview(int length)
        {
            return ToString().AbbreviateText(length, "...");
        } 

AtomPub APIを公開するのにあわせて、Atom実装のコードがたくさんあるけど、その他はパッと見シンプルな実装な感じ。

ViewPageも型指定(強い型付け)でViewDataを扱ってるし、コードをほとんど含まないように書かれててOxiteよりも綺麗な印象。好みの問題?

ちょっとOxiteのコードは量が多くて、冗長な感じがしなくもないし...。 このBlogsvc.netもCMS+Blogエンジンとして(Oxiteだけじゃなく)十分いけてる気がするね!

2008年12月30日火曜日

Repositoryで実装してみる

とりあえずデータアクセスはLINQ to SQLのみなんだけど、テストしやすくしたいし、DataContextクラスに大量のコードを書くのもいかがな物かと。で、やっぱりRepositoryパターンだよね!ってことで今更ながらコードを書いてみた次第なんです。

データストレージ層とデータアクセス層、サービス層の分離を明確にして、インターフェースベースの実装にしとくことでテストしやすくなるっていうことなんだけど、さらにデータストレージ層を置き換えも楽ちんとかいうのは、まぁ実際そんなこと滅多にするもんじゃないから普通のプロダクトにおいてそこがメリットになることはほぼなかったり...?

データモデルクラスをLINQ to SQL(L2S)のモデルの他に、純粋なデータクラスとして別途定義するのはコード量が増えるし、ちょっとイヤだな~、と思うところでそこはLINQ to SQLのモデルクラスをそのまま使うってことで勘弁してもらいます。クラスの変換とか余計な処理も必要になるしな~、と。

でね、IRepositoryでインターフェース定義して、その実装をLINQ to SQLを使って実装するんだけど、SubmitChangesってどこに書くのがいいんだか、ちょっと悩ましいところだったりしまして。 きちんとデータモデルを分けて定義しておけば、サービス層からデータアクセス層にデータクラスを渡して、その中でL2Sモデルに値をマッピングするのが王道パターンだとしても、それをやりたくない場合、サービス層でL2Sモデルに直接値を入れたりすることになるじゃないですか。わかりにくい説明でなんだかわかんなくなるな。

テーブルPeopleがあって、L2Sモデルクラスとして

public class Person
{
  public int Id {get;set;}
  public string FirstName {get;set;}
  public string LastName {get;set;}
}

っていうのがあったとしましょうよ(これがdbmlから自動生成されたとしてください)。 んで、そのDataContextクラスをTestDataContextとしましょう。 これに新しいデータを追加するなら

var db = new TestDataContext();
var person = new Person(){Id=1,FirstName="ロビン",LastName="ニコ"};
db.People.InsertOnSubmit(person);
db.SubmitChanges();

なんて書きますよね。 更新や削除なら

var db = new TestDataContext();

var person = db.People.FirstOrDefault(p=>p.Id=1);
if (person != null) {
  person.FirstName = "オルビア";
  db.SubmitChanges();  // ←これで更新確定
}
db.People.DeleteOnSubmit(person);
db.SubmitChanges();  // ←これで削除確定 

みたいな。 追加、更新、削除を持ったRepositoryにするなら

public interface IPeopleRepository {
  void Add(Person model);
  void Update();
  void Delete(Person model);
} 

とか、って書きたくなるけど、Updateは実質SubmitChangesだけじゃん!みたいな。 この例だと引数は最大3個でいいかもしれないけど、クラス次第でスゴイ引数の数になっちゃうから、もう一つ上で直接モデルに値を入れるほうがコード少なくて済むって話しです。 普通にRepositoryの関数内で都度SubmitChangesを呼び出すのが正しい実装、だと思う。

ASP.NET MVC Storefront Part 10: Shopping Cart Refactor and Authorization : The Official Microsoft ASP.NET Site

↑ここでもそういう書き方にリファクタリングしてるし。

そうすると、複数の更新をまとめてSbumitChangesしたい、なんてのはダメにするか、そういう関数をRepositoryに追加するってことになる。どっちもなんかヤダ。 んじゃ、データ操作の確定のためのSubmitChangesもサービス層で呼べばいいのかっていうと、単純にDataContextを上の層で持ちたくない(db.SubmitChanges()なんてコードをサービス層で書きたくない)。 結局、単純にSubmitChangesと同名のメソッドをRepositoryに実装して、それをサービス層で呼び出せばいいのかな?と。そしたらDataContextをサービス層が弄る必要ないし。

var repository = new PeopleRepository();
var p1 = new Person(){...};
var p2 = new Person(){...};
var p3 = new Person(){...};
repository.Add(p1);
repository.Add(p2);
repository.Add(p3);
repository.SubmitChanges(); 

↑こんな感じ?(コレクションにしてない&InsertAllSubmitにしてないっていうのは気にしないでね) で、ふと思い出した。Fluent Programming(Fluent interface - Wikipedia, the free encyclopedia)。 データ操作の戻り値として、IRepositoryを返せば(この場合ならIPeopleRepository)つなげられるな~、と。

public interface IPeopleRepository {
  IPeopleRepository Add(Person model);
  IPeopleRepository Delete(Person model);
  void SubmitChanges();
}

↑インターフェースはこう替える。 んで、実装は済んでるとして、呼び出し側では↓こう書く。

var repository = new PeopleRepository();
var p1 = new Person(){...};
var p2 = new Person(){...};
var p3 = new Person(){...};

repository.Add(p1);
         .Add(p2);
         .Add(p3);
         .SubmitChanges();

これなら間に、Deleteとかも連結出来るもんね。

他のRepositoryをどうするんだよ、って言うのは、まぁ...、そうなんだけど...。 とりあえず、同じRepositoryの場合はコレでいいですかね(結局L2Sモデルクラスをサービス層で使うんなら分離出来てないじゃないか、っていうのはその通りなんだけど...)。

2008年12月22日月曜日

Oxiteのソリューション構成

ちょいちょいソースを見ていこうと思うところで、まずはソリューションの構成を確認して、どんなコードがどのプロジェクトに含まれてるのかを把握しとこうかなと。


ソリューションを開いたところで、表示されるプロジェクトは↓。

img.aspx

それぞれのプロジェクトに何が含まれてるのかを見てみたよ。

1.Oxite


このプロジェクトにOxiteで共通のライブラリコードや、インターフェースが集中。
BackgroundServicesにBackgroundServicesで実装する各種バックグラウンドタスクのベースクラスや、インターフェース。
Configurationにはweb.configのカスタムエレメント用のクラス。クラス自体は大量にあるけど、内容は各設定値を取得するための物だからエレメントの数だけクラスがある感じ。
Dataにはデータベースのモデルクラスのインターフェース。実装はLinqToSqlProviderに入ってる。
HandlersにMetaWeblogAPIとXmlRPCのクラス。コントローラクラスだけじゃなくてこのハンドラもルーティング登録されてるからコントローラを探しても見つからず。
Routingにはそのままルーティングを登録するクラス。ルート登録の詳細がMVCのWebアプリケーションのglobal.asaxじゃなくて、ここにすべてまとめて入ってる。
Searchに検索機能のインターフェース。
あと、ルートに共通関数が入ってる。
で、 StringExtensionsクラスにStringクラスの拡張メソッドが入ってるんだけど、なんとこの中でAntiXssLibraryをラップしてる関数有り。AntiXss.HtmlEncodeとAntiXss.HtmlAttributeEncodeを使って、エンコードするようにしてる。さすが抜け目なし。ただバージョンは先日公開された3.0じゃなくて1.5を使ってる。ちょっと古いのを使ってるのが気になるところだけどリリースの関係でOxiteの先にリリースしてるし、MIXのサイトでも使ってるって事で、そこはやむなしな感じで。

2.Oxite.BackgroundServices

バックグラウンドで動作する機能の実装。
何個かクラスがあるけど、ようはメール送信(SMTP送信)とトラックバック送信(HTTP POST)の2つを処理してる(と思う)。
CreateMessagesFromSubscriptions(BackgroundService)

コメントをメールメッセージとしてMessageRepositoryに登録。

SendMessages(BackgroundService)
MessageRepositoryに登録されてるメッセージをメール送信。

CreateTrackbacks(PostBackgroundServiceAction)
トラックバック要求をBackgroundServiceActionRepositoryに登録。

SendTrackbacks(PostBackgroundServiceAction)
BackgroundServiceActionRepositoryに登録されてる要求を実行。

3.Oxite.LinqToSqlProvider


QxiteプロジェクトのData内に定義されてる、リポジトリの実装。
モデル自体はLINQ to SQL(dbml)で定義されてるのをそのまま。
あとIQueryable<T>の拡張メソッドでページングデータ(PageOfAList<T>)を取得するものや、モデルクラスの拡張機能など。
MembershipRepositoryがoxite_User/oxite_Roleなんかを見るような独自実装なのがちょっと以外。これはGravator対応とかが関係してるんだろうか?関係なさそうだけどなんでASP.NETのMembershipじゃダメなんだろうね~。

4.Oxite.LiveSearchProvider


サイト検索をWindows Live Searchで実行するための実装。
http://soap.search.live.com/webservices.asmx?wsdlへのWeb参照(SOAP)で検索要求を送信して、結果を取得するようになってる~。

5.Oxite.SearchProvider


これも検索用の実装なんだけど、こっちはLINQ to SQLで実行。
Post.SearchPost(TitleとBodyをくっつけたもの)に対してContainsで単純に検索してる。
Like検索だね。

6.Oxite.MVC


このプロジェクトに全部のコントローラクラスが入ってる。
OxiteApplication クラスがHttpApplicationを派生したクラスとして、定義されててこの中のOnStartでバックグラウンドサービスの開始や、ルーティング登録の実行。BeginRequestでリダイレクトがどうのこうのって処理があるけど、なんのタメの機能なのかよくわかんないかも。
ControllerもBaseControllerっていうクラスをベースにして実装。
ルーティングとコントローラのクラス名を見比べると、URLの設計はそれはそれだろ的な思いもちらほらと。


AccountController(Users/{userName}/~,SignIn,SignOut)
AdminController(Admin/~)
ArchiveController(Archive/{*archiveData})
AreaController({areaName}/~)
FileController
HomeController
PageController({*pagePath})
PingbackController({id}/Pingback)
SearchController(OpenSearch.xml,Search/~)
SEOController(Robots.txt,SiteMap/~)
TagController(Tags/{tagName}/~)
TrackbackController({id}/Trackback)


Viewで使うViewPageクラスの代わりにBaseViewPage/BaseViewUserControlなんかを定義。
OxiteSite(Webアプリケーション)で使う共通関数なんかもここで定義。Oxiteでも共通関数があったりするけど、Webアプリケーションでしか使わないのはたぶんここに集中してるんだろね。
ヘルパーの拡張なんかもここ。
FeedResultっていうのがなぜかViewResultから派生させて、Viewとして定義してるのはちょっとダサイ気がしなくもない。
Trackback/Pingbackを属性クラスで定義して、ViewDataにディスカバリー情報を入れるっていうのはカッコイイ。

7.OxiteSite


これがサイトの実体プロジェクト。
でも、実質コントローラもロジックも別のプロジェクトにごっそり入ってて、ここではもっぱらViewに関する物だけがある。
なので、Global.asaxのコードビハインドではOxiteApplicationクラスから派生。
ほとんどのViewにAtom/Rssそれぞれのフィード専用Viewが入ってて冗長な気が...。もう少し綺麗にできるんじゃないのかな~。ダメなのかな~。
特徴的なのはViewDataがすべてDictionaryのままで、View内キャストするようになってるとこ。
ViewPage<T>なんて定義はなく、すべてキャスト。しかもViewに結構なコードが含まれてる気がする。
あと、ルートにLiveWriterManifest.xml(Windows Live Writer Provider Customization API) があって、MetaWeblogAPIに関する設定が書かれてた。こんなファイルがあるのを初めて知りました。


とりあえず、含まれてるプロジェクトはこんな感じってことで。
中身・機能についてもちょいちょい見ていきます。

2008年12月19日金曜日

RCなんですね

ASP.NET MVC Design Gallery and Upcoming View Improvements with the ASP.NET MVC Release Candidate - ScottGu's Blog


デザインギャラリーについての部分も面白そうだけど、それより何よりASP.NET MVCの次のリリースが1月にRCを出すってところ。
IDataErrorInfoっていうのが、どういうインターフェースなのか気になって年越しが落ち着かないじゃないですか!

View生成の時にScaffold出来るとかは、正直あんまり興味なかったりするけど、管理機能を作るのがどれだけ簡単かが、重要なエンタープライズ(社内システム)開発には有効なんだろうね。
でも、Viewのコードビハインドが全く生成されなくなるっていうのはすばらしいかも。

確かに@PageディレクティブのInheritsに指定するならViewDataの型指定のタメだけのコードビハインドファイルはいらない(Strongly-Typed ViewData Without A Codebehind - Tim Barcz)。
ひょっとして、そうすることで、コンパイルしなくてもViewData.Modelにアクセス出来るようになったりするんだろうか。

試してみたら、今のバージョン(ベータ)でも上記サイトのようにInherits="System.Web.Mvc.ViewPage`1[[モデルクラス名,アセンブリ名]]"を書いておけば、コンパイル無しでもViewData.Modelにアクセスできるね(`1ってついたクラスしてだったり面倒だけど、ジェネリックバージョンはこっちなんだもんな~)。
ViewData.ModelじゃなくてModelから書けるようにもなるっていう。

んで、すっごく気になるのがJavaScriptResult。
どういう風に使えるんだろ。

とにかくJavaScript内でViewDataを参照したいときとか、単純に外部jsに出来ないから、aspxにしてるけど、それをscriptタグで読み込むような使い方にしちゃうと、コードの色分けもIntelliSenceも効かなくてちょっと切ない。かといって、ページに直接 JavaScript埋め込みたくないし。
全然JavaScriptResult関係ない気がしてきた...。
1月にRCだと、その次はいつになるやらデス。

dotnetConf2015 Japan

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