#背景
この記事を書いた背景ですが、自分が以下のような状況になったためです
ASP.NET WebFormsやってた
そろそろASP.NET MVCもやってみよう
プロジェクトテンプレートからMVCを選択して作ってみた!
F5押して動いた!
・・・でもなんで画面が表示されたの?指定されたURLの場所にはファイルはなさそうだけど。。。
よし、調べてみようと思っても情報がまあたくさんあるので、各種サイト、書籍を参考にしながらとめてみました
同じような状況の人の参考になればと思います
また、ASP.NET WebFormsを自分が触っていたのもあり、比較のためにWebFormsの内容を出しておりますが、WebForms自体の説明はないので予めご了承願います
##使用したプロジェクトテンプレート
今回確認用に作成したプロジェクトテンプレートは、以下の設定にしております
#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()
で以下のような処理が実行されています
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
は以下のような処理となっています
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からどのコントローラーに処理を関連づけるかのパターンマッチング文字列
protected void Application_Start()
{
// アプリケーション起動時に設定
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
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プロパティとして公開されています)
public HttpRequestBase Request
{
get { return HttpContext == null ? null : HttpContext.Request; }
}
なので、アクションメソッドを引数無しにしても処理することは可能ですが、
想定していないリクエストを処理しないようにするためにも設定したほうがよいです
public ActionResult UpdateData()
{
// 直接取得するのではなく、
var value = int.Parse(Request.QueryString["value"]);
return View();
}
public ActionResult UpdateData(int value)
{
// モデルバインディングの機構を使って取得した方が安全
return View();
}
###ActionResult
アクションが返す値は、ActionResultを実装したクラスとなります
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)
- Request.Form["id"]
- RouteData.Values["id"]
- Request.QueryString["id"]
- 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と同様に、グローバルにもローカルにも適用が可能です
グローバル
protected void Application_Start()
{
ModelBinders.Binders[typeof(作成したモデルバインダの型名)] = new 作成したモデルバインダの型();
}
ローカル
public ActionResult Index([ModelBinder(typeof(作成したモデルバインダの型名))] Employee employee)
##アクションに設定可能な属性(おまけ程度)
自作したFilterを個別に設定する場合属性を使いますが、
以下のような属性も定義されています
###NonAction
[NonAction]
public ActionResult About()
{
// アクションとして認識されないので、「About」というアクション名で呼び出しても404(NotFound)
return View();
}
Controller内で何らかのメソッドを定義するぐらいにしか使えないような気がしています
そもそもController内で通常処理用のメソッドは書かずに、モデル側に書くべきですが
###ActionName
[ActionName("List")]
public ActionResult GetData()
{
// アクション名を「List」と明示しているので、「GetData」というアクション名で呼び出しても404(NotFound)
return View();
}
###HttpGet,HttpPost,HttpPut,HttpDelete,HttpHead
[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を記述する場合、以下のような内容となります
@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によって適用されます
テンプレートプロジェクトで作成した場合は、以下のような内容
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
##Views/Shared ディレクトリ
各Viewから共通で参照するファイルの置き場所
テンプレートプロジェクトのように、_Layout.cshtmlなど全ファイル共通で参照するファイルや
複数のコントローラーが同じViewを使う場合は、ここに配置してください
後述しますが、View名の解決時に参照されるディレクトリとなるためです(デフォルト設定の場合)
##Layout
WebFormsでのマスターページに相当する機能
各Viewで独自のLayoutを参照したい場合は、以下のように参照するLayoutを変更するだけでよいです
@{
Layout = "~/Views/Shared/_MenuLayout.cshtml";
}
プロジェクトテンプレートから作成した場合、全ページに
Layout = "~/Views/Shared/_Layout.cshtml";
が適用されるようになっているので、そのLayoutから変更する必要がない場合は、設定不要です
設定すると_ViewStart.cshtmlで設定したものとは異なるLayoutが適用されるので注意が必要です
###セクション
WebFormsでのContentPlaceHolderに相当する機能
Layout用のページにて以下のように指定する
<head>
@RenderSection("Stylesheets", false)
</head>
<body>
@RenderBody()
</body>
@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を作成することが可能です
@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ヘルパーの拡張メソッドを作成することで、カスタムヘルパーを作成することが可能です
####拡張メソッド形式
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の記述形式でそのままかけます
使い方
- App_Codeフォルダ作成
- *.cshtmlファイルを作成
- 作成したファイルに、以下のように、通常通りRazorの書式で記述する
@helper showMessage(string value)
{
<h2>@value</h2>
}
ただ、このインラインヘルパーにも制限があり、標準で定義されているヘルパーが使えない点です
このあたりも考慮しながら、拡張メソッド版と使い分ける必要があります
##ViewBagとViewData
ControllerとView間でデータの受け渡すための入れ物
###ViewBag
- ASP.NET MVC 3から登場
- ViewDataをdynamicで処理出来るようにラップしたもの
###記述例
public ActionResult Index()
{
ViewBag.Price = 1;
ViewBag.Message = "ViewBag.Message";
ViewData["Message1"] = "ViewData.Message1";
return View();
}
@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用のルート
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用のルート
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メソッドの場合、以下のような形となります
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に対応しています