環境
- Windows 8.1 pro
- SQL Server 2014 Express
このざっペロの目標
ASP.NET MVC5 特有のことを何かするわけでもないけど、ざーっと Model-View-Controller についてペロる
プロジェクトの作成
Visual C# から Web を選択
Empty を選択し、下部「以下フォルダー及びコア参照を追加:」で「MVC」にチェック。Microsoft Azure の 「クラウド内のホスト」のチェックが外れていることを確認
*実際に作ったプロジェクト名は Chrowa3 だけど適当にどうぞ
Controller
Controller を MyProject/Controllers/HelloWorldController.cs として用意
HelloWorldController.cs の中身
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Chrowa3.Controllers
{
public class HelloWorldController : Controller
{
// アクションメソッド
public ActionResult Index()
{
return Content("Hello World");
}
}
}
ポイント
よくある MVC FW と同様で
- コントローラークラス名は必ず「Controller」を接尾辞とする
- 作成するコントローラーはController クラスを継承 ( : Controller )
- アクションメソッドでリクエストに応じた処理を実行 ( public ActionResult Index() )
// 「Controller」を接尾辞とする例
FooController.cs
BarController.cs
アクションメソッドの戻り値は ActionResult オブジェクト
- Contentメソッドは、ContentReusut オブジェクトを生成する。
- ContentResultクラスは ActionResultクラスの派生クラス。
- Contentメソッドは、Controllerクラスで用意された「ヘルパーメソッド」である
確認してみる
アクションメソッド Index() は、/HelloWorld/Index の様にアドレスを指定することで呼び出すことができる。
http://chrowa3/HelloWorld/Index
URLに関する設定(ルーティング)がデフォルトの設定のままの場合は Indexメソッドはデフォルト値として設定されているので省略できる
http://chrowa3/HelloWorld/
アクションメソッドを追加してみる
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Chrowa3.Controllers
{
public class HelloWorldController : Controller
{
// GET: HelloWorld
public ActionResult Index()
{
return Content("Hello World");
}
// 追加のアクションメソッド
public ActionResult Foo()
{
return Content("bar");
}
}
}
確認
http://chrowa3/HelloWorld/Foo
ルーティング
ルーティングとは、リクエストされた URL に応じて呼び出す処理を決定する仕組みです。もう少し具体的に言うと、クライアントから要求されたURLに応じて呼び出すコントローラーやアクションを決定する。
ルーティングは MyProject/App_Start/RouteConfig.cs で定義されているよ。
RouteConfig.cs を含む App_Start フォルダにある cs ファイルは、アプリケーションの起動時にGlobal.asaxによって呼び出される。これらは大抵の場合 static メソッドで構成され、フォルダの名前の通り初期化を担う役割を持っています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace Chrowa3
{
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 = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
ルーティングの設定は MapRoute( )メソッドで追加していきます。デフォルトでは名前付き引数を使っているので、なんの値を設定しているのかすごくわかりやすいよね。(引数はデフォルト値が設定されていないので省略不可能)
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
各引数について
引数 | 説明 |
---|---|
name | ルーティング設定を識別するための名前。一意であれば自由に設定できる |
url | URLパターン。{ } はプレースホルダーで、controllerやactionは予約されている名前で、それぞれコントローラー名、アクションメソッド名と紐づく。 {id} は任意の文字列で設定してもいいし、受け取るパラメーターはいくらでも増やせる |
default | リクエストに対するデフォルト値を設定。たとえば name: default の設定では、controller を HomeController に、action を Index( ) に、id を UrlParameter.Optional に設定している |
例)引数を2つ受け取るルーティング
name: "Sample" という設定を追加してみた。URL パターンに {foo}/{bar} を追加
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Sample",
url: "{controller}/{action}/{foo}/{bar}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
先ほど追加したアクションメソッド Foo を以下のように変更
public class HelloWorldController : Controller
{
// GET: HelloWorld
public ActionResult Index()
{
return Content("Hello World");
}
public ActionResult Foo(string foo,string bar)
{
return Content(foo + " " + bar); ;
}
}
結果
*Foo( ) の引数がルーティング側の引数名と同一でないと受け取れないので注意
Global.asax
RouteConfig.RegisterRoutes(RouteTable.Routes) で ルーティングを呼び出している
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace Chrowa3
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
}
View
Razor
Razor は ASPX に変わる比較的新し目のビューエンジンで、コードを @ から表し、<% ... %> のように終了デリミタの要らない書きやすい仕様となっている。
ってことで、まずは Bar という名前の View から作ってみるよ。場所は MyProject/Views/HelloWorld に作成するよ。
追加したいフォルダ上で右クリックすれば簡単に View の追加ができるお
こんな感じで
中身
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Bar</title>
</head>
<body>
<div>
<h1>View Sample</h1>
<p>@ViewBag.Message</p>
</div>
</body>
</html>
VeiwBag は下部ソースの通り dynamic 型で定義されているので、型をあんまり気にせずにバインドできちゃいます。
//
// 概要:
// 動的なビュー データ ディクショナリを取得します。
//
// 戻り値:
// 動的なビュー データのディクショナリ。
[Dynamic]
public dynamic ViewBag { get; }
csharp:HelloWorldController に Bar というアクションメソッドを追加
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Chrowa3.Controllers
{
public class HelloWorldController : Controller
{
// GET: HelloWorld
public ActionResult Index()
{
return Content("Hello World");
}
public ActionResult Bar()
{
ViewBag.Message = "Hello View ! Hello Bar !";
return View();
}
public ActionResult Foo(string foo,string bar)
{
return Content(foo + " " + bar); ;
}
}
}
ここで重要なのが、View名とアクションメソッド名が同名になっているということ(名前で紐付いている)。もっと言えば View ファイルである cshtml は
/コントローラー名/アクション名.cshtml
である必要があるよ。
レイアウトを適用する
レイアウトは、複数のページで共通する様なHTMLを共有化する手段を提供してくれるよ。Viewsディレクトリのなかに Shared ディレクトリを作成し、_BaseLayout.cshtml を用意。
Razor では _(アンダースコアで始まるテンプレートを「レイアウト」や「部分ビュー」として認識するので、必ず _(アンダースコア)で始まるファイル名で定義する。
今回はこんな感じで用意してみた
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>@ViewBag.Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
@RenderBody()
</body>
</html>
ViewBag.Title は 先ほどの ViewBag.Message を同じ考え方で理解できるよ。
RenderBody() では 対象の View ファイルがバインドされるイメージで
Bar.cshtml を以下の様に変更
@{
Layout = "~/Views/Shared/_BaseLayout.cshtml";
}
<p>@ViewBag.Message</p>
先ほど作成した _BaseLayout.cshtml を Layout に設定。
HelloWorldController に ViewBag.Title を追加
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Chrowa3.Controllers
{
public class HelloWorldController : Controller
{
// GET: HelloWorld
public ActionResult Index()
{
return Content("Hello World");
}
public ActionResult Bar()
{
ViewBag.Message = "Hello View ! Hello Bar !";
ViewBag.Title = "Bar Title";
return View();
}
public ActionResult Foo(string foo,string bar)
{
return Content(foo + " " + bar); ;
}
}
}
結果
見た目は先ほどの /HelloWorld/Bar と余り変わらないけど html はこういう状態に。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Bar Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<p>Hello View ! Hello Bar !</p>
<!-- Visual Studio Browser Link -->
<script type="application/json" id="__browserLink_initializationData">
{"appName":"Chrome","requestId":"2c4b17abeffa4e29af53a582e90a741f"}
</script>
<script type="text/javascript" src="http://localhost:49504/2b322cb7b4614b37a34871f4fdba20cf/browserLink" async="async"></script>
<!-- End Browser Link -->
</body>
</html>
部分ビュー
ヘッダーとフッターを定義してみるよ
<div id="header">
<h1>Chrowa3.com</h1>
</div>
<div id="footer">
<p>copyright 2015 chrowa3.com</p>
</div>
_BaseLayout.cshtml をこんな感じに
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>@ViewBag.Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
@Html.Partial("_Header")
@RenderBody()
@Html.Partial("_Footer")
</body>
</html>
Html.Partial( ) で引数に View名を指定すると部分ビューが引っ張られる。
bootstrap が効いているとわかりにくいなw
Model
MyProject/Models に Books.cs を作る。いわゆるデータモデルだ。
namespace Chrowa3.Models
{
public class Book
{
public int id { get; set; }
public string Title { get; set; }
public int Price { get; set; }
public string ISBN { get; set; }
public string PublishDate { get; set; }
}
}
Nuget で EntityFramework を突っ込む
コンテキストクラスを作成
コンテキストクラスはデータモデルとデータベースとの接続の橋渡しをするクラスです。
MyProject/Models に StandardContext.cs を作る。
using System.Data.Entity;
namespace Chrowa3.Models
{
public class StandardContext : DbContext
{
public DbSet<Book> Books { get; set; }
}
}
重要な点は以下の 3つ
- System.Data.Entity を using で追加
- DbContext クラスを継承
- DbSet<データモデル>型のプロパティを定義(※)
※ public であることと、単数系のモデル名に対して複数系の名前で定義する
イニシャライザ(initialize:初期化)を作成
データベースの「中身」を入れるためのイニシャライザを用意
using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace Chrowa3.Models
{
public class StandardInitializer : CreateDatabaseIfNotExists<StandardContext>
{
protected override void Seed(StandardContext context)
{
var books = new List<Book> {
new Book {
Title = "C#",
Price = 4800,
ISBN = "978-4-87311-650-1"
},
new Book {
Title = "JavaScript",
Price = 4536,
ISBN = "978-4-87311-573-3"
},
new Book {
Title = "Ruby",
Price = 4104,
ISBN = "978-4-87311-573-3"
}
};
books.ForEach(c => context.Books.Add(c));
context.SaveChanges();
}
}
}
データベースの初期化処理は以下の様なクラスを継承することで挙動を変えることが出来るよ
クラス名 | 説明 |
---|---|
DropCreateDatabaseAlways | アプリケーションを実行する度にデータベースを作成 |
CreateDatabaseIfNotExists | データベースが存在しない場合にデータベースを作成 |
DropCreateDatabaseIfModelChanges | モデルが変更された場合にデータベースを作成 |
Seedメソッドについて
SeedメソッドはCreateDatabaseIfNotExistsクラスやDropCreateDatabaseAlwaysクラスで定義されているprotectedな抽象メソッドだ。
このSeedメソッドをオーバーライドして初期化するデータを入れてあげれば、データベースに反映される
実際にコンテキストにデータを追加してシードするためにオーバーライドされるメソッド。 既定の実装では、何も行われません。
MSDN : CreateDatabaseIfNotExists クラス
先ほどのコードでは books に book を登録していっている
var books = new List<Book> {
new Book {
Title = "C#",
Price = 4800,
ISBN = "978-4-87311-650-1"
},
new Book {
Title = "JavaScript",
Price = 4536,
ISBN = "978-4-87311-573-3"
},
new Book {
Title = "Ruby",
Price = 4104,
ISBN = "978-4-87311-573-3"
}
}
データ定義後は books を ForEachでループし、public DbSet Books { get; set; } に対して add で追加。最後に SaveChanges() でデータベースに反映させています。
books.ForEach(c => context.Books.Add(c));
context.SaveChanges();
イニシャライザの生成と呼び出し
イニシャライザはアプリケーションの起動時に一度呼び出せべばいいので、Global.asax で生成、おそらく Database.SetInitializer 辺りが呼び出しておいてくれるんでしょう。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Chrowa3.Models;
using System.Data.Entity;
namespace Chrowa3
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
Database.SetInitializer<StandardContext>(new StandardInitializer());
}
}
}
Web.config に DB接続文字列を追加する
DB は SQLServer ってことでこんな感じで connectionStrings を追加した。
<configuration>
<connectionStrings>
<add name="StandardContext" connectionString="Data Source=(local)\SQLEXPRESS;Initial Catalog=Chrowa3;User ID=sa;Password=xxxxxxx;" providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
確認
まとめ
いわゆるよくある MVC FW で、コントローラー名、アクションメソッド名、アクションメソッドに引き渡すパラメーター、View名など「名前」にかなり依存するデザインとなっている。
最初は慣れないかもしれないが、この「雰囲気」は他の同じようなFWに言っても潰しが利くし、然程難しいルールじゃないってのと、今となっては VisualStudio も無料で手に入れられる時代なので、ASP.NET MVC5 から C# に触れてみるのもありなんじゃないかなぁと。
てゆーか EntityFramework って Nuget でごにょごにょしなきゃダメだったっけw