Archive

Archive for the ‘mvcConf @:Japan’ Category

mvcConf @:Japan ビデオ公開

mvcConf @:Japan ~ ASP.NET MVC ブートキャンプ ~

mvcConf @:Japan の各セッションおよび会場で流れたビデオ等が公開されました。
なんと公開先はChannel9です。
いいのかなぁ。。。

私のセッションについては以下のページで見ることができます。
まぁ、セッションのビデオは画面の映像だけなのがちょっと安心できるとこ(w

http://channel9.msdn.com/Events/mvcConf/Japan/mvcConf-Japan/Session4

セッションは45分と短い時間だったため、実はBlogに書いた内容のほうが詳しくなっています。
それでもなんなので、ほんとに一部だけBlogに書いていない話もあったりしますが。

よろしかったら見てやってください。

#自分ははずかしいので見れない気がする。。。

mvcConf @:Japan ふりかえり 6: Webサービス(OData)連携

2011年6月22日 1件のコメント

.NET Frameworkを利用している場合、WCF Data Servicesを利用することでODataベースのWebサービスを簡単に提供することができます。
このWCF Data Services と連携するASP.NET MVC 3 のプログラムを作成してみます。

まずはWCF Data Servicesの実装、ということで空のWebアプリケーションプロジェクトを作成します。
WCF Data ServicesでCodeFirstを利用することは、いまのところ予定されている、という状態にとどまっています。
ここではデータベースファーストでWCF Data Servicesを構築することにします。

作成したWebアプリケーションにApp_Dataフォルダを追加し、そこに最初のサンプルで構築されたデータベース(Group.sdfファイル)を追加します。
この時点で一度ビルドを実行しておきます。

Entity Data ModelをGroupModel.edmxという名前で追加します。

image

ウィザードはデフォルトの状態のまますすめていきます。

image

テーブルをモデルに含めます。

image

モデルが作成されます。

image

この時点でまたビルドを実行します。
WCF Data Serviceのページを追加します。

image

データサービスのコードのひな形が作成されるので、以下の赤い部分を追加します。

—————————————————————————————–

using System.Data.Services;
using System.Data.Services.Common;

namespace WebApplication5
{
    public class WcfDataService1 : DataService< GroupEntities >
    {
        // このメソッドは、サービス全体のポリシーを初期化するために、1 度だけ呼び出されます。
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }
}

—————————————————————————————–

プログラムを実行すると、次のような画面が表示され、すでにWebサービスとしてAtomPabの形式でデータの提供が開始されることを確認できます。

image

これでWebサービス側のシステムが構築できたことになります。
このサービスは起動したまま、次の作業に入ります。
サービスが起動していないと以降の作業はうまく動作しませんので気をつけてください。

もうひとつVisual Web Developerを起動し、MVCのプロジェクトを作成します。
後で記述するサンプルコードと名前空間を合わせるため、「MyService」という名前でプロジェクトを作成してください。
ソリューションエクスプローラー上で参照設定を右クリックし、サービス参照の追加を選択します。
アドレス欄にWebサービスのURLをコピーし、名前空間を「GroupServiceReference」としてOKボタンをクリックします。

image

コントローラーとしてMemberController.cs を作成し、以下のコードを記述します。

—————————————————————————————–

using System;
using System.Linq;
using System.Web.Mvc;
using System.Data.Entity.Infrastructure;
using MyService.GroupServiceReference;

namespace MyServiceControllers
{
    public class MemberController : Controller
    {
        private GroupEntities db = new GroupEntities(new Uri("WebサービスのURLをコピー"));

        public ViewResult Index()
        {
            var member = db.Members.Expand("Categories");
            return View(member.ToList());
        }

        public ViewResult Details(int id)
        {
            Members member = db.Members.Where(m => m.ID == id).First();
            return View(member);
        }

        public ActionResult Create()
        {
            ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name");
            return View();
        }

        [HttpPost]
        public ActionResult Create(Members member)
        {
            if (ModelState.IsValid)
            {
                db.AddToMembers(member);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name", member.CategoryID);
            return View(member);
        }

        public ActionResult Edit(int id)
        {
            Members member = db.Members.Where(m => m.ID == id).First();

            ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name", member.CategoryID);
            return View(member);
        }

        [HttpPost]
        public ActionResult Edit(Members member)
        {
            if (ModelState.IsValid)
            {
                db.AttachTo("Members", member);
                db.UpdateObject(member);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name", member.CategoryID);
            return View(member);
        }

        public ActionResult Delete(int id)
        {
            Members member = db.Members.Where(m => m.ID == id).First();
            return View(member);
        }

        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {
            Members member = db.Members.Where(m => m.ID == id).First();
            db.DeleteObject(member);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
}

—————————————————————————————–

このプログラムはCodeFirstのサンプルでスキャッフォールドで生成したコードをもとにしています。
CodeFirstではDbContextを利用していましたが、WCF Data Servicesを参照したこのプログラムではDataServiceContextを利用することになります。
基底となるクラスが用意するメソッドが異なるため、おおきな流れはスキャッフォールドで生成したコードから変えていませんが、利用しているメソッドは異なっている点に注意してください。

次にViewを作成していきます。
まずViewフォルダにMemberフォルダを追加し、その中に下記のコードと追加していきます。

・Create.cshtml

—————————————————————————————–

@model MyService.GroupServiceReference.Members

@{
    ViewBag.Title = "Create";
}

<h2>Create</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>Member</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.Mail)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Mail)
            @Html.ValidationMessageFor(model => model.Mail)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.CategoryID, "Category")
        </div>
        <div class="editor-field">
            @Html.DropDownList("CategoryID", String.Empty)
            @Html.ValidationMessageFor(model => model.CategoryID)
        </div>

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

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

—————————————————————————————–

・Delete.cshtml

—————————————————————————————–

@model MyService.GroupServiceReference.Members

@{
    ViewBag.Title = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<fieldset>
    <legend>Member</legend>

    <div class="display-label">Name</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Name)
    </div>

    <div class="display-label">Mail</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Mail)
    </div>

    <div class="display-label">Category</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Categories.Name)
    </div>

    <div class="display-label">BirthDay</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.BirthDay)
    </div>

</fieldset>
@using (Html.BeginForm()) {
    <p>
        <input type="submit" value="Delete" /> |
        @Html.ActionLink("Back to List", "Index")
    </p>
}

—————————————————————————————–

・Details.cshtml

—————————————————————————————–

@model MyService.GroupServiceReference.Members

@{
    ViewBag.Title = "Details";
}

<h2>Details</h2>

<fieldset>
    <legend>Member</legend>

    <div class="display-label">Name</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Name)
    </div>

    <div class="display-label">Mail</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Mail)
    </div>

    <div class="display-label">Category</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Category.Name)
    </div>

    <div class="display-label">BirthDay</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.BirthDay)
    </div>

    <div class="display-label">Age</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Age)
    </div>
</fieldset>
<p>
    @Html.ActionLink("Edit", "Edit", new { id=Model.ID }) |
    @Html.ActionLink("Back to List", "Index")
</p>

—————————————————————————————–

・Edit.cshtml

—————————————————————————————–

@model MyService.GroupServiceReference.Members
@{
    ViewBag.Title = "Edit";
}
<h2>
    Edit</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>Member</legend>
        @Html.HiddenFor(model => model.ID)
        <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.Mail)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Mail)
            @Html.ValidationMessageFor(model => model.Mail)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.CategoryID, "Category")
        </div>
        <div class="editor-field">
            @Html.DropDownList("CategoryID", String.Empty)
            @Html.ValidationMessageFor(model => model.CategoryID)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.BirthDay)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.BirthDay)
            @Html.ValidationMessageFor(model => model.BirthDay)
        </div>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

—————————————————————————————–

・Index.cshtml

—————————————————————————————–

@model IEnumerable<MyService.GroupServiceReference.Members>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th>
            Name
        </th>
        <th>
            Mail
        </th>
        <th>
            Category
        </th>
        <th>
            BirthDay
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Mail)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Categories.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.BirthDay)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
            @Html.ActionLink("Details", "Details", new { id=item.ID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.ID })
        </td>
    </tr>
}

</table>

—————————————————————————————–

Viewのコードはスキャッフォールドで作成したコードからmodelの名前空間を変更し、また、計算項目として追加していたAgeがデータベース内にないことからその表示部分を削っただけの修正しかしていません。
これでとりあえず動くサンプルが完成しました。
データの追加/削除/変更が自由に行え、それがWebサービスをとおしてデータベース内に反映されることを確認してみましょう。

少しさわっていると、バリデーションが行われないことに気づくと思います。
最初のサンプルと同様のバリデーションを組み込むにはMetaDataを利用するのが簡単です。
Modelsフォルダに以下のコードを追加します。

・MetaClass.cs

—————————————————————————————–

using System.ComponentModel.DataAnnotations;

namespace MyService.GroupServiceReference
{
    [MetadataType(typeof(MyMetaData))]
    public partial class Members
    {
    }

    public class MyMetaData
    {
        [Required(ErrorMessage="名前は必須入力です")]
        public object Name { get; set; }
        [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "メールが正しくありません")]
        public object Mail { get; set; }

    }
}

—————————————————————————————–

これでバリデーションが実行されるようになります。
MetaDataを利用する際に一番注意が必要なことは上記のプログラムでいうとpartialクラスとして定義しているMembersの名前空間を含めた定義になります。
この部分がバリデーションをあてようとしている元のクラスと正しく一致しないと設定が反映されません。
やっかいなことに元のクラスと一致していない場合でもコンパイルエラーにはならないため、なぜ設定が反映されないのか悩んで悩んで、結局このクラス定義にミスがあった、ということがよくあります。

 

○ふりかえりのまとめ

 

ASP.NET MVC では、.NET Frameworkが用意しているデータ連携の機能すべてを利用できます。
CodeFirstベースでデータベースを利用することもできますし、このサンプルのようにODataベースのWebサービスを簡単に利用することもできます。
今回は説明していませんが、XMLベースでのデータ配信を取り込んで利用するのも簡単です。
そして、多少利用できるメソッドの違いはありますが、全体的な流れはModel側のデータがなんであっても変わりません。

ASP.NET MVC 3ではスキャッフォールドで生成されるコードがかなり参考にできるコードになったので、ぜひ一度自分で試して、中身を納得できるまでよく解析してみると良いと思います。

なおセッション中では特に言及しませんでしたが、このBlogの内容はすべて無償版であるVisual Web Developerで動作させ、確認を行っています。
WebPIからVisual Web Developerをインストールすると必要な環境はすべて整いますので、ぜひインストールして試してみてください。

ASP.NET MVCはWebアプリケーションの開発において非常に魅力的な技術です。
CodeFirstの登場により、開発者にとってはその魅力がより増した、と感じています。
これらの技術に触れる最初の手がかりとして、このBlog記事が役に立つといいな、と思っています。

mvcConf @:Japan ふりかえり 5: 同時実行制御

2011年6月21日 1件のコメント

スライド15

CodeFirstとASP.NET MVCとの組み合わせでは同時実行制御も簡単に実装することができます。
ふりかえり1で作成したサンプルプログラムを同時実行制御のエラーチェックができるように修正してみましょう。

まず、POCOのクラスに以下のようにTimestamp属性を付加した制御用の項目を追加します

—————————————————————————————————–

public class Member
{
    public int ID { get; set; }
    [Required(ErrorMessage="名前は必須入力です")]
    public string Name { get; set; }
    [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "メールが正しくありません")]
    public string Mail { get; set; }
    public int CategoryID { get; set; }
    public DateTime? BirthDay { get; set; }
    public int? Age
    {
        get
        {
            int? age = null;
            if (BirthDay != null)
            {
                age = DateTime.Now.Year – BirthDay.Value.Year;
                if (DateTime.Now < BirthDay.Value.AddDays(-1).AddYears(age.Value)) age -= 1;
            }
            return age;
        }
    }
    [Timestamp]
    public Byte[] Timestamp { get; set; }

    public virtual Category Category { get; set; }

}

—————————————————————————————————–

これにより、データベースにTimestamp項目が追加されます。
データの型がByte[]型であることに注意しておきましょう。

スキャッフォールドで生成されるコントローラー、ビューはTimestamp項目がないときと同じです。
そのままだと更新時にエラーとなるので、Editビュー(Edit.cshtml)にhiddenとしてTimestamp項目を追加します。

—————————————————————————————————–

@model MyDal.Member

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</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>Member</legend>

        @Html.HiddenFor(model => model.ID)

        <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.Mail)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Mail)
            @Html.ValidationMessageFor(model => model.Mail)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.CategoryID, "Category")
        </div>
        <div class="editor-field">
            @Html.DropDownList("CategoryID", String.Empty)
            @Html.ValidationMessageFor(model => model.CategoryID)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.BirthDay)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.BirthDay)
            @Html.ValidationMessageFor(model => model.BirthDay)
        </div>
        @Html.HiddenFor(Model => Model.Timestamp)
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

—————————————————————————————————–

同時実行制御のエラーが発生したときは例外としてDbUpdateConcurrencyExceptionがthrowされるので、このエラーをキャッチします。
実際のエラー時の対処はシステムの仕様によって異なります。
ここではとりあえずエラー時のメッセージを表示するようにだけEditアクションに手を入れます。

—————————————————————————————————–

[HttpPost]
public ActionResult Edit(Member member)
{
   try
    {
        if (ModelState.IsValid)
        {
            db.Entry(member).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
    catch (DbUpdateConcurrencyException e)
    {
        ModelState.AddModelError(string.Empty, e.Message);
    }
    ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "Name", member.CategoryID);
    return View(member);
}

—————————————————————————————————–

このようにサンプルプログラムを修正して実行します。
ブラウザを2つ起動して、片方でEdit画面を表示させた状態でもう1つのブラウザから同じデータを更新します。
その後、先にEdit画面を表示していたほうでデータを更新しようとすると以下のようなエラーが表示されます。

image

このサンプルで基本的な動作は理解していただけると思います。
より実際の動きに近い形の同時実行制御のサンプルがこちらで公開されています。
ぜひ参考にしてみてください。

mvcConf @:Japan ふりかえり 4: バリデーション(入力値チェック)

2011年6月17日 1件のコメント

スライド14

ASP.NET MVCでは入力値のチェックに属性の設定によるバリデーションが利用できます。

Modelとして作成したクラスのプロパティに属性を設定するだけで以下のようなエラーチェックが可能です。

image_thumb9

ここではNameプロパティに

[Required(ErrorMessage="名前は必須入力です")]

Mailプロパティに

[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "メールが正しくありません")]

という属性を設定しています。

スキャッフォールディングで作成したビューでは、標準で上記の属性によるバリデーションがJQuery.Validateが利用され、クライアント側でチェックが行われるようになっています。
ビュー内に以下の記述がありますが、この記述をはずすとクライアント側でのチェックが行われなくなり、一度サーバーにリクエストをなげて、そこでエラーチェックが行われるようになります。

—————————————————————————————————–

<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>

—————————————————————————————————–

属性によるバリデーションは基本的に1つの項目(プロパティ)に対してチェックを行うものです。
複数の項目の値によってチェックを行いたい、という場合には属性ではチェックできません。

複数の項目間の値チェックを行うためにModelクラスに IValidatableObject を実装する、という方法が使えます。
Memberクラスを次のように修正します。

—————————————————————————————————–

public class Member : IValidatableObject
{
    public int ID { get; set; }
    [Required]
    public string Name { get; set; }
    [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "メールが正しくありません")]
    public string Mail { get; set; }
    public int CategoryID { get; set; }
    public DateTime? BirthDay { get; set; }
    public int? Age
    {
        get
        {
            int? age = null;
            if (BirthDay != null)
            {
                age = DateTime.Now.Year – BirthDay.Value.Year;
                if (DateTime.Now < BirthDay.Value.AddDays(-1).AddYears(age.Value)) age -= 1;
            }
            return age;
        }
    }

    public virtual Category Category { get; set; }

   public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Age >= 20 && Mail == null)
            yield return new ValidationResult("20歳以上メールアドレス必須");
    }
}

—————————————————————————————————–

IValidatableObjectをMemberクラスの基底クラスとして設定し、Validateメソッドを実装しています。
Validateメソッド内ではエラーチェックを行い、エラーがある場合にyieldでValidationResultを返しています。
これにより、年齢が20歳以上になる場合にMailプロパティに値が入力されていないと、以下のようにエラーが表示されます。

image

バリデーションの実装方法にはほかにもいくつかの方法があります。
たとえばコントロール内でModelStateクラスにたいしてAddModelErrorメソッドを利用して直接エラー状態を設定する、といったことも可能です。

バリデーションの使い分けに関しては適材適所という考え方もありますが、むしろシステム全体として利用方針を決めておくべきでしょう。
バリデーションだけでなく、ASP.NET MVCは自由度が高いために同じことを実現するのに複数の実装方法が選択できる、ということが多いです。
このような場合、システムとしての実装方法の利用方針をあらかじめ決定しておくことで、統一感がありメンテナンスのしやすいシステムが構築できると思います。

カテゴリー:ASP.NET MVC 3, mvcConf @:Japan

mvcConf @:Japan ふりかえり 3: MVC 3 のスキャッフォールディング

2011年6月16日 1件のコメント

スライド10

ASP.NET MVC 3では、スキャッフォールドの形式が変わり、コントローラーの追加からビューの作成までを一度に行うことができるようになりました。

image_thumb1

オプションのテンプレートで上記のように設定すると、指定したコントローラーとIndex.cshtml、Create.cshtml、Details.cshtml、Edit.cshtml、Delete.cshtmlの5つのビューを作成してくれます。

データコンテキストクラスにはCodeFirstで作成するDbContextを継承したクラス以外に、Entity Frameworkのデータベースファースト、モデルファーストで仕様するObjectContextを継承したクラスが利用できます。
DbContextを利用している場合とObjectContextを利用している場合ではコントロールクラス内に生成されるプログラムが異なっており、それぞれにあわせて適切なメソッドを利用するようになっています。

このことからもわかるように、MVC 3 のスキャッフォールディングはデータの操作のサンプルとして有用です。
どのようなメソッドでデータ操作を行っているか確認してみましょう。

なお、テストを構築するためにリポジトリパターンでコントローラーを作成するサンプルがほしい、という場合はNuGetからインストールできるMvcScaffoldingを試してみてください。
リポジトリパターンを利用したコントローラーだけでなく、そのテストメソッドまでを生成することができます。
リポジトリパターンを利用しない場合でも、MvcScaffoldingで生成されるコントローラー、ビューは標準のスキャッフォールディングで作成されるプログラムと少し異なりますので、どこが違うかを見てみるのも勉強になると思います。

スライド11

DbContextでは単一のデータを取得するのにFindメソッドが使えます。
従来だとSingleメソッド等を使っていた部分なので、引数に m => m.ID == id といったラムダ式を記述する必要がありました。
少しですがタイプ数が改善されています。

複数のデータを取得する場合、特にナビゲーションプロパティに含まれる値を取得するにはIncludeメソッドを利用します。
ちなみに、ナビゲーションプロパティにvirtualの設定をしてレイジーローディングを有効にしている場合はIncludeを利用しなくてもプログラム自体は動作します。
ただしこの場合、必要になった時点でデータの取得が行われるため、複数回の問い合わせがデータベースに対して行われることになります。
Includeを利用することでデータベースへの問い合わせ(コストが高い)の回数を減らすことができます。

スライド12

データ更新時のEntryメソッドもDbContextで新しく追加されたメソッドです。
従来ですとAttachした後に更新状態を変更する、という2度の手間が必要だった作業を1行に簡潔に記述できるようになっています。

なお、CodeFirstではDbSetを用いることでデータの状態変更を追跡することができるようになっています。
これはWPFアプリケーション等クライアント側でデータの状態(ステート)を保持して操作する場合には大変有効な機能です。
ただ、Webアプリケーションは基本がステートレスとなり、リクエストを超えてデータの状態を保持することは逆に面倒になります。
上記のようにリクエストにより与えられたデータをAttachし、データ更新を行う、というのが一般的な手順となります。

スライド13

MVC 3のスキャッフォールディングで個人的に一番感銘を受けたのがDropDownListを利用するためのプログラムの生成の部分です。
サンプルプログラムで作成したような1対多の状態を作成していると、自動的にDropDownListによるマスター項目の選択ができるようになっています。
これを実現するのに、上記の2行のコードしか必要ではありません。
ものすごくシンプルです。

この書き方は参考になりますが、なかの仕組みはわかりにくいかと思います。
なぜこれで動くのか、というのを掘り下げてみると、いろいろと応用がきくのではないかと思います。

mvcConf @:Japan ふりかえり 2: CodeFirstでのデータベース定義の概要

2011年6月16日 1件のコメント

スライド5

CodeFirst ではクラスを定義すればそれがそのままテーブルの設計となります。
データベースにあまり強くないプログラマにとってこれはうれしいことのようですが、実際にはそうでもありません。
どのようにテーブル設計をするのがよいのかよく考えたうえでクラスの定義を行う必要があります。
ただ、そのクラス定義を必要があれば簡単に変更し、すぐにデータベースに反映していくことができる、という意味で、やはりCodeFirstはプログラマにとって大変魅力的なものとなります。

このクラス定義=テーブル設定においては、CoCの原則が適用されます。
たとえば、

  • ID、またはクラス名+IDという名前のプロパティは自動的に主キーとなる
  • ナビゲーションプロパティを定義すると、それにあわせてリレーションが設定される
  • リレーションの相手となるクラス名+IDという名前のプロパティは外部キーとなる
  • ナビゲーションプロパティに virtual を指定することでレイジーローディングが有効になる
  • 文字列型のプロパティはnull許容の設定になる
  • 値型のプロパティはnull否許容となる。nullable型(?をつける)とするとnull許容の設定になる
  • getメソッドしかもたないプロパティ項目はデータベース上に作成されず、計算で表示される項目となる

といった規約が利用できます。

これらの規約にたいし、属性を設定することでデータベース上の設定を変更することができます。
たとえば [Required] を付加することで文字列型のプロパティでもnull否許容とすることができます。
また、[MaxLength] により、データベース上の文字の最大値を設定することができます。

属性での設定以上に細かい定義をしたい場合はFluent APIと呼ばれるAPIを利用して設定を行うことになります。

スライド6

MemberクラスのNameプロパティに [Required] の設定を行うのと同じことをFluent APIで記述すると上記のようになります。
注意が必要なのは、Fluent APIを利用することでデータベース上の定義は変更することができますが、これはASP.NET MVCとしての動作にはそのままでは影響を与えない、という点です。
データベース上でName項目はnull否許容となるので、データの更新時に値が入っていなければエラーとなります。
ただし、[Required] という属性定義を利用した場合、あとで述べるASP.NET MVCのバリデーションの機能が働くため、エラーとなるまえに利用者にメッセージを表示して注意を促すことができます。

なお、Fluent APIを利用すると細かい定義を設定することが可能です。
特にすでに存在しているデータベースとPOCOクラスを関連づけたい、という場合はFluent APIを利用しないといけない場面があるでしょう。

スライド7

単なるクラス定義を実際にデータベース定義に結びつけるのがDbContextを継承したクラスでのDBSet<T>型のプロパティ定義です。
テーブル名はここで指定されたプロパティの名称になりますが、プロパティ名を単数形で指定してもテーブル名は複数形になるようです。
なお、テーブル名はPOCOクラスに[Table]メソッドを付加することで指定することが可能ですし、Fluent APIを用いて指定することも可能です。

スライド8

データベースを作成する場所としては、DbContextを継承したクラスの名称と同じ名称が設定されている接続文字列が探されます。
もしそのような接続文字列が存在しない場合、CodeFirstはSQL ServerのSQLExpressインスタンス(VisualStudioをインストールする際、デフォルトでSQL Server Expressのインスタンスとして作成される)の中にデータベースを作成します。
このとき作成されるデータベースの名前はnamespaceを含めたDbContextを継承したクラス名となります。

接続文字列がみつかった場合はその接続文字列に指定されている場所、名前でデータベースが作成されます。
このときSQLCe4を指定することもできます。

スライド9

デフォルトではCodeFirstによるデータベースの生成は一度しか行われず、定義の変更も行われません。
このため、POCOのクラス定義を変更すると、エラーが発生します。
Initializerの基底となるクラスを変更し、アプリケーションの実行時にInitializerを設定することでこの動作を変更することができます。

Initializerの基底となることができるクラスには以下のものがあります。

  • CreateDatabaseIfNotExists
  • DropCreateDatabaseIfModelChanges
  • DropCreateDatabaseAlways

それぞれの役割は名前のとおりです。

また、InitializerクラスのSeedメソッドをオーバーライドすることでデータベースの初期値を設定することができます。
データベースの作成時に常に同じデータをセットすることができるため、テスト環境の構築には有効だと思われます。
また、マスターデータ等初期状態で必須となるデータをデータベースに設定するのにも使えると思います。

mvcConf @:Japan セッションスライド

mvcConf @:Japan で使われた各セッションのスライドが以下に公開されています。

http://www.slideshare.net/mvcjpn/presentations

動画の公開はまた後日になります。

カテゴリー:mvcConf @:Japan