Posted at

ASP.NET MVC 開発を始める前に理解しておきたいこと

More than 3 years have passed since last update.


背景

この記事を書いた背景ですが、自分が以下のような状況になったためです

ASP.NET WebFormsやってた

そろそろASP.NET MVCもやってみよう

プロジェクトテンプレートからMVCを選択して作ってみた!

F5押して動いた!

・・・でもなんで画面が表示されたの?指定されたURLの場所にはファイルはなさそうだけど。。。

よし、調べてみようと思っても情報がまあたくさんあるので、各種サイト、書籍を参考にしながらとめてみました

同じような状況の人の参考になればと思います

また、ASP.NET WebFormsを自分が触っていたのもあり、比較のためにWebFormsの内容を出しておりますが、WebForms自体の説明はないので予めご了承願います


使用したプロジェクトテンプレート

今回確認用に作成したプロジェクトテンプレートは、以下の設定にしております

プロジェクト.png


ASP.NET MVC LifeCycle

Httpのリクエストを受け付けてからレスポンスが返されるまでの間にどういった処理が実行されているのか以下の資料がわかりやすかったです

ASP.NET MVC 5 APPLICATION LIFECYCLE

上記に記載されているように、リクエストを受けてからの大きな流れは、以下のようになっています。


  • Routing

    まずは、ルーティングによって、どのコントローラーの処理を実行するかを特定します

    ここでの情報を、MvcHandlerに渡して、以下の処理が実施されます


  • Controller Creation

    対象のコントローラーが作成されます


  • Authentication and Authorization

    認証と、認可が行われます


  • Model Binding

    リクエストの値から、アクションメソッド(コントローラー内で定義されているメソッド、ルーティングにより関連付けられます)の引数に渡す値を解決します

    モデルバインディングについては、後述します


  • Action Method Invocation

    実際のアクションメソッドが実行されます


  • Result Execution

    アクションメソッドの実行結果を元にレスポンスを作成します


1枚目のLifecycle内の途中に[Action Invocation(with Filters)]という記載がありますが、

それぞれのタイミングに応じたIxxxFilterを実装し、その作成したFilterをアクションメソッドに関連付ける事で

Controllerの各処理の前後に介入することが可能となります

例えば、コントローラーのActionの実行前後に介入するためには、

IActionFilter

を実装したFilterを作成し、アクションに対して属性として設定します

全体として適用するためには、GlobalFilters.Filtersに対してAddします

このLifecycleが実行されることにより、クライアントのリクエストが処理されてレスポンスが返されるようになります

※WebFormsの時のLifecycleも参考までにリンクを貼っておきます

ASP.NET Application Life Cycle Overview for IIS 7.0

※Authenticationと、Authorizationどっちかわからなくなることがあるのでメモ


  • 認証(Authentication)

    「本人確認」のこと

    多くの場合、本人を識別するIDと、その本人しか知り得ぬであろうパスワードの対が一致すれば、

    “自称”本人が紛れもなく本人であると見なすといった具合で「認証」を行うこととなる


  • 認可(Authorization)

    認証済みの利用者に対して、何らかのサービスの利用やリソースへのアクセスなどに対する権限を与えたりすること



Global.asax

テンプレートからプロジェクトを作成すると、Application_Start()で以下のような処理が実行されています


Global.asax

protected void Application_Start()

{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}

それぞれの内容を簡単に記載すると、


  • AreaRegistration.RegisterAllAreas();

    エリア(区分)を登録します

    エリアは、コントローラーをグループ化する機能です


  • GlobalConfiguration.Configure(WebApiConfig.Register);

    WebAPIに対する設定を行います


GlobalConfiguration.Configureは以下のように、HttpConfigurationを引数に取るActionを要求しています

public static void Configure(Action<HttpConfiguration> configurationCallback)

WebApiConfig.Registerは以下のような処理となっています


App_Start/WebApiConfig.cs

public static void Register(HttpConfiguration config)

{
// Web API の設定およびサービス

// Web API ルート
config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}


WebAPIの実装は、System.Web.Http namespace配下となっており、MVC FrameworkのSystem.Web.Mvcとは異なります

同じような機能がありますが、実装が異なるので、別々の設定が現状は必要です

※ASP.NET MVC 6では、このあたりの実装が統一されるようです

http://www.asp.net/vnext/overview/aspnet-vnext/overview


As part of ASP.NET vNext, the MVC, Web API, and Web Pages frameworks are being merged into one framework, called MVC 6.

The new framework removes a lot of overlap between the existing MVC and Web API frameworks.

It uses a common set of abstractions for routing, action selection, filters, model binding, and so on.

You can use the framework to create both UI (HTML) and web APIs.



  • FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

    全コントローラーに適用するFilterを登録します(WebAPIの設定は関係ありません。WebApiConfig.Registerにて設定するため)

    Lifecycleの箇所で記載しましたが、Filterによって、コントローラーの各種処理の前後に割り込めるようになります


  • RouteConfig.RegisterRoutes(RouteTable.Routes);

    コントローラーのルートを登録します(WebAPIの設定は関係ありません。WebApiConfig.Registerにて設定するため)


  • BundleConfig.RegisterBundles(BundleTable.Bundles);

    JavaScript、cssのミニファイ、結合を行うための設定



ルーティング

従来は、実際に指定されたURLと、ファイルを物理的にマッピングするようなルーティングが実行されていました

WebFormsで作成したプロジェクトをコンパイルした後に、空でも「aspx」ファイルの配置が必要だったのは、そのページに対するリクエストを処理するためのマークとしてでした

※現在は、WebFormsでもルーティングを使用することは可能ですが、話がややこしくなるので詳細は割愛

MVCのルーティングでは、リクエストのURLをアプリケーションが定義しているルート(route)と照合します

一致するルートが見つかった場合は、該当するコントローラーのアクションメソッドが実行されます

一致しなければ、404エラーとなります


ルート(route)

URLからどのコントローラーに処理を関連づけるかのパターンマッチング文字列


Global.asax

protected void Application_Start()

{
// アプリケーション起動時に設定
RouteConfig.RegisterRoutes(RouteTable.Routes);
}


App_Start/RouteConfig.cs

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 }
);
}
}


上記に記載したようなルートを、Global.asaxのアプリケーション起動時に登録しておきます

ルートの設定は、アプリケーションに必要な数だけ登録が必要です

{controller}:コントローラー名

{action}:アクション名

に関連付けられます

上記のルートが設定されている状態で

http://www.hoge/Test/Start

というURLで呼び出すと、TestControllerのStartメソッドが呼び出されるようになります

この辺りを実行しているのが、UrlRoutingModuleなので、詳しくはこちらを参照願います


ASP.NET MVC Controller

ルーティングにより関連付けられたリクエストを実際に処理するためのコードを記述します

このControllerになんでも記述が出来てしまうので、大きくなり過ぎないように注意が必要となります

※WebFormsのaspx.csになんでも記述出来るのと同じです

 ASP.NET MVCにしたから、MVCの形式で作れるようになるわけではなく、

 考え方、作り方を変えないと同じ事になるので注意しましょう


アクションメソッド

ルーティングにより関連付けられたコントローラー内で定義されているメソッドを以降、アクションメソッドと呼びます


引数

無しでも作成可能ですが、必要な場合には設定したほうがよいです

ここで設定した引数は、後述するモデルバインディングの機能によってリクエストの値を元に設定されます

アクションメソッドは、HTTPリクエストの全てのデータにアクセスすることが可能です

(HttpContextが参照可能なため

またControllerクラスには、Requestプロパティとして公開されています)


System.Web.Mvc.Controller.cs

public HttpRequestBase Request

{
get { return HttpContext == null ? null : HttpContext.Request; }
}

なので、アクションメソッドを引数無しにしても処理することは可能ですが、

想定していないリクエストを処理しないようにするためにも設定したほうがよいです


csharp

public ActionResult UpdateData()

{
// 直接取得するのではなく、
var value = int.Parse(Request.QueryString["value"]);
return View();
}
public ActionResult UpdateData(int value)
{
// モデルバインディングの機構を使って取得した方が安全
return View();
}


ActionResult

アクションが返す値は、ActionResultを実装したクラスとなります


System.Web.Mvc.ActionResult.cs

  public abstract class ActionResult

{
/// <summary>
/// <see cref="T:System.Web.Mvc.ActionResult"/> クラスを継承するカスタム型によって、アクション メソッドの結果の処理を有効にします。
/// </summary>
/// <param name="context">結果が実行されるコンテキスト。このコンテキスト情報には、コントローラー、HTTP コンテンツ、要求コンテキスト、ルート データなどが含まれます。</param>
public abstract void ExecuteResult(ControllerContext context);
}

ExecuteResultは、クライアントに返すためのHttpResponseオブジェクトを設定するのが目的となります

この処理を実装することで、独自の処理を作成する事が可能となります

ASP.NET MVCには複数種類の定義済みのクラスがあります

要件に応じて使いわけましょう

定義済みのActionResultの派生クラスには代表的なものとして以下があります


  • ViewResult

    return view();とした時に返されているのはこれです

    そのためデフォルトで作成されているアクションメソッドの戻り値の多くはこの型になっています


  • JsonResult

    JSON文字列を送信します

    内部的には、渡されたオブジェクトを、JavaScriptSerializerを使ってシリアライズしたデータを送信します


  • RedirectResult

    HTTP302レスポンスを返します



モデルバインディング

クライアントから送信されてきたデータを、コントローラのアクションメソッドの引数にバインドする処理


プリミティブ型のバインディング

以下のようなアクションメソッドがあった場合、規定では、以下の順序で解決が行われます

public ActionResult Index(int id)


  1. Request.Form["id"]

  2. RouteData.Values["id"]

  3. Request.QueryString["id"]

  4. Request.Files["id"]

特定のリソースに対してのみ解決したい場合は、以下のような属性を指定する事で可能です

例:Bodyからのみ取得する

public ActionResult Index([FromBody]int id)

QueryStringから取得するFromUriもあります


モデルバインディング時に発生する例外

引数に指定された値の解決ができない場合、例外が発生します

public class HomeController : Controller

{
public ActionResult Index(int id)

Home/Index

 必須のパラメータのため例外

Home/Index?id=ten

 tenを数値に変換できない、その結果idに渡される値がnullになるため例外

 規定のモデルバインダは、引数の解決が出来なかった場合、nullを渡すため

対応方法

public ActionResult Index(int? id)のようにnull許容型にするか、

public ActionResult Index(int id=1)のようにデフォルト値を設定する

 

モデルバインダの例外は、アクション実行前に発生するので、アクションの処理内ではtry~catch出来ない


複合型のバインディング

プリミティブ型のバインディングと、基本的な解決方法は同じです

よって、リクエストに、Id、Nameの値が設定されていれば、その値が引数のオブジェクト(今回の例であれば、employee)に設定されます

public class Employee

{
public int Id { get; set; }
public string Name { get; set; }
}

public class EmployeeController : Controller
{
public ActionResult Index(Employee employee)
}

ただし、プリミティブ型の場合とくらべて注意が必要な点としては、

引数が存在しなかった場合は、空のEmployeeオブジェクトとなる点です(nullになるわけではない)

Employee/Index

employee.Id == 0

employee.Name == null

Employee/Index?Name=taro

employee.Id == 0

employee.Name == "taro"

Employee/Index?Id=10

employee.Id == 10

employee.Name == null

プリミティブ型で直接受けた場合、例外が発生するものもありましたが、

複合型では、デフォルト値になるので注意してください


モデルバインダのカスタマイズ

IModelBinderを実装すれば可能ですが、大変です

特定の型のみ対応が必要な場合は、DefaultModelBinderを継承して作成するのが楽です

作成したModelBinderはFilterと同様に、グローバルにもローカルにも適用が可能です

グローバル


Global.asax

protected void Application_Start()

{
ModelBinders.Binders[typeof(作成したモデルバインダの型名)] = new 作成したモデルバインダの型();
}

ローカル


EmployeeController.cs

public ActionResult Index([ModelBinder(typeof(作成したモデルバインダの型名))] Employee employee)



アクションに設定可能な属性(おまけ程度)

自作したFilterを個別に設定する場合属性を使いますが、

以下のような属性も定義されています


NonAction


csharp

[NonAction]

public ActionResult About()
{
// アクションとして認識されないので、「About」というアクション名で呼び出しても404(NotFound)
return View();
}

Controller内で何らかのメソッドを定義するぐらいにしか使えないような気がしています

そもそもController内で通常処理用のメソッドは書かずに、モデル側に書くべきですが


ActionName


csharp

[ActionName("List")]

public ActionResult GetData()
{
// アクション名を「List」と明示しているので、「GetData」というアクション名で呼び出しても404(NotFound)
return View();
}


HttpGet,HttpPost,HttpPut,HttpDelete,HttpHead


csharp

[HttpGet]

public ActionResult Index()
{
// HttpのGetメソッドの場合のみ、処理される
return View();
}
[HttpPost]
public ActionResult Index1()
{
// HttpのPostメソッドの場合のみ、処理される
return View();
}

属性で指定されたメソッドのみ処理が実行されます

上記以外にも以下のものがあります

[HttpPut, HttpDelete, HttpHead]


ASP.NET MVC View

Controllerから、表示に必要なデータを受け取り、画面に表示するためのhtmlを用意します

MVC 3 からは、View側を記述するためのDSLとして、Razorが導入されました

※aspxビューエンジンもありますが、今からは使う必要ないと思います。色んな点で、Razorで書くほうが楽です


Razor

cshtmlを記述する場合、以下のような内容となります


xxx.cshtml

@model dynamic

@{
ViewBag.Title = "About";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

@* コメント *@
@if (Model.Authorized)
{
<p>@Model.Data</p>
}
else
{
<text>Hello, world.</text>
}


Razorのコードは「@」で始めます

Controllerから渡されたデータには、@Modelでアクセス可能です

@Modelでの参照以外にも、@ViewBag@ViewDataなどがありますが、こちらは後述します

@ifの箇所を見ると、以下のように{}で囲まれた中にhtmlのタグが含まれています

Razorではこのあたりを柔軟に解釈してくれるので、true時の

はタグとして正しく認識されます

ただ、単純にその文字列を出力したいという場合には、タグで囲む必要があります

Razorについては、詳しく記述されたサイトがたくさんあるので、そちらを参照して頂いた方がよいです

“Razor”の紹介 - ASP.NET向け新ビュー・エンジン

C# Razor構文 基礎文法 総まとめ


@model

View(cshtml)で使用する型が指定できます

@model Employee

以降、@Model.Idのような参照が可能となります

デフォルトでは、@model dynamicとなっているので、なんでもありの状態ですが明示的に指定したほうがよいです

コンパイル時に存在しないプロパティを参照しているとエラーになりますし、インテリセンスもきくようになります


Views/_ViewStart.cshtml

全てのViewに対して適用される設定を記述します

RazorViewEngineによって適用されます

テンプレートプロジェクトで作成した場合は、以下のような内容


_ViewStart.cshtml

@{

Layout = "~/Views/Shared/_Layout.cshtml";
}


Views/Shared ディレクトリ

各Viewから共通で参照するファイルの置き場所

テンプレートプロジェクトのように、_Layout.cshtmlなど全ファイル共通で参照するファイルや

複数のコントローラーが同じViewを使う場合は、ここに配置してください

後述しますが、View名の解決時に参照されるディレクトリとなるためです(デフォルト設定の場合)


Layout

WebFormsでのマスターページに相当する機能

各Viewで独自のLayoutを参照したい場合は、以下のように参照するLayoutを変更するだけでよいです


Views/XXX/xxx.cshtml

@{

Layout = "~/Views/Shared/_MenuLayout.cshtml";
}

プロジェクトテンプレートから作成した場合、全ページに

Layout = "~/Views/Shared/_Layout.cshtml";

が適用されるようになっているので、そのLayoutから変更する必要がない場合は、設定不要です

設定すると_ViewStart.cshtmlで設定したものとは異なるLayoutが適用されるので注意が必要です


セクション

WebFormsでのContentPlaceHolderに相当する機能

Layout用のページにて以下のように指定する


_Layout.cshtml

<head>

@RenderSection("Stylesheets", false)
</head>
<body>
@RenderBody()
</body>


index.cshtml

@section Stylesheets{

<link href="~/css/test.css" rel="stylesheet" />
}

@RenderSectionによって、独自のセクションを定義することが可能です

Layoutを参照しているViewは、@section セクション名とすることで、Layoutページの指定された位置に記述内容を出力できるようになります

@RenderBodyは、デフォルトで定義されているもので、これが実行される箇所に該当のLayoutを読み込んでいるViewの内容が出力されます

Layoutは、マスターページ同様に入れ子にする事が可能です


HTMLヘルパー

WebFormsのサーバーコントロールに相当する機能

ただ、サーバーコントロールとは違うので、イベントなどがあるわけではなく、単純にHTMLを生成するために使います

HtmlHelperの拡張メソッドとして定義されたもの

例えば、リンクを作成するActionLinkやフォームを作成するBeginFormなどがあります


参考:フォームの作成

Html.BeginFormを使用してFormを作成してみます

MVCでは、Html.BeginFormを使用することでFormを作成することが可能です


index.cshtml

@using (Html.BeginForm())

{
@Html.TextBox("id")
<br />
@Html.TextBox("name")
}
@using (Html.BeginForm("actionName", "controlerName") )
{
@Html.TextBox("id2")
<br />
@Html.TextBox("name2")
}

// 上記のコードが以下のようなHtmlに変換される

<form action="/Controller/Index" method="post">
<input id="id" name="id" type="text" value=""><br>
<input id="name" name="name" type="text" value="">
</form>
<form action="/controlerName/actionName" method="post">
<input id="id2" name="id2" type="text" value=""><br>
<input id="name2" name="name2" type="text" value="">
</form>


usingを使う事で、EndFormを呼び出す必要がないので記述が簡単になります

また、上記に記載した以外にも多くのオーバーロードがあるので、必要に応じて使い分けてください

WebFormsではポストバックという仕組みのためにFormは1つしか作成できませんでしたが、MVCではその制約がないので処理応じてFormを作成することが可能です


カスタムヘルパー

MVCで定義されているHtmlヘルパーが拡張メソッドであるように、

自分でHtmlヘルパーの拡張メソッドを作成することで、カスタムヘルパーを作成することが可能です


拡張メソッド形式


HtmlHelperExtensions.cs

public static IHtmlString Spacer(this HtmlHelper helper, int height)

{
var div = new TagBuilder("div");

var img = new TagBuilder("img");
img.MergeAttribute("height", height.ToString());
img.MergeAttribute("width", "1");
img.MergeAttribute("src", "/img/space.gif");
img.MergeStyle("height", height.ToString() + "px");

div.MergeAttribute("height", height.ToString());
div.MergeStyle("height", height.ToString() + "px");

div.InnerHtml = img.ToString();

return MvcHtmlString.Create(div.ToString());
}


TagBuilderはコンストラクタで指定いたタグを作成します

これをつかって必要なDOMを構築し、文字列を返します(文字列を得るには、ToStirng()するだけ)

上記の例では、MvcHtmlStringを使って文字列をIHtmlString形式に変換しています

IHtmlString型は「HTMLエンコード済みであること」を表すオブジェクトで、

ASP.NETに対して「文字列をエンコードしてはならない」ことを通知するために使用します

String型のままでは、戻り値に含まれるタグはRazor側でエスケープ処理されてしまうので注意が必要です


インラインヘルパー

拡張メソッドを定義する方法の問題点は、複雑なタグ構造を作りにくい点です

インラインヘルパーを使えば、Razorの記述形式でそのままかけます

使い方


  1. App_Codeフォルダ作成

  2. *.cshtmlファイルを作成

  3. 作成したファイルに、以下のように、通常通りRazorの書式で記述する


index.cshtml

@helper showMessage(string value)

{
<h2>@value</h2>
}

ただ、このインラインヘルパーにも制限があり、標準で定義されているヘルパーが使えない点です

このあたりも考慮しながら、拡張メソッド版と使い分ける必要があります


ViewBagとViewData

ControllerとView間でデータの受け渡すための入れ物


ViewBag


  • ASP.NET MVC 3から登場

  • ViewDataをdynamicで処理出来るようにラップしたもの


記述例


Controllers/ViewController

public ActionResult Index()

{
ViewBag.Price = 1;
ViewBag.Message = "ViewBag.Message";
ViewData["Message1"] = "ViewData.Message1";
return View();
}


Views/View/index.cshtml

@ViewBag.Message1         @* ViewData.Message1 と表示される *@

<br/>
@(ViewBag.Price * 10) @* 10 と表示される *@
<br />
@ViewData["Message"] @* ViewBag.Message と表示される *@
<br/>
@* @(ViewData["Price"] * 10) *@ @* 実行時に型を変換しろとエラーになる *@
@((int)ViewData["Price"] * 10) @* 10 と表示される *@

実行時の変換が面倒なので、ViewBagのほうが個人的には使いやすい

ただ、そもそもControllerから渡す値は、View毎に、@modelで指定する型に全て入っている方が望ましい


View名の解決

ViewEngine毎に異なりますが、Razorのデフォルトは以下のようになっています

ViewEngines.Engines

Count = 2
[0]: {System.Web.Mvc.WebFormViewEngine}
[1]: {System.Web.Mvc.RazorViewEngine}
(ViewEngines.Engines[1] as System.Web.Mvc.RazorViewEngine).ViewLocationFormats
{string[4]}
[0]: "~/Views/{1}/{0}.cshtml"
[1]: "~/Views/{1}/{0}.vbhtml"
[2]: "~/Views/Shared/{0}.cshtml"
[3]: "~/Views/Shared/{0}.vbhtml"

"~/Views/{1}/{0}.cshtml"

{1}コントローラー名、{0}アクション名が設定され検索されます

デフォルトでこうなっているので、間違えやすいですが、ルートで設定するのはあくまでもコントローラーとアクションメソッドまでです

View名の解決自体は別の箇所で処理されているというのをわかっていないと、Sharedディレクトリに入れたViewを上手く使えなかったりするのでご注意願います


ASP.NET Web API

MVC 4から追加されました

2014/9/2時点での最新は、Web API 2.2

基本的な部分はMVCと大きくは変わらないですが、Web APIを簡単に作成できるようになっています

MVCとの主な違いは以下のような部分です


  • Web API用のコントローラーを作成する場合、継承するのは、System.Web.Http.ApiController


  • 返すデータがクラスとなり、それを、Json、XMLなどのフォーマットにシリアル化されたデータがクライアントには返される

    この書式は、HTTPリクエストの内容(デフォルトでは、ヘッダ「Accept」の値)によって選択されます


※Lifecycleの箇所でも記述しましたが、MVCとほぼ同じように動作しますが、MVC 5の時点では別実装となっています

 MVC:System.Web.Mvc

 Web API:System.Web.Http

これは、MVC 6で統一される予定です


ルーティング

基本的な処理はMVCと同じですが、ルートの登録箇所、アクションメソッドの関連付けが少し異なります

ルートの登録箇所は、Global.asaxの箇所で説明したGlobalConfiguration.Configure(WebApiConfig.Register);をご参照ください

ルートの設定をそれぞれ見てみると、WebAPIのデフォルト設定では、{action}が設定されていません

WebAPI用のルート


csharp:App_Start/WebApiConfig.cs

public static void Register(HttpConfiguration config)

{
// Web API の設定およびサービス

// Web API ルート
config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}


MVC用のルート


csharp:App_Start/RouteConfig.cs

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 }
);
}


では、どのようにコントローラー内のメソッドと関連付けているのかというと、HTTPのメソッドを使用します

例えばGetメソッドの場合、以下のような形となります


csharp

public class TestApiController : ApiController

{
// http://localhost/TestApi/
// http://localhost/TestApi/TestTest
public Employee Get()
{
return new Employee();
}
// http://localhost/TestApi/?id=1
public Employee Get(int id)
{
return new Employee { Id = id };
}
// http://localhost/TestApi/?id=1&id2=2
// http://localhost/TestApi/TestTest?id=1&id2=2 アクション名を使っていないので、パラメータが一致すれば呼び出し可能
public Employee GetValue(int id, int id2)
{
return new Employee { Id = id };
}
// http://localhost/TestApi/Value?id=1&id2=2&id3=3
// Getのメソッド名はついていませんが、属性をつける事で呼び出しが可能となります
[HttpGet]
public Employee Value(int id, int id2, int id3)
{
return new Employee { Id = id };
}
// 関連付けるための情報がないためよびだせません
// このアクションメソッドを呼び出したい場合は、ルートにて{action}をとるようにする必要があります
public Employee Value(int id, int id2)
{
return new Employee { Id = id };
}
// このメソッドに以下のように属性をつけるとアクションメソッドとして有効なメソッドとなります
// [HttpGet]
// public Employee Value(int id, int id2)
// {
// return new Employee { Id = id };
// }
// ですが、上記のpublic Employee GetValue(int id, int id2)と、アクションメソッドとしては同じ扱いのものとなってしまうので、
// http://localhost/TestApi/?id=1&id2=2 を呼び出すとエラーが返されます
// 要求に一致する複数のアクションが見つかりました: 型 MvcStudy.Controllers.TestApiController の GetValue 型 MvcStudy.Controllers.TestApiController の Value
// コンパイルは通ってしまうので注意してください
}

Getの例を記載しましたが、Post、Putなどについても同様にアクションメソッドの解決が行われます


動作確認

通常のコントローラーの処理を要求するのと同じように、WebAPI用のURLをブラウザに入力すると呼び出し可能です

※ブラウザから呼び出す場合、HttpGetが許可されている事を確認してください

ただ、その表示結果はブラウザによって異なる可能性があります

現状であれば、IEならプロンプトが表示され、ファイルを保存するか開くかするとjson形式のデータが表示されると思います

ChromeならXMLが表示されると思います

その理由は、ApiControllerが返す値をシリアル化する際に、HTTPリクエストの内容(デフォルトでは、ヘッダ「Accept」の値)に応じて形式を決めるためです

IEは、以下の様なAcceptヘッダとなっています

Accept: text/html, application/xhtml+xml, */*

WebAPIは、JsonとXMLをサポートしていますが、Jsonを優先するためIEはJsonを受け取りました

Chromeでは、以下のようなAcceptヘッダとなっており*/*よりもapplication/xmlを優先するようになっているため、XMLを受け取っています

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

Jsonを許可するでもAcceptヘッダに「application/json」を「application/xml」よりも先に来るようにFiddlerなどを使ってリクエストを書き換えてあげると、json形式で表示されます


参考にした情報

プログラミングMICROSOFT ASP.NET MVC (Microsoft Press)

MVC 3ベースですが、ASP.NET MVCの基本的な部分を理解するにはこの1冊あれば十分だと思っています

Pro ASP.NET MVC 5

洋書ですが、MVC 5に対応しています