-
Notifications
You must be signed in to change notification settings - Fork 0
MS_ASPNETMVCTerms
- 戻る(ASP.NET MVC)
「ASP.NET MVCの用語」は
「ASP.NET MVCの利用方法」と比べて、
ASP.NET MVC の基本的なトピックをまとめています。
補足(本ページの前提/最新化): 本ページが扱うのは
.NET Framework 版の ASP.NET MVC 5(System.Web.Mvc)である。
ASP.NET Core MVC とは別実装で、
概念は共通だが API 名や既定動作が異なる箇所がある。主な対応関係(詳細は各節の補足で述べる):
本ページ(ASP.NET MVC 5) ASP.NET Core MVC ActionResultIActionResultHtml.TextBoxFor等タグ ヘルパー( asp-for)Ajax.BeginForm廃止(部分描画とJavaScript) RouteConfig.RegisterRoutesapp.MapControllerRouteBind属性[Bind](健在)/DTO 分離が推奨HandleError属性例外ミドルウェア Global.asaxProgram.cs
Model には、
- B 層クラスと、
- B 層クラスが返す View に渡される Entity, Bean, POCO 的なクラス(ViewModel)
がある。
View = 画面表示のための処理。
- マスタページ的な View と、
- 全体 View
- 部分 View がある。
- Action Method を実装する。
- Action Method は、
- 「M(Model)」を呼び出して
- 「(Bean ≒ M)」を取得して
- 「V(View)」呼び出す。
- C(Controller)を作成
- V(View)を作成
- M(Model)を作成
- C(Controller)に Action Method を追加し、C→M→V と繋げる。
- ルート定義に従い、URL から Controller や Action Method に処理を振り分ける。
- ユーザがブラウザに URL を入力すると、
指定したルーティング規則を使用し、URL が解析され、Controller のパスが特定される。
- 既定のルート定義は、RouteConfig.RegisterRoutes メソッドで定義されている。
- ココで MapRoute() メソッドを使用し、ルートパラメタ(routeName, routeValues)を登録する。
- ルートパラメタ(routeName, routeValues)は、URL の作成にも使用される。
- デフォルトでは以下のようにルート定義されている(自由にカスタマイズ可能)
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);- 個別のルート定義は、RouteConfig.RegisterRoutes メソッドで定義されている。
- ココで MapMvcAttributeRoutes() メソッドを使用し、属性ルーティングを有効化する。
routes.MapMvcAttributeRoutes();- 以下のようにルート定義可能(自由にカスタマイズ可能)
[Route("{controller}/{action}/{id}", Name="xxxx")]-
第一引数には、MapRoute の「URL with parameters」と同じものを指定。
-
第二引数の Name はオプションで、Html ヘルパー(@Html.RouteLink)で使用できる。
-
また、パラメタについて、以下の様な設定を行うことができる。
- オプション設定
- 既定値の設定
- 制約条件の設定
- データ型指定
- 値範囲指定
- 正規表現指定
- カスタム制約条件指定
-
その他、モデルレベルに既定の ActionMethod 名を追加可能。
[Route("{action=Top}")]
public class XXXXXController : Controller
{
public ActionResult Top() { ・・・ }
}移行メモ(誤記): 原文の
[Route("{action=Top")]は
閉じ波括弧が欠けているため、[Route("{action=Top}")]に修正した。
ルートプレフィックスを使用すると、モデルレベルに、
「URL with parameters」のルート部分の定義を追加できる。
補足(ASP.NET Core での書き方): 属性ルーティングは
ASP.NET Core MVC でも同じ考え方で使える
(むしろ API では属性ルーティングが標準)。[ApiController] [Route("api/[controller]")] // ルート プレフィックス public class ProductsController : ControllerBase { [HttpGet("{id:int}")] // GET api/products/1 public IActionResult Get(int id) => Ok(); }
Program.cs側の規約ルーティングは次の形になる。app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");**
{id?}(省略可能)や{id:int}(型制約)**といった
インライン制約が使えるため、
原文が挙げる「制約条件の設定」を属性の中で完結して書ける。
この時、ブラウザから
http://server/applicationname/Products/Index/1
という URL でリクエストを送信した場合、
http://(Server FQDN名)/(Controller名)/(Action Method名)/(id 値)
ルート定義に従い、ページハンドラは
- Controller 名:Products
- Action Method 名:Index
- id (Action Method に渡す値):1
と判断し、
Products Controller の Index Action Method を呼び出し、
Action Method の引数として "1" を渡す。
Action Method 名と id 値は省略可能で、Action Method 名を省略すると、
Index Action Method が実行される。
- ASP.NET MVC アプリケーションのコントローラーとアクション メソッド
https://learn.microsoft.com/ja-jp/previous-versions/aspnet/dd410269(v=vs.98)
-
引数とのマップの方法
-
単方向バインディング
POST や GET のパラメタの名前と一致した引数を定義しておけば、自動的にマップされる。- Get ならクエリ文字列のキー名
- Post ならフォームデータのキー名
- モデルバインディング
Model クラスを使うこともできる(Property 名と一致させる)。
-
- Controller の ActionMethod と View の間に、
ポストバック的な復元動作がある場合に便利。 - Model クラスを使う(Post の場合のみ)。
- Controller の ActionMethod と View の間に、
-
-
その他
- FormCollection を使う (Post の場合のみ)
<form> の中身がコレクション型として保持されたもの。
- FormCollection を使う (Post の場合のみ)
Action Method の結果として、Action Result を返す。
詳細はAction Resultを参照。
-
クライアントから送信されてきたデータのキー名と、
Controller の Action Method の引数名とが一致するキー値を探して、バインドする。 -
非常に単純な仕組みのため、ASP.NET Web Formsに比べて、
単体テストが遣り易くなっている反面、オーバーポスティング攻撃を受け易く、
セキュリティ的に脆弱とまでは言えないが、課題があると言える。 -
仕組みの詳細は、下記を参照。
補足(オーバーポスティング攻撃とは): 原文が繰り返し警告している
この攻撃は、モデル バインディングが「送られてきた項目を素直に埋める」
ことに起因する。【画面】 名前・メールアドレスの入力欄のみ 【モデル】 class User { Name; Email; IsAdmin; } 攻撃者が POST に IsAdmin=true を追加して送る → モデル バインダーが素直に IsAdmin にも値を入れる → そのまま保存すると権限昇格ASP.NET Web Forms では起きにくいのは、
ViewStateで「画面に出した項目」が固定されているためである
(原文が「Web Forms に比べて」と書いているのはこの対比)。対策の優先順位:
対策 評価 ① 画面専用の ViewModel(DTO)を使う 最善。危険な項目がそもそも無い ② [Bind(Include=...)]で明示有効だが、項目追加時に漏れやすい ③ [Bind(Exclude=...)]危険(除外漏れが起きる) ④ 保存前に手で詰め替える 有効 エンティティをそのまま Action Method の引数にしない、
というのが現在の定石である。
ASP.NET Core でも同じ問題は残っている。
Request を検索して Value を取得するクラス。
- 既定の ValueProvider
| 項番 | ValueProvider | 値取得元 |
|---|---|---|
| 1 | ChildActionValueProvider | 子アクション |
| 2 | FormValueProvider | フォーム値 |
| 3 | RouteDataValueProvider | Route データ |
| 4 | QueryStringValueProvider | クエリ文字列 |
| 5 | HttpFileCollectionValueProvider | HTTP ファイルのコレクション |
- その他の ValueProvider
| 項番 | ValueProvider | 値取得元 |
|---|---|---|
| 1 | JsonValueProvider | Json |
| 2 | CookieValueProvider | Cookie |
| 3 | SessionValueProvider | Session |
| 4 | ServerVariablesValueProvider | ServerVariables |
| 5 | TempDataValueProviderFactory | TempData |
移行メモ(表記): 原文の「ValuProvider」は
ValueProviderの表記揺れと判断し統一した。
-
ValueProvider の処理順
上記の既定の ValueProvider は Controller.ValueProvider の順番と同じ
(同じキー名の場合、ValueProvider の順に先勝になる) -
ValueProvider の追加
以下の ValueProviderFactory で順に新規 ValueProvider を追加できる。- 実装(Global.Application_Start に実装)
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
ValueProviderFactories.Factories.Add(new CookieValueProviderFactory());
ValueProviderFactories.Factories.Add(new SessionValueProviderFactory());
ValueProviderFactories.Factories.Add(new ServerVariablesValueProvider());
ValueProviderFactories.Factories.Add(new TempDataValueProviderFactory());-
参考
- ValueProviderFactory クラス (System.Web.Mvc)
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.valueproviderfactory
- ValueProviderFactory クラス (System.Web.Mvc)
-
カスタム ValueProvider の自作
カスタムの ValueProvider を実装するために以下の I/F を使用する。- IValueProvider(値プロバイダーに必要なメソッドを定義)
- IEnumerableValueProvider(列挙に必要なメソッドを定義)
- IUnvalidatedValueProvider(検証スキップに必要なメソッドを定義)
補足(「先勝ち」が事故のもとになる): 同じ名前のキーが
フォームとクエリ文字列の両方にある場合、
上表の順(Form が先)で決まる。POST /Edit?id=1 (クエリ文字列に id=1) Body: id=99 (フォームにも id=99) → FormValueProvider が先なので 99 が採用されるどこから取るかを明示するのが安全である。
// ASP.NET MVC 5 public ActionResult Edit([FromUri] int id, MyModel model) { } // ASP.NET Core(属性が整理されている) public IActionResult Edit([FromRoute] int id, [FromForm] MyModel model) { }ASP.NET Core のバインド元属性:
属性 取得元 [FromRoute]ルート パラメータ [FromQuery]クエリ文字列 [FromForm]フォーム [FromBody]リクエスト本文(JSON) [FromHeader]ヘッダー [FromServices]DI コンテナー(ASP.NET Core における DI)
[ApiController]を付けると、これらが規約で自動推論される
(複合型は[FromBody]、単純型は[FromQuery]等)。
-
ModelBinder は、ValueProvider から必要なデータを取得し、
Model に対して値の設定を行う。 -
DefaultModelBinder は、前述の「既定の ValueProvider」から必要なデータを検索する。
-
カスタム ModelBinder の自作も可能。
前述の ModelBinder の動作を制御する。
通常、
- コレクション系は Form
- プリミティブ型は QueryString
から取得するようになっているが、
以下の属性を Action Method の引数に対して使用すると、この動作を変更できる。
-
FromUri 属性
QueryString から値を取得するように制御。 -
FromBody 属性
Form から値を取得するように制御。
移行メモ(
FromBodyの説明): 原文は
「FromBody 属性:Form から値を取得するように制御」としているが、
正確には[FromBody]は「リクエスト本文(JSON / XML 等)から取得する」
という意味である(Web API 由来の属性)。
フォーム データの取得は既定の動作であり、
ASP.NET Core では[FromForm]が別途用意されている。
ASP.NET MVCは、ASP.NET Web Formsと比べて、
オーバーポスティング攻撃を受け易いため引数を明記する仕組み。
-
基礎
-
Bind 属性の引数
- Prefix : Form の name 属性の Prefix
http://stackoverflow.com/questions/1317523/how-to-use-bind-prefix - Include : Bind 対象の Property 名(カンマ区切り)
- Exclude : Bind 禁止の Property 名(カンマ区切り)
- Prefix : Form の name 属性の Prefix
-
設定方法
Action Methodに以下の様な属性を付与する。[Bind(Include = "PropName1, PropName2...")][Bind(Exclude = "PropName3, PropName4...")][Bind(Include = "PropName1, PropName2...", Exclude = "PropName3, PropName4...")]
-
-
応用(コレクションを Bind するときの name or key の設定方法)
http://qiita.com/kazuhisam3/items/94542f6d7ccf3acca41c- 配列 or リスト系
VariableName[n].PropertyName[n].PropertyName
- Dictionary 系 :
-
VariableName[n].Key,VariableName[n].Value.PropertyName -
[n].Key,[n].Value.PropertyName
-
- 配列 or リスト系
補足(ASP.NET Core では
Includeが無くなった):[Bind]は
ASP.NET Core にもあるが、引数の形が変わっている。// ASP.NET MVC 5 public ActionResult Create([Bind(Include = "Name,Email")] User user) // ASP.NET Core(プロパティ名を直接列挙。Exclude は無い) public IActionResult Create([Bind("Name,Email")] User user)
Excludeが廃止されたのは、
**「除外方式は漏れる」**という前述の理由による。
ホワイトリスト(列挙)のみが残された、と読める。なお、推奨は依然として ViewModel(DTO)の分離である。
-
モデルバインディング
- ASP.NET モデルバインディング - Qiita
http://qiita.com/kazuhisam3/items/94542f6d7ccf3acca41c - ASP.NET WEB API モデルバインド その1 値の取得先 - miso_soup3 Blog
http://miso-soup3.hateblo.jp/entry/20130204/1359976197 - ASP.NET MVC - ASP.NET MVC モデル バインディングの特長と問題点
https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2012/august/asp-net-mvc-the-features-and-foibles-of-asp-net-mvc-model-binding
- ASP.NET モデルバインディング - Qiita
-
DefaultModelBinder
- 強力になったDefaultModelBinder
http://takepara.blogspot.jp/2009/02/defaultmodelbinder.html
- 強力になったDefaultModelBinder
-
ValueProvider
- ValueProviderを扱うときに気をつけること - miso_soup3 Blog
http://miso-soup3.hateblo.jp/entry/20120625/1340633706
- ValueProviderを扱うときに気をつけること - miso_soup3 Blog
- Controller の ActionMethod と View の間に、ポストバック的な復元動作がある場合に便利。
- Model クラスを使う(Post の場合のみ)。
@model で定義した Model プロパティと、
のHtml ヘルパーを使用する。
以下のように双方向バインドする。
- Controller
return View(vm);- View スクリプト
@model ViewModel
・・・
Html.TextBoxFor(model => model.Category)
通常、Model プロパティは、Model でアクセスするが、
ここでの model はラムダ式の仮引数名なので自由。
移行メモ(表記): 原文の
@Model ViewModelは
**ディレクティブとしては@model(小文字)**が正しい
(@Modelは「Model プロパティ」を指す)。
原文自身がModel プロパティの節で
@model ViewModelClassと書いているため、
こちらを正として統一した。
またHtml.TextBoxFor?(...)の?は入力誤りと判断し削除した。
-
XxxxxFor メソッドは、モデルバインディングに対応する
- 要素名(id, name 属性)を自動で設定するため、
- 要素名(id, name 属性)を指定する引数が無い。
-
参考
- Label/TextBox/TextArea/Password/Hidden/ RadioButton/CheckBoxメソッド[Razor] - Build Insider
http://www.buildinsider.net/web/bookaspmvc5/040207 - TextBoxFor/TextAreaFor/PasswordFor/ HiddenFor/RadioButtonFor/CheckBoxForメソッド[Razor] - Build Insider
http://www.buildinsider.net/web/bookaspmvc5/040203
- Label/TextBox/TextArea/Password/Hidden/ RadioButton/CheckBoxメソッド[Razor] - Build Insider
Controller の Action Method は、View の選択と指示として、
ActionResult クラスの
オブジェクトを返す必要がある。
Action Method で、
return View();と、View を指定しない overload で呼び出すと、
/Views/コントローラ名/アクション名.cshtml
を呼び出す。
ActionResult クラスには、以下の種類が存在する。
| 項番 | 種類 | 概要・用途 | コード例(ヘルパー・メソッド) |
|---|---|---|---|
| 1 | ViewResult | 指定された全体 View の表示を指示する。 基本的には HTML.BeginForm の場合に使用する。 |
return View("Result");("Result" は全体 View 名) |
| 2 | PartialViewResult | 指定された部分 View の表示を指示する。 基本的には Ajax.BeginForm の場合に使用する。 |
return PartialView("Result");("Result" は部分 View 名) |
| 3 | RedirectResult | 指定した URL にリダイレクトする場合に使用する。 | return Redirect("http://www.wings.msn.to/"); |
| 4 | RedirectToActionResult | 指定した Controller, Action にリダイレクトする場合に使用する。 | return RedirectToAction("Index"); |
| 5 | RedirectToActionResult | 指定したルートパラメタ(routeName, routeValues)にリダイレクトする場合に使用する。 | return RedirectToRoute("View Product", new { ProductName = <商品名> }); |
| 6 | FilePathResult | 指定されたパスの内容をファイルとして出力 | return File(@"C:\temp\file.zip", "application/zip", "file.zip"); |
| 7 | FileContentResult | byte 配列の内容をファイルとして出力 | return File(bytes, "application/pdf"); |
| 8 | FileStreamResult | ストリームの内容をファイルとして出力 | return new FileStreamResult(fileStream, "application/pdf"); |
| 9 | ContentResult | プレーン・テキストを出力(CSV 出力等) | return Content("こんにちは、世界!", "text/plain"); |
| 10 | JsonResult | 指定されたコンテンツを JSON として出力(Ajax 通信) | return Json(JsonConvert.SerializeObject(result), JsonRequestBehavior.AllowGet); |
| 11 | JavaScriptResult | 指定されたコンテンツを JavaScript スクリプトとして出力 | return JavaScript(code); |
| 12 | EmptyResult | 何もしない | - |
移行メモ(表の展開): 原文は 5 行目の「種類」列を
PukiWiki のセル結合(~)で 4 行目と共有していたが、
GitHub Markdown にセル結合が無いため同じ値を展開した。
また"C:\temp\file.zip"は C# のリテラルとして不正なので
@"C:\temp\file.zip"に修正した。
-
c# - RedirectToAction and RedirectToRoute - Stack Overflow
http://stackoverflow.com/questions/8944355/redirecttoaction-and-redirecttoroute -
参考
-
ActionResult クラス
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.actionresult -
ASP.NET MVC ActionResultの派生型 | 宇宙仮面の研究室
https://uchukamen.wordpress.com/2011/02/06/asp-net-mvc-actionresult%E3%81%AE%E6%B4%BE%E7%94%9F%E5%9E%8B/ -
連載:ASP.NET MVC入門:第3回
ActionResultオブジェクトでアクション操作も自由自在 - @IT
-
補足(ASP.NET Core での戻り値): ASP.NET Core では
IActionResultが基本で、ActionResult<T>も使える。// 型を明示できる(Swagger 生成にも効く) public ActionResult<Product> Get(int id) => _repo.Find(id) is { } p ? p : NotFound();JSON の返し方が簡潔になっている点も違いである。
// ASP.NET MVC 5:JsonRequestBehavior が必要だった return Json(obj, JsonRequestBehavior.AllowGet); // ASP.NET Core:GET でも普通に返せる return Ok(obj); // または return obj;([ApiController] 時)
JsonRequestBehavior.AllowGetが不要になったのは、
かつての JSON ハイジャック対策が
現在のブラウザでは不要になったためである。なお、原文の例が
JsonConvert.SerializeObjectを渡しているのは
二重シリアライズになるため注意が要る
(Json()自体がシリアライズするので、オブジェクトをそのまま渡す)。
View ではなく、HTTP 状態コードを返す。
| 項番 | 種類 | 概要・用途 | コード例(ヘルパー・メソッド) |
|---|---|---|---|
| 1 | HttpStatusCodeResult | 任意の HTTP 応答コードをセット | - |
| 2 | HttpUnauthorizedResult | HTTP 応答コード「401 Unauthorized」をセット | - |
| 3 | HttpNotFoundResult | HTTP 応答コード「404 NotFound」をセット | - |
- System.Web.Mvc.HttpStatusCodeResult
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.httpstatuscoderesult- System.Web.Mvc.HttpNotFoundResult
- System.Web.Mvc.HttpUnauthorizedResult
可能
-
フィルタ属性は、ActionMethod に、以下の様な共通的な処理を追加するために使用する。
- 認証処理
- 例外処理
- ロギング
- , etc.
-
フィルタ属性は、以下に設定可能である。
| 項番 | 適用範囲 | 設置場所 | 説明 |
|---|---|---|---|
| 1 | ActionMethod Filter | ActionMethod | ActionMethod 単位 |
| 2 | Controller Filter | Controller | Controller 単位 |
| 3 | Global Filter | FilterConfig | Application 単位 |
- 以下のフィルタ属性分類があり、実装される処理は項番の順番に呼び出される。
| 項番 | 分類 | 実装する I/F | 実装する処理 |
|---|---|---|---|
| 1 | 認証 | IAuthenticationFilter | 認証に関係する処理 |
| 2 | 承認 | IAuthorizationFilter | 承認(認可)に関係する処理 |
| 3 | Action | IActionFilter | ActionMethod の開始処理の前後処理 |
| 4 | Result | IResultFilter | ActionMethod の終了処理の前後処理 |
| 5 | 例外 | IExceptionFilter | 例外処理 |
| 6 | Override | IOverrideFilter | 上位フィルタを上書き |
- 標準のフィルタ属性には以下の様なものがある。
| 項番 | 分類 | 属性 | 概要 |
|---|---|---|---|
| 1 | 承認 | Authorize 属性 | 認証済みアクセス(Cookie 認証、Token 認証) |
| 2 | 承認 | ChildActionOnly属性 | 子アクションとしてのみ呼び出し可能に設定 |
| 3 | 承認 | RequireHttps 属性 | HTTPS アクセスのみ呼び出し可能に設定 |
| 4 | 承認 | ValidateInput 属性 | XSS 対策に使用する。 |
| 5 | 承認 | ValidateAntiForgeryToken 属性 | CSRF 対策に使用する。 |
| 6 | 例外 | HandleError 属性 |
<customErrors mode="On or RemoteOnly"/>+ FilterConfig に設定した時のカスタムエラーページの定義 |
| 7 | Action/例外/Result | OutputCache 属性 | 出力キャッシュルールの定義 |
| 8 | Action/Result | AsyncTimeout 属性 | 非同期処理のタイムアウトの定義 |
| 9 | Override | OverrideAuthentication 属性 | グローバル・モデルなど上位で定義されたフィルタを上書き |
| 9 | Override | OverrideAuthorization 属性 | 同上 |
| 9 | Override | OverrideAction 属性 | 同上 |
| 9 | Override | OverrideResult 属性 | 同上 |
| 9 | Override | OverrideException 属性 | 同上 |
移行メモ(表の展開): 項番 9 の Override 系 5 行は、
原文ではセル結合(~)で項番・分類・概要を共有していたため、
値を展開して掲載した。
-
フィルタ属性は、自作可能。
-
参考
- Filters(ASP.NET Core)
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/controllers/filters
- Filters(ASP.NET Core)
補足(ASP.NET Core のフィルターとミドルウェアの使い分け): 概念は
ほぼそのまま引き継がれているが、ミドルウェアという選択肢が増えた
(HttpApplication(Global.asax)、HttpModule、HttpHandler)。【ミドルウェア】 すべてのリクエストが通る。MVC の外側 → 静的ファイル、認証、CORS、圧縮、例外処理 【フィルター】 MVC のパイプライン内。ModelState 等にアクセスできる → 認可、モデル検証、アクション固有のログ判断の目安: MVC の情報(
ModelState、アクション名、
ルート値)が要るならフィルター、要らないならミドルウェア。主な変更点:
ASP.NET MVC 5 ASP.NET Core IAuthenticationFilter廃止(認証はミドルウェアへ) HandleError属性UseExceptionHandlerミドルウェアOutputCache属性[ResponseCache]/ 出力キャッシュ(.NET 7+)AsyncTimeout属性CancellationTokenを受け取るフィルターの DI [ServiceFilter]/[TypeFilter]で注入できる// DI を効かせたフィルター [ServiceFilter(typeof(MyAuditFilter))] public IActionResult Index() { ... }
Controller からの ActionMethod の呼び出しを制御する。
| 項番 | 属性 | 概要 |
|---|---|---|
| 1 | HttpXxxxx 属性 | Action Method が受け付ける HTTP Method を指定 |
| 2 | AcceptVerbs 属性 | Action Method が受け付ける1つ以上の HTTP Method を指定 |
| 3 | NonAction 属性 | Action Method でないことを明示する。 |
| 4 | ActionName 属性 | Action Method 名と別名の Action Name を付与する。 |
セレクタ属性は、自作可能。
この技術の登場の背景には C10k problem (C10K 問題)と
言うものがある模様。
-
ASP.NET MVC での非同期コントローラーの使用
https://learn.microsoft.com/ja-jp/previous-versions/aspnet/ee728598(v=vs.98)-
非同期 Controller を使用すると、async/awaitを使用し、
Web サーバのスレッド枯渇を防ぐことができる。 -
実行に時間のかかる CPU バインド以外の要求に非同期 Controller を使用すると、
Web サーバの待機スレッドを他に転用可能になるので、Web サーバのスレッド数を節約し、
スレッド枯渇による「HTTP 503 (サーバーがビジー状態です。)」を防止できる。 -
図から、リクエスト処理開始時とレスポンス処理終了時のスレッドが変わることが解る。
後のスレッドは独立したスレッドプールから取得する。複雑なのでオーバーヘッドがある。
-
内部的には、I/O 完了ポートを使用しているものと思われる。
-
旧(AsyncController を継承する。)
- AsyncController を継承する。
- 以下の命名規約を守った Action Method を定義する。
- ActionNameAsync
- ActionNameCompleted(delegate)
-
Task<ActionResult> を返す。
MVC 4 以降であれば、以下のように書ける。- Action Method 内部で Task を使用する場合、
public Task<ActionResult> Index()- Action Method 内部でasync/awaitキーワードを使う場合、
public async Task<ActionResult> Index()補足(ASP.NET Core では非同期が既定): ASP.NET Core では
AsyncControllerという区別自体が無く、すべてのコントローラーが
async Task<IActionResult>を返せる。public async Task<IActionResult> Index(CancellationToken ct) { var items = await _repo.ListAsync(ct); return View(items); }また、ASP.NET config で述べた通り、
同期コンテキストが撤廃されたため、
async/await のデッドロック問題も構造的に解消している。
CancellationTokenを引数に取ると、
クライアントが接続を切った時点で処理を打ち切れる
(HttpContext.RequestAbortedが自動でバインドされる)。
- 非同期コントローラを使ってみた - しばやん雑記
http://blog.shibayan.jp/entry/20100715/1279204349 - ASP.NET MVC 4 の新機能、タスク対応の非同期コントローラを使う - しばやん雑記
http://blog.shibayan.jp/entry/20111105/1320495813
ここでは、B 層クラスではなく、
「B 層クラスが返す View に渡される
Entity, Bean, POCO 的なクラス(ViewModel)」
について説明する。
Controller から View にデータを渡すときに使用する入れ物的なモノ。
- 参考
- ViewData vs ViewBag vs TempData - 夜になったら寝る
http://kyabatalian.hatenablog.com/entry/2015/12/05/232504
- ViewData vs ViewBag vs TempData - 夜になったら寝る
- ViewBag は dynamic object。
- ViewData は Dictionary。
-
TempData は Dictionary。
-
TempData は保持されるため、リダイレクト先に値を渡したいときなどに使う。
-
TempData の詳しい動作は、以下が参考になる。
- ASP.NET MVC TempData は"次のリクエスト"以降も参照できる - miso_soup3 Blog
http://miso-soup3.hateblo.jp/entry/2013/12/14/070356
- ASP.NET MVC TempData は"次のリクエスト"以降も参照できる - miso_soup3 Blog
補足(3 つの関係と、使うべきでない理由): 実体としては、
ViewBag … ViewData の dynamic ラッパー(同じ物を見ている) ViewData … ViewDataDictionary(string → object) TempData … ITempDataDictionary。「1 回読むまで」保持される
生存範囲 型安全 ViewBag / ViewData 同一リクエスト内 × 実行時エラー TempData 次のリクエストまで(読むと消える) × ViewModel( @model)同一リクエスト内 ○ コンパイル時に検出 原則は ViewModel を使うこと。
ViewBagはタイプミスが実行時まで分からない(dynamicのため)。
TempDataの注意点:
- 保存先が セッション または Cookie
→ セッションを無効にしていると使えない- 読んだ時点で消える(
Peek()なら消えない、Keep()で保持)- ASP.NET Core では
[TempData]属性でプロパティに付けられる[TempData] public string Message { get; set; }リダイレクト後の「登録しました」といったメッセージ表示
(Post/Redirect/Get パターン)が主用途である。
View スクリプトから強く型付けされた ViewModel を参照する際に使用する。
- 次の旧構文を使用している場合は、
@inherits System.Web.Mvc.WebViewPage<ViewModelClass>
- 次の構文に置き換える。
@model ViewModelClass
-
@model キーワードで指定した ViewModel に対しては、Model プロパティでアクセスする。
- Controller から View に Model を渡す。
return View(vm);- View スクリプトで Model を使う。
@Model.PropertyName
移行メモ(誤記): 原文の「@Model ViewModel」は、
直前の@model ViewModelClass(ディレクティブ)と
混同した記述と判断し、@Model.PropertyName(プロパティ参照)に修正した。
- ASP.NET MVC 3: Razorの@model新キーワード:CodeZine(コードジン)
https://codezine.jp/article/detail/5549- ScottGu's Blog - ASP.NET MVC 3: New @model keyword in Razor
https://weblogs.asp.net/scottgu/asp-net-mvc-3-new-model-directive-support-in-razor
- ScottGu's Blog - ASP.NET MVC 3: New @model keyword in Razor
View には、
Web ページのビューエンジンには以下の 2 つのものがある。
従来の ASP.NET と同様、
式やコードブロックを コード・ナゲット(<% ~ %>)で囲む記述形式。
View の拡張子も、従来の ASP.NET と同様、「*.aspx」で表される。
ASP.NET MVC 3 で登場したビューエンジン。
式やコードブロックの先頭に「@」を付与する記述形式で、
冗長なコード・ナゲット(<% ~ %>)が不要になる。
View の拡張子は、C# の場合は「.cshtml」、VB の場合は「.vbhtml」で表される。
補足(現在は Razor のみ): ASPX ビューエンジンは
ASP.NET Core MVC には移植されなかった。
現在の選択肢は Razor(.cshtml)のみである。
VB の.vbhtmlも ASP.NET Core では非対応
(Razor は C# のみ)。Razor 自体はその後も発展し、
技術 内容 Razor Pages ページ単位( .cshtml+.cshtml.cs)。MVC より軽量Razor コンポーネント Blazor( .razor)Razor クラス ライブラリ ビューを NuGet パッケージで配る といった形に広がっている。
- C# Razor構文 基礎文法 総まとめ - @IT
http://www.atmarkit.co.jp/fdotnet/rapidmaster/rapidmaster_03/rapidmaster_03.html - 第5回 新しいビュー・エンジン「Razor」の基本を理解しよう - @IT
http://www.atmarkit.co.jp/fdotnet/aspnetmvc3/aspnetmvc3_06/aspnetmvc3_06_01.html - ASP.NET MVC 3 開発入門 (12) - Razor の文法 - しばやん雑記
http://blog.shibayan.jp/entry/20110317/1300294985 - ASP.NET MVC のビューは ASPX ではなく Razor を
http://miso-soup3.hateblo.jp/entry/2013/12/02/030906
通常の View はコレ。
部分 View は、「Partial View」ともいわれ、以下の用途で使われる。
- 画面の共通化のため
ASP.NET のユーザーコントロールのように、共通的な画面コンポーネントを部品化しておくもの。 -
Ajax.BeginForm の部分更新の範囲を表すため
Ajax.BeginForm を使用した非同期処理の場合、
部分更新の範囲を部分 View で定義する。
- /View/Shared/_XXXXPartial.cshtml
- /View/Controller名/_XXXXPartial.cshtml (優先)
- 通常
@Html.Partial("_XXXXPartial", model)
- 検索機能無し(仮想パス)
@RenderPage("~/View/Controller名/_XXXXPartial.cshtml", model)
- HTML 文字列を戻さず、応答ストリームに直接書き出す。
ViewDataDictionary の独自のコピーを取得するので、親の ViewData には影響を与えない。
@{ Html.RenderPartial("_XXXXPartial", model); }
- 参考
- .net - Html.Partial vs Html.RenderPartial & Html.Action vs Html.RenderAction - Stack Overflow
http://stackoverflow.com/questions/5248183/html-partial-vs-html-renderpartial-html-action-vs-html-renderaction
- .net - Html.Partial vs Html.RenderPartial & Html.Action vs Html.RenderAction - Stack Overflow
補足(ASP.NET Core では
<partial>/@await):Html.Partialは
ASP.NET Core では非推奨(同期実行によるデッドロックの恐れ)で、
次のいずれかを使う。@* ① タグ ヘルパー(推奨) *@ <partial name="_XXXXPartial" model="Model" /> @* ② 非同期版 *@ @await Html.PartialAsync("_XXXXPartial", Model) @{ await Html.RenderPartialAsync("_XXXXPartial", Model); }さらに、ビュー コンポーネント(View Component)が
「子アクション」の後継として用意されている(次節を参照)。
- 子 Action Method の定義
- 子 Action Method の定義は、部分 View が固有データを必要とする場合に追加する。
- 通常、全体 View を呼び出す Controller に、部分 View の子 Action Method の定義を追加する。
- Action Method の実行を部分 View の呼び出し時に限定する場合、
[ChildActionOnly] 属性を追加する。 - 子 Action Method が ActionResult を返す場合、
PartialView() ヘルパー・メソッドを使用する。
[ChildActionOnly]
public ActionResult Current()
{
・・・
return PartialView("_XXXXPartial", partialViewModel);
}移行メモ(誤字): 原文の「ActionResult を帰す」は返す、
「PartialView() ペルパー・メソッド」はヘルパーの
誤変換と判断し修正した。
- 子 Action Method の呼び出し
- 通常
@Html.Action("XXXX", model)
- HTML 文字列を戻さず、応答ストリームに直接書き出す。
ViewDataDictionary の独自のコピーを取得するので、親の ViewData には影響を与えない。
@{ Html.RenderAction("XXXX", model); }
補足(ASP.NET Core ではビュー コンポーネント):
Html.Action/
[ChildActionOnly]は ASP.NET Core には無い。
代わりに ビュー コンポーネントを使う。public class CartViewComponent : ViewComponent { private readonly ICartService _svc; // ← DI が効く public CartViewComponent(ICartService svc) => _svc = svc; public async Task<IViewComponentResult> InvokeAsync() => View(await _svc.GetAsync()); }<vc:cart /> @* または *@ @await Component.InvokeAsync("Cart")
子アクション(MVC 5) ビュー コンポーネント 実体 コントローラーのアクション 専用クラス ルーティング URL から直接呼べてしまう( [ChildActionOnly]で防ぐ)呼べない(安全) DI しにくい コンストラクタ注入 非同期 しにくい asyncが自然URL から直接叩けてしまうという子アクションの弱点が
構造的に解消されている点が大きい。
(View ヘルパーという呼称もあるようだが、
ここでは Html ヘルパーという呼称に統一する)
従来の ASP.NET では、ASP.NET Web コントロールを使用して、
ラベルやテキストボックスなどのコントロールを表示していたが、
ASP.NET MVC では、基本的に、以下の様な便利な機能が実装されている
Html ヘルパーを使用する。
- Html ヘルパーは、サニタイジング処理などを同梱する。
- また、Html ヘルパーは、DataAnnotation の内容を元に表示内容を制御できる。
ASP.NET MVC では、主に以下のような Html ヘルパーが使用できる。
- 表示のための Html ヘルパー
| 項番 | 用途 | Html ヘルパー |
|---|---|---|
| 1 | データの表示 | Html.DisplayFor |
| 2 | 入力可能なデータの表示 | Html.EditorFor |
| 3 | ラベルの表示1 | Html.DisplayNameFor |
| 4 | ラベルの表示2 | Html.LabelFor |
- HTML タグに対応した Html ヘルパー
| 項番 | HTML タグ | Html ヘルパー |
|---|---|---|
| 1 | フォーム <form>
|
HTML.BeginForm または Ajax.BeginForm |
| 2 | リンク <a>
|
Html.ActionLink |
| 3 | テキストボックス <input type="text">
|
Html.TextBox または Html.TextBoxFor |
| 4 | テキストエリア <textarea>
|
Html.TextArea または Html.TextAreaFor |
| 5 | パスワード <input type="password">
|
Html.Password または Html.PasswordFor |
| 6 | チェックボックス <input type="checkbox">
|
Html.CheckBox または Html.CheckBoxFor |
| 7 | ドロップダウンリスト <select>
|
Html.DropDownList または Html.DropDownListFor |
| 8 | リストボックス <select>
|
Html.ListBox または Html.ListBoxFor |
| 9 | ラジオボタン <input type="radio">
|
Html.RadioButton または Html.RadioButtonFor |
| 10 | 隠しフィールド <input type="hidden">
|
Html.Hidden または Html.HiddenFor |
- URL 生成
| 項番 | 用途 | Html ヘルパー |
|---|---|---|
| 1 | 「~/...」を仮想パスに変換 | Url.Content |
| 2 | Controller、ActionMethod 名などから仮想パスを生成 | Url.Action |
| 3 | RouteValueDictionary から仮想パスを生成 | Url.RouteUrl |
移行メモ(表の見出し): 1 つ目と 3 つ目の表は
原文の見出しが「HTMLタグ」となっていたが、
内容が HTML タグではないため**「用途」**に改めた。
- なお、ボタンを生成する Html ヘルパーはないため、
直接<input type="button">または<input type="submit">を記述する。
Html.xxxx と Html.xxxxFor の 2 種類の Html ヘルパーがある。
-
Html.xxxx
Model から View への単方向バインディング。
Html.TextBox("Category") のように、
プロパティを文字列でマップ指定する場合は、
"For" がつかない Html ヘルパーを使用する。 -
Html.xxxxFor
Model ⇔ View の双方向バインディング。- ...For という名称の Html ヘルパーは、View と Model の間での
双方向バインディングを行う。 - Html.TextBoxFor(model => model.Category) のように、
プロパティをラムダ式でマップ指定する場合は、"For" で終わる Html ヘルパーを使用する。
- ...For という名称の Html ヘルパーは、View と Model の間での
ただし、HTTP を経由しての双方向バインディングになるので、
処理方式的には、以下のような処理シーケンスになる。
- POST 時に、Html.TextBoxFor などへの入力値を、自動的に Model に復元する。
- Controller は、復元された Model から情報を取得して処理を行う。
補足(
For付きを使う理由は型安全性): 原文が挙げる
「双方向バインディング」に加え、コンパイル時に検出できる点が大きい。@* 文字列指定:タイプミスが実行時まで分からない *@ @Html.TextBox("Categoly") @* ラムダ式:コンパイル エラーになる *@ @Html.TextBoxFor(m => m.Category)ASP.NET Core では タグ ヘルパーが推奨される。
<input asp-for="Category" class="form-control" /> <label asp-for="Category"></label> <span asp-validation-for="Category"></span>HTML に近い書き方のままバインドできるため、
デザイナーとの分業がしやすい、というのが移行の主な動機である。
Html ヘルパー タグ ヘルパー 見た目 C# のメソッド呼び出し ほぼ HTML 属性の追加 匿名オブジェクト( new { @class = "..." })そのまま属性を書く IntelliSense あり あり(HTML 補完も効く)
ヘッダーやフッター、サイドメニューなどをアプリケーションで共通的に表示させたい場合、
マスターページを使用してレイアウトを共通化させることができる。
マスターページには、画面ごとに個別実装が必要な箇所を定義する。
- ビューエンジンが ASPX の場合は、従来の ASP.NET と同様 ContentPlaceHolder を使用する。
- ビューエンジンが Razor の場合は、RenderBody を使用してメインのコンテンツ領域を定義する。
- アプリケーション共通
- 配置場所
- /View/Shared/_Layout.cshtml
- 使用方法
- /View/Shared/_ViewStart.cshtml の Layout プロパティ経由で呼び出される。
- 配置場所
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
- View 単位
- 配置場所
- /View/Shared/_XXXXLayout.cshtml
- /View/Controller名/_XXXXLayout.cshtml (優先)
- 使用方法
- 配置場所
メインのコンテンツ領域以外に、画面ごとに個別実装が必要な箇所を定義する場合、
RenderSection を使用して「セクション」と呼ばれるサブのコンテンツ領域を定義する。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<!-- メインのコンテンツ領域を描画する場所を定義 -->
@RenderBody()
@Scripts.Render("~/bundles/jquery")
<!-- セクション (サブのコンテンツ領域) を描画する場所を定義 -->
@RenderSection("scripts", required: false)
</body>
</html>- 例えば、上記のようにマスターページを定義した場合、
各コンテンツページでは以下のように実装する。
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml"; // 使用するマスターページを指定
}
@* メインのコンテンツ領域 *@
<h2>Index</h2>
@* セクション (サブのコンテンツ領域) *@
@section scripts{
<script type="text/javascript">
...
</script>
}
- 上記のように、セクションを実装する場合、
- C# の場合は「@section <セクション名> { ~ }」、
- VB の場合は「@Section <セクション名> ~ End Section」
で囲む必要がある。
- /View/Controller名/YYYY.cshtml の Layout プロパティ経由で呼び出される。
@{
Layout = "~/Views/Shared/_XXXXLayout.cshtml";
}
@{
Layout = "~/View/Controller名/_YYYYLayout.cshtml";
}
- Html ヘルパー・メソッドの第二引数に Layout スクリプト名を指定する。
return View("YYYY", "_XXXXLayout");return View("YYYY", "_YYYYLayout");移行メモ(誤記): 原文の
return View("YYYY, "_XXXXLayout");は
引用符の位置が誤っているため、
return View("YYYY", "_XXXXLayout");に修正した。
- 以下の section で選択する場合、
if 文などを使用して、Layout プロパティに設定する *Layout.cshtml を切り替える。
@{
Layout = "~/Views/Shared/_XXXXLayout.cshtml";
}
-
ActionResult で選択する場合、
if 文などを使用して、return View() の第二引数に設定する部分 View 名を切り替える。 -
RouteData で選択する場合、以下のように実装する。
if ((HttpContext.Current.Request.RequestContext.RouteData.Values["Controller"].ToString() == "Account")
&& (HttpContext.Current.Request.RequestContext.RouteData.Values["Action"].ToString() == "Login"))
{
Layout = "~/Views/Shared/_Layout2.cshtml";
}
else
{
Layout = "~/Views/Shared/_Layout.cshtml";
}- 参考
- Layout を変更する4種類の方法(ASP.NET MVC) - Jiro Laboratory
http://jirolabo.hatenablog.com/entry/2014/12/06/211528
- Layout を変更する4種類の方法(ASP.NET MVC) - Jiro Laboratory
BeginForm には以下の 2 つのものがある。
従来の ASP などで MVC 方式を採用した場合と同じ、画面全体を再描画する仕組み。
- リクエスト・レスポンスの度に、画面全体を再描画する。
-
ViewState相当の状態保存処理を独自実装する必要がある
(For 付きの Html ヘルパー(Html.xxxxFor)を使用する)。
@modelに置き換えられた。
View スクリプトから強く型付けされた ViewModel を参照する際に使用する。
マスタページを利用する際、定義された section を実装する。
View スクリプト内にHtml ヘルパーを定義。
補足:
@helperは ASP.NET Core の Razor では廃止された。
代替は タグ ヘルパー、ビュー コンポーネント、
または@functions内のローカル関数である。
- コードナゲット(インライン式)
式の値の出力- 通常のコードナゲット
@...
- 明示的なコードナゲット
@(...)
- コードブロック
- 値の代入
- メソッド呼び出し
- オブジェクトの生成
@{...}
-
制御構文(ネスト可能)
if, switch, while, for/foreach- if
@if(...) {
・・・
}
else if (...) {
・・・
}
else {
・・・
}- switch
@switch (i)
{
case 0:
・・・
break;
case 1:
・・・
break;
・・・
default:
・・・
break;
}- while
@while (flg)
{
・・・
}- for
@for (var i = 0; i < 10; i++)
{
・・・
}- foreach
@foreach (var obj in list)
{
・・・
}- 静的コンテンツ化
- 単一行
@:
- 複数行
<text>・・・</text>- コメント
- サーバーコメント
@* ... *@
- HTML コメント
<!-- ... -->補足(Razor の自動エスケープ): 重要な性質として、
@による出力は自動的に HTML エスケープされる。@Model.Name @* エスケープされる(安全) *@ @Html.Raw(Model.Html) @* エスケープされない(XSS に注意) *@
@Html.Rawを使う箇所は、必ず入力元を確認すること。
原文が「Html ヘルパーはサニタイジング処理などを同梱する」と
述べているのは、この自動エスケープを指している。
ASP.NET MVC のテンプレートは、グルーピングを目的に、
既定で以下のフォルダ構成となっている。
それぞれのフォルダには、以下のようにファイルを配置することが推奨されている。
| 項番 | フォルダ名 | 配置されるファイル | 備考 |
|---|---|---|---|
| 1 | App_Start | 起動時に、初期設定を行うモジュール | - |
| 2 | Contents | CSS | BundleConfig が使用しているため、既定の CSS ファイルは変更しない |
| 3 | Controllers | Controller | - |
| 4 | Models | Model | - |
| 5 | Scripts | JavaScript | BundleConfig が使用しているため、既定の JavaScript ファイルは変更しない |
| 6 | Views | View | 対応する Controller 名のフォルダ以下に、View のファイルを配置する 例 Views\(コントローラー名)\Index.cshtml |
補足(ASP.NET Core の既定構成): 大きく変わっている。
ASP.NET MVC 5 ASP.NET Core App_Start\*.csProgram.csに集約Content/Scriptswwwroot\(静的ファイルの公開ルート)Global.asax無し Web.configappsettings.json(.NET Core config)Controllers / Models / Views 同じ(またはフィーチャー単位に分割)
wwwrootの導入が大きな違いで、
**「ここに置いたものだけが公開される」**という明示的な境界ができた
(ASP.NET では逆に「公開したくないものを塞ぐ」設計だった)。
Area (区分) とは、ASP.NET MVC アプリケーションを論理的に分割する仕組みのことである。
(ASP.NET MVC プロジェクトをシステム全体とすると、
Area ごとにサブシステム (のようなもの) に分割できる。
App_Start のマップルートが追加されるような感じ、と理解すると分かりやすいかもしれない)
補足(Area は現在も使える): ASP.NET Core でも Area は健在で、
モジュラー モノリス的な分割の手段として使える。Areas/ Admin/ Controllers/ Views/ Models/ Shop/ Controllers/ Views/ Models/[Area("Admin")] public class UsersController : Controller { }app.MapControllerRoute( name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");ただし、規模が大きくなるなら
クラス ライブラリ(Razor クラス ライブラリ)に分ける方が
境界が明確になる、という選択肢もある
(VSソリューション プロジェクトの構成検討)。
Tags: 移行, .NET開発, ASP.NET, ASP.NET MVC
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。