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を右クリックし、「追加」→「コントローラー」を選択します。TodoTodoesContext~/Views/_LayoutPage1.cshtmlTodoesController以下のファイルが生成されます。
後ほど、生成されたファイルの中身について解説します。
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を使用することを想定した作りになっていますので
特別な対応を行わなくても、ある程度見栄えのするアプリケーションが作成できます。