ラベル ASP.NET MVC 3 の投稿を表示しています。 すべての投稿を表示
ラベル ASP.NET MVC 3 の投稿を表示しています。 すべての投稿を表示

2012年8月31日金曜日

ASP.NET MVCのキャッシュについてあれこれ

一年半前ぐらいにASP.NET MVC 3のキャッシュ周りについて調べまくったので今更ながらまとめておく。一年以上前の話なのであやふやな部分も少々あるけれどそこはご愛嬌ということで話半分に読んで欲しい。

で、キャッシュについて。キャッシュを有効活用できれば一番手っ取り早くサーバのスループットをあげることができるわけで、もちろんASP.NET MVC 3にもキャッシュ機能はある。


OutputCacheAttribute
OutputCacheAttributeがそれにあたる。この属性をControllerかActionに付与することによってそれらの出力結果をキャッシュしてくれる。使用方法は下記のような感じ。
[OutputCache( Duration = 5, VaryByParam = "fish;angler", VaryByHeader = "X-Requested-With" )]
public class ChinuController : Controller{
}
Durationで何秒間キャッシュするのかを指定。VaryByParamやVaryByHeaderの指定でQueryStringのパラメータとかヘッダーごとにキャッシュを分けることが可能となっている。その他にもいくつか設定できる項目があるみたいだけれど使ったことがないのでよく分からない。

で、この属性を使う上で一つ注意が必要なのが、さきほども強調表示してあるけれどこの属性は出力結果をキャッシュするようになっている。つまりこの属性を使用するとControllerのアクションは当たり前だけどViewのレンダリングもスキップされることになる。

で、それの何が問題なの?ってのは次の場合。


ドーナツキャッシュ
ログインユーザに「ようこそYooさん」のようなメッセージを表示する会員制のサイトがあったとする。そういう場合にOutputCacheだとディモールト(非常に)都合がよろしくない。というのもOutputCacheは出力結果をキャッシュするので、Yooさんの内容がキャッシュされた状態でSasukeさん(うちの猫)がそのページにアクセスすると「ようこそYooさん」と表示されてしまうのである。じゃぁそういう場合はどうするの?ってのでドーナツキャッシュという手法がある。下図のようにある一箇所だけを除外してキャッシュするのでドーナツキャッシュと呼ばれる。


その除外された箇所の描画用コールバックを用意しておくとそれが後から呼ばれるという寸法だ。で、詳細は下記を参照して欲しい。
Donut Caching in ASP.NET MVC

うん、冒頭のUPDATEを読んでびっくりだと思うけどこの機能はASP.NET MVC 3には組み込まれてない。キャッシュについて調べてたときも上記のサイトの説明を飛ばしてソースコードから入ったので、実装したら動かなくてちゃんと読み直したらびっくりしたね。F***って思ったよ。

で、困ったなーということで自前のキャッシュフィルターを作ることにした。


ResultCache
要するに出力結果をキャッシュされちゃうと柔軟性に欠けるというわけで、それならControllerのアクションの結果(ActionResult)だけをキャッシュして、そのキャッシュしといたActionResultをViewに渡して描画するようにすれば大体の問題は解決できるので下記のようなアクションフィルターを作ろうと思ったら既に作っている人(ASP.NET MVC Result Cache)がいたのでそれを拝借した。
public class ResultCacheAttribute : ActionFilterAttribute
{
 public ResultCacheAttribute()
 {
  Duration = 1200;    // 20 mins
 }
 public string CacheKey{ get; private set; }
 public CacheDependency Dependency { get; set; }
 private CacheItemPriority _priority = CacheItemPriority.Default;
 public CacheItemPriority Priority
 {
  get { return _priority; }
  set { _priority = value; }
 }
 public int Duration{ get; set; }

 public override void OnActionExecuting(ActionExecutingContext filterContext)
 {
  var url = filterContext.HttpContext.Request.Url.PathAndQuery;
  this.CacheKey = "ResultCache-" + url;
  if (filterContext.HttpContext.Cache[this.CacheKey] != null)
  {
   filterContext.Result = (ActionResult)filterContext.HttpContext.Cache[this.CacheKey];
  }
  base.OnActionExecuting(filterContext);
 }

 public override void OnActionExecuted(ActionExecutedContext filterContext)
 {
  filterContext.Controller.ViewData["CachedStamp"] = DateTime.Now;
  filterContext.HttpContext.Cache.Add(this.CacheKey, filterContext.Result, Dependency, DateTime.Now.AddSeconds(Duration), System.Web.Caching.Cache.NoSlidingExpiration, Priority, null);
  base.OnActionExecuted(filterContext);
 }
}
リクエストされたURLをキャッシュのキーにしてActionResultを保持しておき、再度同じURLがリクエストされた場合はキャッシュからActionResultを取得してfilterContextのResultに渡すというだけの至極シンプルなフィルター。OnActionExecutingを見て不思議に思うかもしれないけれど、実はfilterContextのResultにActionResultが設定されるとControllerのアクションは呼び出されないのだ。


というわけで、サーバリソース節約のためにもキャッシュはガンガン使っていくべきなので機会があれば快適なWebを実現するためにもドシドシ有効にしていただきたい。ただ一点注意が必要なのはなんでもかんでもキャッシュしていると予期せぬ出力結果になることがままあるのでキャッシュが有効になっている箇所の周りを開発する場合はその箇所だけをテストするのではなく、複数人からのリクエストを想定したテストなどを行わないと本番環境にリリースしてから痛い目にあうことがあるのでくれぐれも注意して欲しい。かく言う私もチヌかかり釣りMEGAのスマホ版をリリースしたさいに、ControllerのアクションでPC版とスマホ版のViewを動的に切り替えていたのでActionResultがキャッシュされてしまい予期せぬ動作になりとても焦った。ただその問題は前述のアクションフィルターにスマホ用のキャッシュキーを追加することによってサクッと事なきを得たので良かったけれども。

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年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年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にした場合の解説をする予定だ。