ASP.NET MVC
によるWebアプリケーション開発 - 基礎編ASP.NET MVC
の概要MVC(Model View Controller モデル・ビュー・コントローラ)は、ユーザーインタフェースをもつアプリケーションソフトウェアを実装するためのデザインパターンである。 アプリケーションソフトウェアの内部データを、ユーザーが直接参照・編集する情報から分離する。そのためにアプリケーションソフトウェアを以下の3つの部分に分割する。
- model: アプリケーションデータ、ビジネスルール、ロジック、関数
- view: グラフや図などの任意の情報表現
- controller: 入力を受け取りmodelとviewへの命令に変換する
元々は Smalltalk
における ウィンドウプログラム開発の設計指針として生まれたものです。
ASP.NET MVC
は MVCのデザインパターンで ASP.NET Webアプリケーション を開発するにあたって
必須であったり、便利な機能を提供するフレームワークです。
参考: Getting Started with Entity Framework 6 Code First using MVC 5
NuGetを使用して、最新のEntity Framework
をインストールします。
Entity Framework (EF) は、.NET 開発者がドメイン固有のオブジェクトを使用してリレーショナル データを処理できるようにするオブジェクト リレーショナル マッパーです。 開発者が通常、記述する必要のあるデータ アクセス コードがほとんど不要になります。
Microsoft謹製のものが EntityFramework
。
軽量で、最も利用されていると思われるのが dapper。
Visual Studio用のパッケージ管理システム。 rubyのgemやpythonのpipのようなツール。
コマンドラインやPowerShellからも利用可能ですが、今回は Visual Studio が用意している GUI で操作します。
EntityFramework
を検索し、「インストール」をクリックします。EntityFramework
にチェックが入っている事を確認します。データ構造を表現する POCO
(Plain Old Clr Object: 特別なクラスやインターフェイスを継承していないクラス(のオブジェクト)) と
POCOを管理する Contextクラスを定義することで、Entity Frameworkが必要なテーブルを生成します。
(Databaseの操作は必要ありません。)
コード ファースト開発は Entity Framework 4.1
から提供された機能です。
詳細については Entity Framework (EF) の概要 を参照してください。
Todoクラス を作成していきます。
Models
を右クリックし、「追加」→「クラス」を選択します。Todo.cs
とし、「追加」をクリックします。Todo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;
namespace WebApplication1.Models
{
/// <summary>
/// ToDoモデル
/// </summary>
public class Todo
{
public int id { get; set; }
[DisplayName("概要")]
public string summary { get; set; }
[DisplayName("詳細")]
public string detail { get; set; }
[DisplayName("期限")]
public DateTime limit { get; set; }
[DisplayName("完了")]
public bool done { get; set; }
}
}
モデルの定義にあたって抑えるポイントは以下の3点です。
id
(またはId
) という名前がデフォルトです。<ClassName>Id
という名前でもOKです。[DisplayName("概要")]
など、プロパティの上に記述されたカギカッコで括られた記述は
属性(Attributes あるいは Annotation)と呼ばれるものです。
Viewで項目が表示される時に、DisplayName
に設定した文言が項目名として使用されます。
DisplayName
以外にも、 NOT NULL制約を表す Required
や
データの最大長を示す MaxLength
などがあります。
Contextクラスは先ほど作成した POCO とデータベースを繋げる役割を果たします。
データの取得、更新などの操作はすべてこのContextクラスを使用して行います。
Models
を右クリックし、「追加」→「クラス」を選択します。TodoesContext.cs
とし、「追加」をクリックします。TodoesContext.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace WebApplication1.Models
{
public class TodoesContext : DbContext
{
public DbSet<Todo> Todoes { get; set; }
}
}
Contextクラスは DbContext
クラスを継承します。
Todoのコレクションを管理するので、Todoes
というDbSet<Todo>
を定義します。<Todo>
はDbSet
に格納するクラスを表します。Todoes
にはデータベースから取得した Todo
のコレクション (配列のようなもの) です。
Views
を右クリックし、「追加」→「MVC 5 レイアウト ページ (Razor)」 を選択します。_LayoutPage1.cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@ViewBag.Title</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
@RenderBody()
</div>
</body>
</html>
Bootstrap
を読み込むように指定します。
また、div
にclass="container"
の指定を追加します。
@RenderBody()
の位置に、後ほど作成する各Viewの内容がセットされます。
Razor
は HTMLにC#/VBのコードを埋め込むための仕組み (ビューエンジン) です。@
で始まる箇所がサーバーサイドで実行され、クライアントに生成したHTMLが返されます。
BootstrapはWebサイトやWebアプリケーションを作成するフリーソフトウェアツール集である。 タイポグラフィ、フォーム、ボタン、ナビゲーション、その他構成要素やJavaScript用拡張などがHTML及びCSSベースのデザインテンプレートとして用意されている。
スキャフォールディング(Scaffolding、「骨組み」「足場」という意味)によって、 Create(作成)、Read(参照)、Update(更新)、Delete(削除)のような定型的なコードの骨組みを自動生成できます。
Controllers
を右クリックし、「追加」→「コントローラー」を選択します。Todo
TodoesContext
~/Views/_LayoutPage1.cshtml
TodoesController
以下のファイルが生成されます。
後ほど、生成されたファイルの中身について解説します。
App_Start/RouteConfig.cs
を修正し、デフォルトページをToDoの一覧ページに変更します。
routes.MapRoute
メソッドのdefaults
引数にデフォルトの設定を定義します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace WebApplication1
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Todoes", action = "Index", id = UrlParameter.Optional }
);
}
}
}
ルーティングとは、リクエストURIに応じて処理を受け渡し先を決定することを言います。
ASP.NET MVC
ではクライアントからの要求を受け取ると、RouteConfig.cs
の内容を元に
呼び出すべきコントローラー/アクションを決定します。
routes.MapRoute
メソッドのurl
引数がルーティングの定義です。
例えば http://localhost/Todoes/Details/3
というリクエストが来た場合、
url
の定義にしたがってTodoesController
のDetails
メソッドにid=3
を引数に与えて呼び出します。
また、defaults
でデフォルトのコントローラー、アクションを指定しているので、http://localhost/
という
リクエストが来た場合は http://localhost/Todoes/Index
というリクエストとして処理されます。
F5キーを押して、デバッグ実行を行います。 ブラウザが起動し、一覧ページが表示されます。
ToDoの追加、変更、削除が行えることを確認します。
Controller
で終わる必要があります。Controller
クラスを継承します。(Controllerクラスを継承した独自のControllerクラスでも構いません)ActionResult
オブジェクトコードの細部について、順に解説します。
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
public class TodoesController : Controller
{
private TodoesContext db = new TodoesContext();
コントローラーのプライベート変数に Models に作成したTodoesContext
を保持しています。
データベースへのアクセスはTodoesContext
を介して行います。
// GET: Todoes
public ActionResult Index()
{
return View(db.Todoes.ToList());
}
Index
メソッドの定義です。 View メソッドは
アクションメソッドに対応した View を元に ViewResult
( ActionResult
を継承した、Viewを表示するためのクラス)を返します。
ToList()
は Todo
の全要素を取得し、リストとして返すメソッドです。
ここでは、views/Index.cshtml
に すべてのTodoをListに格納したオブジェクトを渡して生成される結果を返しています。
アクションメソッドは、戻り値となる ActionResult
の派生オブジェクトを介してアクションの結果 (その後に行うべき挙動) を通知します。
代表的なActionResultの派生クラスは以下の通りです。
クラス名 | ヘルパーメソッド | 概要 |
---|---|---|
ViewResult | View | アクションメソッドに対応したViewを出力 |
RedirectToRouteResult | RedirectToAction | 指定のアクションメソッドに処理を転送 |
ContentResult | Content | 指定されたテキストを出力 |
FileContentResult | File | 指定されたファイルを出力 |
JsonResult | Json | 指定されたオブジェクトをJSON形式で出力 |
HttpNotFoundResult | HttpNotFound | 404ページを出力 |
EmptyResult | - | なにも行わない |
// GET: Todoes/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Todo todo = db.Todoes.Find(id);
if (todo == null)
{
return HttpNotFound();
}
return View(todo);
}
Details
メソッドの定義です。
int?
は int
の Nullable型 です。
通常の int
は null
を設定できませんが、Nullable型は null
が許容されます。
RouteConfig.cs
の defaults
の定義により、id
は省略可能です。id
が省略された場合、Details
の引数 id
には null
が設定されます。
id
が null
の場合は要求されたURLが正しくないので、
BadRequest
を返しています。
Todo todo = db.Todoes.Find(id);
はデータベースから id
が一致するデータをひとつ取り出します。
一致するデータが存在しない場合は Find
から null
が返ってくるので、
NotFound
を返しています。
一致するデータが存在すれば、それをView
にセットして返します。
// GET: Todoes/Create
public ActionResult Create()
{
return View();
}
新規登録時は View
を表示するだけです。
// POST: Todoes/Create
// 過多ポスティング攻撃を防止するには、バインド先とする特定のプロパティを有効にしてください。
// 詳細については、http://go.microsoft.com/fwlink/?LinkId=317598 を参照してください。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,summary,detail,limit,done")] Todo todo)
{
if (ModelState.IsValid)
{
db.Todoes.Add(todo);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(todo);
}
[HttpPost]
は http
の POST
メソッドでリクエストがあった場合に呼び出されるアクションメソッドを表す属性です。
ValidateAntiForgeryToken
は クロスサイト・リクエスト・フォージェリ攻撃 を防ぐための記述です。
クロスサイトリクエストフォージェリ(Cross site request forgeries、略記:CSRF、またはXSRF)は、WWW における攻撃手法のひとつである。 具体的な被害としては、掲示板に意図しない書き込みをさせられたり、オンラインショップで買い物をさせられたりするなどが挙げられる。 クロスサイトリクエストフォージェリ
Bind は POSTされたデータを
Todo
モデルに紐付けます。
ModelState.IsValid
は入力チェックがOKかどうかを判定します。
例えば、DateTime
型の limit
に日付以外の値がセットされている場合は IsValid
が false
となるため
登録処理が行われません。
更新処理の中身はそのままの意味ですが、
Add
でPOSTされたデータを DbSet
に登録しSaveChanges
で DbSet
の変更をデータベースに反映します。RedirectToAction
は 指定されたアクションメソッドに転送します。ASP.NET MVCでは、クライアントからの入力値を自動的にモデルに割り当てる モデルバインド という機能があります。
上記の Create
は以下のように書くこともできます。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Todo todo)
こうすることで、クライアントからPOSTされてきたデータのkey名を見てモデルに自動的に割り当ててくれるのですが、 上記の記述ではセキュリティホールの原因となる場合もあります。
例えば、以下の様な Account
クラスがあった場合を考えます。
public class Account
{
public int id { get; set; }
public string mail { get; set; }
public string password { get; set; }
public string role { get; set; }
}
ここで、 role
(権限) は管理者のみが設定でき、エンドユーザーからは勝手に編集できないものとします。
(以下の様なViewのイメージ)
<html>
<body>
@using (Html.BeginForm())
{
@Html.HiddenFor(model => model.id)
@Html.EditorFor(model => model.mail)
@Html.EditorFor(model => model.password)
}
</body>
Create.cshtml から POSTされた際に実行されるアクションメソッドを以下のように実装したとします。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Account account)
{
if (ModelState.IsValid)
{
db.Accounts.Add(account);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(account);
}
Create.cshtmlからPOSTされた場合は id, mail, password のみがセットされてきます。Add
では新しいレコードとしてDBに登録されるので、id は自動的に採番されますので、
プログラマーが期待している動作としては、 POSTされてきた mail と password がDBに登録される
という動作だと思います。
しかし、悪意あるユーザーがPOSTデータを改ざんし、role値を含んだデータを送信すると、
Create
メソッドはその値をモデルに割り当ててしまい、結果、意図せず role
に値が設定されてしまいます。
このような攻撃を 過多ポスティング攻撃 といいます。
自動生成された Create
メソッドのように Bind
を使用して
モデルバインドするプロパティを明示することにより、過多ポスティング攻撃を防止します。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,mail,password")] Account account)
// GET: Todoes/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Todo todo = db.Todoes.Find(id);
if (todo == null)
{
return HttpNotFound();
}
return View(todo);
}
// POST: Todoes/Edit/5
// 過多ポスティング攻撃を防止するには、バインド先とする特定のプロパティを有効にしてください。
// 詳細については、http://go.microsoft.com/fwlink/?LinkId=317598 を参照してください。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "id,summary,detail,limit,done")] Todo todo)
{
if (ModelState.IsValid)
{
db.Entry(todo).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(todo);
}
Edit
メソッドは Details
と Create
の処理を組み合わせた内容です。
// GET: Todoes/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Todo todo = db.Todoes.Find(id);
if (todo == null)
{
return HttpNotFound();
}
return View(todo);
}
// POST: Todoes/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Todo todo = db.Todoes.Find(id);
db.Todoes.Remove(todo);
db.SaveChanges();
return RedirectToAction("Index");
}
削除時の流れは以下のようになります。
Delete/{id}
にリクエスト (Get)Delete
メソッドから Delete.cshtml
が返されるDelete
ボタンをクリック -> Delete
にPOSTされるIndex
を返す protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
Dispose
は終了処理です。
db.Dispose()
で保持している Context
を開放しています。
まず、Index.cshtml
の内容について順に解説していきます。
@model IEnumerable<WebApplication1.Models.Todo>
コントローラーのアクションメソッドにて View(db.Todoes.ToList())
で引き渡されたデータを受け取るには、
@modelディレクティブ
を使用します。IEnumerable<WebApplication1.Models.Todo>
は Todo
クラスのリストを表します。
@
から始まる文はRazorの コードナゲット としてサーバーサイドで処理されます。
HTML内にRazorの式としてではなく、@
をそのまま表示したい場合は
@@
とすることでエスケープして表示されます。
また、コメントを記載する場合は @* ... *@
と記述します。
HTMLのコメントと異なり、サーバーサイドでコメントとして処理されるため、ブラウザでソースを表示しても
@* ... *@
は出力されません。
@{
ViewBag.Title = "Index";
Layout = "~/Views/_LayoutPage1.cshtml";
}
@{ ... }
の中にC#のコードを書くと、サーバーサイドで実行されます。
ここでは Title
の設定とレイアウトの指定をしています。
ViewBag
はコントローラとビューの間でデータをやりとりする際に使用します。
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
@Html
から始まるメソッドは HTMLヘルパー と呼ばれ、HTMLの生成をカプセル化します。
@Html.ActionLink()
はリンクを生成します。
上記の場合は TodoesController
のCreate
アクションへのリンクが生成されます。
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.summary)
</th>
<th>
@Html.DisplayNameFor(model => model.detail)
</th>
<th>
@Html.DisplayNameFor(model => model.limit)
</th>
<th>
@Html.DisplayNameFor(model => model.done)
</th>
<th></th>
</tr>
@Html.DisplayNameFor()
はモデルの定義に応じて、プロパティの表示名を表示します。
<label>
タグとして出力したい場合は@Html.LabelFor()
を使用すると、表示名をラベルに整形して出力します。
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.summary)
</td>
<td>
@Html.DisplayFor(modelItem => item.detail)
</td>
<td>
@Html.DisplayFor(modelItem => item.limit)
</td>
<td>
@Html.DisplayFor(modelItem => item.done)
</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>
制御構文内は自動的にコードブロックとして認識されます。
foreach
だけでなく、if
や switch
などC#の制御構文がそのまま使用できます。
@Html.DisplayFor()
はモデル定義に応じて出力される内容が変わります。
DisplayFor
はプロパティの内容を表示するだけなので、チェックボックスはreadonlyとなります。
つづいて、編集画面の例として Edit.cshtml
の解説をしていきます。
@model WebApplication1.Models.Todo
@{
ViewBag.Title = "Edit";
Layout = "~/Views/_LayoutPage1.cshtml";
}
<h2>Edit</h2>
ここまでは Index.cshtml
と同様です。
コントローラでは 1つの項目を取得して Viewにセットされてきますので、
@model
では単純に Todo
モデルを受け取るように指定しています。
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@using (Html.BeginForm()) { ... }
はブロック内を <form>
で括ります。
@Html.AntiForgeryToken()
は CSRF対策の <hidden>
タグを埋め込みます。
<div class="form-horizontal">
<h4>Todo</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.id)
<div class="form-group">
@Html.LabelFor(model => model.summary, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.summary, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.summary, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.detail, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.detail, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.detail, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.limit, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.limit, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.limit, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.done, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
@Html.EditorFor(model => model.done)
@Html.ValidationMessageFor(model => model.done, "", new { @class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
HTMLヘルパーで入力フォームを生成しています。
@Html.ValidationSummary()
: 入力検証(validation)の結果で返されるメッセージが入ります。@Html.HiddenFor()
: hidden
タグを生成します。@Html.LabelFor()
: label
タグを生成します。@Html.EditorFor()
: モデル定義に応じたタグを生成します。@Html.ValidationMessageFor()
: 各エディタでのvalidationのエラーメッセージが表示されます。<div>
@Html.ActionLink("Back to List", "Index")
</div>
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
最後に jQuery
の読み込みを行っています。
これらのJavaScriptはvalidationで使用されています。
ASP.NET MVC 5
と Entity Framework 6
を活用して、非常にシンプルなWebアプリケーションを、簡単・簡潔に作成する手順について解説しました。
ほとんどコーディングを行わずに、基本的な CRUD を行うアプリケーションが作成できることに驚かれたと思います。
また、デバッグ実行したブラウザで F12
キーを押し、開発者ツールを起動すると分かりますが
生成されるHTMLは見通しがよく、JavaScript
やCSS
での操作が容易です。
生成されるViewはBootstrap
やjQuery
を使用することを想定した作りになっていますので
特別な対応を行わなくても、ある程度見栄えのするアプリケーションが作成できます。