-
Notifications
You must be signed in to change notification settings - Fork 0
MS_ASPNETMVCUsage
- 戻る(ASP.NET MVC)
「ASP.NET MVCの利用方法」は
「ASP.NET MVCの用語」と比べて、
少々高度な応用的トピックをまとめています。
補足(本ページの前提/最新化): ASP.NET MVCの用語 と同じく、
.NET Framework 版の ASP.NET MVC 5(System.Web.Mvc)を前提とする。
設計論(モジュール化、Controller の粒度)は
ASP.NET Core MVC でもそのまま通用するが、
API・属性・検証まわりには差異がある。各節で補足する。
まず、Model と View と Controller の役割について整理する。
-
Model:
- アプリケーションの基礎となるデータ構造(ViewModel)、
- およびそのデータを取得・加工する業務ロジック
-
View:
Model が保持するデータ(ViewModel)を参照し、ユーザーに表示する。 -
Controller:
- ユーザーからの入力を受け取り、
- Model に対してデータの取得・加工を指示する。
- その結果を受けて、View に表示を指示する。
ASP.NET MVC の開発は、ViewModel の ModelMetadata 駆動になる。
このため、ViewModel の設計が重要になるが、これを
- 画面をベースにするか?
- データモデルをベースにするか?
どちらをベースにするか?でモジュール化が異なってくる。
-
前者は、更に以下の2つに分類することができる。
- 画面単位の Controller を作成する場合、
これを「全体 View ごとに Controller を作成する方式」と言う。 - 機能単位の Controller を作成する場合、
これを「全体 View をまとめる 機能単位の Controller を作成する方式」と言う。
- 画面単位の Controller を作成する場合、
-
後者では、データ単位の Controller が作成される。
これを「スキャフォールディング方式」と言う。
「スキャフォールディング方式」は肥大しやすい傾向がある。
補足(この論点は現在も有効): 「画面ベースか、データモデル ベースか」
という問いは、フレームワークを問わず
設計の最初に決めるべき事柄であり続けている。【データモデル ベース(スキャフォールディング)】 エンティティ 1 つに Controller 1 つ、CRUD 4 画面 → 立ち上がりが速い → 業務が複雑化すると Controller が肥大する(原文の指摘通り) → 「画面 = テーブル」の対応が崩れると破綻する 【画面/機能ベース】 業務のまとまりごとに Controller → 実際の業務に沿う → 設計に手間がかかる**近年の潮流は「機能(フィーチャー)ベース」**で、
例えば次のような整理が広く使われる。
手法 内容 Vertical Slice Architecture 機能単位でフォルダを切る(Controller / ViewModel / Handler を同居) Razor Pages ページ単位( .cshtml+.cshtml.cs)= 画面ベースそのものMinimal API エンドポイント単位 特に Razor Pages は、原文が言う
「全体 View ごとに Controller を作成する方式」を
フレームワークとして正式化したものと言え、
画面ベースを選ぶなら Razor Pages の方が素直である。
Model (0..1) <---> (1) 全体 View
全体 View が、Model が保持するデータを何も表示しない状態。(静的なページなど?)
全体 View が、Model が保持するデータを参照し、ユーザーに表示している状態。
ただし、全体 View が参照できるのは「Model のプロパティ」のみであり、
全体 View から直接 Model のメソッドが呼ぶことはしない。
補足(この制約は重要): 「View から Model のメソッドを呼ばない」
という原則は、現在も守るべき指針である。@* 悪い:ビューから DB を引いている(N+1、例外処理の欠落、テスト不能) *@ @foreach (var id in Model.Ids) { <li>@Repository.Find(id).Name</li> } @* 良い:必要なデータは Controller で揃えて ViewModel に載せる *@ @foreach (var item in Model.Items) { <li>@item.Name</li> }ASP.NET Core における DI の
@injectの節でも
同じ注意をしている通り、**ビューは「表示するだけ」**にする。
全体 View (1..*) <---> (1) Controller
1 つの 全体 View に対して、1 つの Controller を対応させる考え方。
=「全体 View ごとに Controller を作成する方式」
その 全体 View からは、対応する Controller へのみリクエストを送る。
Controller の処理の結果は、対応する 全体 View にのみ指示を送る、というもの。
Controller の処理の結果を、別の 全体 View に表示させたい場合は、
Controller.RedirectToAction メソッドなどを使用して、
その 全体 View に対応した Controller に処理をリダイレクトする。
乱暴な言い方をすれば、従来の ASP.NET の WebForm に近い考え方、と言えるかもしれない。
-
画面 (*.aspx) が、全体 View に相当する
-
コードビハインド (*.aspx.cs, *.aspx.vb) が、Controller に相当する
-
画面を遷移するときは、Response.Redirect メソッドを使用して処理をリダイレクトする
-
メリット
- 従来の ASP.NET の経験がある人には、とっつきやすい可能性がある。
-
デメリット
- Controller クラスの数が膨大になる可能性がある。
補足(この方式は Razor Pages そのもの): 原文が
「Web Forms に近い」と評しているこの方式は、
ASP.NET Core の Razor Pages で正式にサポートされた。Pages/ Orders/ Index.cshtml ← 画面 Index.cshtml.cs ← その画面専用のハンドラー(PageModel) Edit.cshtml Edit.cshtml.cspublic class EditModel : PageModel { [BindProperty] public OrderVm Order { get; set; } public void OnGet(int id) { ... } // GET public IActionResult OnPost() { ... } // POST }原文が挙げるデメリット(クラス数が膨大)は解消していないが、
1 画面 = 1 フォルダにまとまるため、
Controller が別フォルダに散らばる MVC より見通しが良い。画面数が多い業務アプリでは Razor Pages、
API やリソース指向なら MVC / Minimal API、
というのが現在の使い分けの目安である。
複数の 全体 View に対して、1 つの Controller を対応させる考え方。
アプリケーションの
- 機能や、
- =「全体 View をまとめる 機能単位の Controller を作成する方式」
- データ構造に
対して、1 つの Controller を対応させる考え方。
- メリット
- まとまった業務ごとに Controller を作るので、Controller の数を抑えられる。
- デメリット
- 複数の 全体 View からの処理をすべて 1 つの Controller で受け付けるため、
その Controller のコード量が多くなる可能性がある。
- 複数の 全体 View からの処理をすべて 1 つの Controller で受け付けるため、
移行メモ(重複): 原文はこの節の最初の箇条書きで
「機能や、=『全体View ごとに Controller を作成する方式』」と
記していたが、前節の名称と重複しており文脈に合わないため、
「全体View をまとめる 機能単位の Controller を作成する方式」
(概要節で使われている呼称)に統一した。
たとえば、受注処理を行うアプリケーションを考えると、
「受注作成画面」・「受注内容更新画面」・「受注削除画面」など、
複数の 全体 View に分かれていても、同じ「受注」業務=受注エンティティ
に関するリクエストは 1 つの Controller が受け付ける、という考え方。
-
(1 つの Model に対して、その Model に関するリクエストを受け付ける
1 つの Controller、および CRUD を行う 4 つの 全体 View が作成される) -
スキャフォールディングの場合、M/V/C それぞれの多重度は、以下のようになる。
- 全体 View (4) <---> (1) Model
(1 つの Model に対して、CRUD を行う 全体 View がそれぞれ作成される) - Controller (1) <---> (1) Model
(1 つの Model に対して、その Model に関するリクエストを受け付ける Controller が 1 つ作成される) - 全体 View (4) <---> (1) Controller
(CRUD を行う 4 つの 全体 View は、1 つの Controller にのみリクエストを送る)
- 全体 View (4) <---> (1) Model
-
しかし、1 つの 全体 View で、CRUD 全てを実現できる
(CRUD ごとに 全体 View が分かれない) 場合は、以下のようになる。- 全体 View (1) <---> (1) Model
- Controller (1) <---> (1) Model
- 全体 View (1) <---> (1) Controller
Controller をどのような単位で作成するかにかかっている。
- 全体 View (1..*) <---> (1) Model
- Controller (1) <---> (1..*) Model
- 全体 View (1..*) <---> (1) Controller
- 全体 View (1..*) <---> (1) Model
- Controller (1) <---> (1..*) Model
- 全体 View (1) <---> (1) Controller
モジュール化の要約で紹介した、
何れかの方針に合わせて、Controller を作成する。
特定の HTTP メソッドのみを受け入れる属性を付与することができる。
なお、Action Method に HTTP メソッド属性を指定しなかった場合、
Action Method は、すべての HTTP メソッドを受け入れる。
AcceptVerbs 属性は、複数の HTTP メソッドを受け入れる Action Method の定義に使用する。
- AcceptVerbs 属性は、以下のように利用する。
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult XXXXX(・・・) { ・・・ }- 以下は、AcceptVerbs 属性で使用できる列挙型の列挙子の一覧
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.httpverbs
MVC 2 からは HttpGet, HttpPost, HttpPut, HttpDelete という 4 つの属性が追加された。
-
[HttpGet] メソッド属性
- 概要
- Get メソッドのみ受け入れる。
- それ以外の HTTP メソッドは受け入れない(404 が返る)。
- ユースケース
- Get で別画面に画面遷移する場合
- 入力項目が無い状態で、同一画面内で状態遷移する場合
- 概要
-
[HttpPost] メソッド属性
- 概要
- Post メソッドのみ受け入れる。
- それ以外の HTTP メソッドは受け入れない(404 が返る)。
- ユースケース
- フォームの入力項目を Controller に Post する場合。
- Post で別画面に画面遷移する場合。
- 概要
-
, etc.
補足(属性を明示すべき理由): 「指定しないと全メソッドを受け入れる」
という既定は、セキュリティ上の弱点になり得る。[HttpPost] を付け忘れた Delete アクション → GET /Order/Delete/1 でリンクを踏むだけで削除できてしまう → クローラーや、画像タグ(<img src="...">)で誘発される → CSRF 対策([ValidateAntiForgeryToken](#validateantiforgerytoken属性))も GET では効かない状態を変更するアクションには必ず
[HttpPost](または PUT/DELETE)を付ける
のが原則である。ASP.NET Core での違い:
ASP.NET MVC 5 ASP.NET Core 未指定時 全メソッドを受け入れる 同じ 不一致時 404 405 Method Not Allowed(より適切) AcceptVerbsあり あり( [AcceptVerbs("GET","POST")])複合指定 HttpVerbs.Get | Post[HttpGet, HttpPost]と並べる
Action Method 名と、外部に公開する Action Name とを別にする。
例えば
| 項番 | Action Method 名 | ActionName | 処理の内容 |
|---|---|---|---|
| 1 | Delete | Delete | 削除画面の初期表示処理 |
| 2 | DeleteConfirmed | Delete | 削除処理の実行 |
補足(この定型の意味): 上表は
スキャフォールディングが生成する定番の組である。GET /Order/Delete/1 → Delete(int id) … 確認画面を出す POST /Order/Delete/1 → DeleteConfirmed(int id) … 実際に削除するC# は「引数が同じで戻り値も同じ」メソッドを 2 つ定義できないため、
メソッド名を変え、[ActionName("Delete")]で URL 上の名前を揃えている。ASP.NET Core でも同じ手法が使えるが、
Razor Pages ではOnGet/OnPostで自然に分かれるため
この技巧が不要になる。
CSRF 対策に使用する。
複数ブラウザウィンドウ対応がなされているかどうかは未確認。
- View
@using (Html.BeginForm())
{
@* トークンを埋め込む *@
@Html.AntiForgeryToken()
}- Controller
// トークンを検証する
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed()
{
・・・
}- 参考
- 連載:ASP.NET MVC入門:第4回
フィルタ属性による認証/キャッシュ/セキュリティ対策の実装 (5/5) - @IT
http://www.atmarkit.co.jp/ait/articles/0908/14/news041_5.html - ASP.NET のセキュリティ対策について考える - しばやん雑記
http://blog.shibayan.jp/entry/20120526/1338001863 - ASP.NET の組み込み機能を活用し、Web 攻撃を回避する
https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2005/january/build-secure-asp-net-applications
- 連載:ASP.NET MVC入門:第4回
補足(原文の疑問への回答と、現在の既定): 原文が
「複数ブラウザウィンドウ対応がなされているか未確認」としている点について。対応している。ASP.NET の偽造防止トークンは
Cookie 側とフォーム側の 2 つを突き合わせる方式
(Double Submit Cookie 系)で、
タブやウィンドウごとに値が変わるわけではないため、
複数ウィンドウを開いても問題は起きない。Cookie: __RequestVerificationToken=(暗号化された値) Form: __RequestVerificationToken=(対になる値) → 両方が揃い、かつ対応していることを検証する → Cookie はブラウザ単位なので、タブ間で共通ただし、認証状態が変わると値が変わるため、
「別タブでログアウトした後、古いタブから POST すると失敗する」
という現象は起こり得る。ASP.NET Core での違い:
ASP.NET MVC 5 ASP.NET Core トークンの埋め込み @Html.AntiForgeryToken()が必要<form>タグ ヘルパーが自動で埋め込む検証 [ValidateAntiForgeryToken]を付ける既定で自動検証( [AutoValidateAntiforgeryToken]相当)除外 - [IgnoreAntiforgeryToken]属性名 ValidateAntiForgeryTokenValidateAntiForgeryToken(同名)@* ASP.NET Core:これだけでトークンが埋め込まれる *@ <form asp-action="Delete" method="post">「付け忘れ」が起きにくくなったのが大きな改善である。
API(JSON)で Cookie 認証を使う場合は、
ヘッダー経由で送る設定が別途要る。builder.Services.AddAntiforgery(o => o.HeaderName = "X-CSRF-TOKEN");
モジュール化の考え方のように、Model には 2 つの意味がある。
-
アプリケーションの基礎となるデータ構造
- POCO として作成する。
- XXXXViewModel という名称を付与する。
-
そのデータを取得・加工する業務ロジック
- 通常の業務ロジック・クラスとして作成する。
ココでは前者について言及する。
補足(ViewModel を分ける意義): 「XXXXViewModel という名称を付与する」
という原文の指針は、ASP.NET MVCの用語 で述べた
オーバーポスティング攻撃の対策でもある。エンティティ(DB のテーブルに対応) ↕ 詰め替え(AutoMapper / 手書き / Mapperless) ViewModel(画面に必要な項目だけ) ↕ モデル バインディング HTTP リクエストエンティティを直接バインドしないことで、
- 危険な項目(
IsAdmin、CreatedAt等)がそもそも存在しない- 画面固有の項目(確認用パスワード、表示用ラベル)を持てる
- DB のスキーマ変更が画面に波及しない
という利点が得られる。
System.ComponentModel.DataAnnotations 属性により、
- 検証機能
- テンプレート・ヘルパーと呼ばれる Html ヘルパー
- Controller のスキャフォールディング生成
の動作を制御する事ができる。
- 参考
- 第3回 モデル・バインドとアノテーション検証の実装 - @IT
http://www.atmarkit.co.jp/fdotnet/aspnetmvc3/aspnetmvc3_04/aspnetmvc3_04_01.html - 第6回 テンプレート機能でビュー開発を効率化 - @IT
http://www.atmarkit.co.jp/fdotnet/aspnetmvc3/aspnetmvc3_07/aspnetmvc3_07_01.html - ASP.NET MVC の ModelMetadata は奥が深い - しばやん雑記
http://blog.shibayan.jp/entry/20120108/1326030354
- 第3回 モデル・バインドとアノテーション検証の実装 - @IT
型情報を指定することで、
- 表示に関する制御属性
- 汎用的な検証属性
として使用される。
[DataType(DataType.XXXX)]-
参考
- DataType 列挙体 (System.ComponentModel.DataAnnotations)
https://learn.microsoft.com/ja-jp/dotnet/api/system.componentmodel.dataannotations.datatype- Password 列挙子
- Text 列挙子
- Url 列挙子
- ImageUrl 列挙子
- MultilineText 列挙子
- EmailAddress 列挙子
- PhoneNumber 列挙子
- PostalCode 列挙子
- Currency 列挙子
- CreditCard 列挙子
- Date 列挙子
- Time 列挙子
- DateTime 列挙子
- Duration 列挙子
- Html 列挙子
- Upload 列挙子
- Custom 列挙子
- DataType 列挙体 (System.ComponentModel.DataAnnotations)
補足(
DataTypeは「検証」ではない点に注意): 原文は
「汎用的な検証属性として使用される」としているが、
[DataType]単体では検証は行われない(表示のヒントが主目的)。[DataType(DataType.EmailAddress)] // ← 検証しない(type="email" になるだけ) public string Email { get; set; } [EmailAddress] // ← こちらが検証する public string Email { get; set; }
[EmailAddress]/[Phone]/[Url]などの
DataType派生属性は検証も行う(後述のDataType の派生の属性)。
原文が後段のDataType 属性の節で
「DataType 属性も検証属性として機能する」と述べているのは、
正確にはこの派生属性を指していると読むのが妥当である。
DataType 属性と同様に利用される。
-
参考
-
ASP.NET MVCのモデルでよく使う属性(アノテーション)リスト - Qiita
http://qiita.com/mrpero/items/607c31895d77815a77cb -
DataTypeAttribute クラス (System.ComponentModel.DataAnnotations)
https://learn.microsoft.com/ja-jp/dotnet/api/system.componentmodel.dataannotations.datatypeattribute- CreditCard 属性
- EmailAddress 属性
- EnumDataType 属性
- FileExtensions 属性
- Phone 属性
- Url 属性
-
表示名(Label 表示を行なう Html ヘルパーに使用される)
-
DisplayName
[DisplayName("xxxx")]
-
Display
- Name
[Display(Name = "xxxx")]
- ResourcesType(国際化対応用)
[Display(Name = "xxxx", ResourceType = typeof(yyyy))]
- Name
補足(
ResourceTypeはリソース ファイルと連携する): これが
国際化対応の入口になる。
ResourceTypeに指定するのは、.resxから生成される
厳密に型指定されたリソース クラスである。[Display(Name = "UserName", ResourceType = typeof(Resources.Labels))] public string UserName { get; set; } // → Labels.ja.resx の "UserName" が使われる詳細は リソースファイル、
および ASP.NET MVCの国際化対応を参照。ASP.NET Core では
IStringLocalizer方式も選べる。builder.Services.AddMvc() .AddDataAnnotationsLocalization(); // 検証メッセージも localize される
フォーマットの指定。
[DisplayFormat(DataFormatXXXX="YYYY")]- DisplayFormatAttribute クラス (System.ComponentModel.DataAnnotations)
https://learn.microsoft.com/ja-jp/dotnet/api/system.componentmodel.dataannotations.displayformatattribute- DataFormatString(書式指定文字列)
- ApplyFormatInEditMode(編集モードで書式を適用するかどうか)
- NullDisplayText(null の場合表示するテキスト)
- ConvertEmptyStringToNull(空文字列を null に変換)
- , etc.
- 独自の表示/編集テンプレート・ヘルパーを準備する。
- 例えば DateTime 型を指定した EditorFor で
使用する jQuery UI の Datepicker を適用する。
補足:
<input type="date">が全ブラウザで使えるようになったため、
jQuery UI の Datepicker を当てる必然性は薄れている
(jQuery UI 自体も開発終了。ASP.NET の Modernization 参照)。[DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime BirthDate { get; set; } // → EditorFor で <input type="date"> が出力される
- Model 間の Relation を設定した際、Model の表示名に使用している Column。
- Model 間の Relation は、ナビゲーション・プロパティで設定する。
検証属性については、コチラ。
AutoMapperを使用する。
移行メモ(表記): 原文の「Razer」は Razor の誤記と判断し修正した。
ASP.NET MVC には、<form> タグを生成する Html ヘルパーが 2 種類ある。
BeginForm ヘルパーは引数にコントローラー名、アクション名が付与でき、
指定したアクションメソッドにリクエストを送ることができる。
通常の <form> タグを生成する場合に使用する。
-
全体更新が多数を占める場合 (アクションメソッドの結果として、View 全体を更新する場合)。
-
画面リフレッシュにより、リクエスト・レスポンスのステータスを明確にしたい場合。
<form> タグに Ajax リクエスト用の属性が付与され、リクエストが非同期で処理される。
-
部分更新が多数を占める場合 (アクションメソッドの結果として、View の一部分のみを更新する場合)。
-
サーバー側処理が非常に重い業務の場合 (Ajax は非同期処理のため)。
-
画面入力状態を保持したまま POST 送信したい場合
- ViewStateがサポートされない MVC で、情報復元処理を割愛したい場合。
-
リクエスト・レスポンスのサイズを削減して、性能向上を図りたい場合。
-
画面リフレッシュによる、画面のちらつきなどをなくしたい場合。
補足(
Ajax.BeginFormは廃止済み): 部分描画とJavaScript で
述べた通り、Ajax.BeginFormは ASP.NET Core MVC に
移植されなかった。原文が挙げる利点は、現在は次の手段で得る。
原文の利点 現在の手段 部分更新 fetch+ 部分ビュー、htmx、SPA、Blazor重い処理の非同期化 async/await(サーバ側)+ 進捗表示 入力状態の保持 クライアント側で状態を持つ(React/Vue/Blazor) 通信量の削減 JSON API(HTML 断片より小さい) ちらつきの回避 同上 「サーバー側処理が非常に重い業務」に Ajax を使うという発想は、
現在では非同期ジョブ + 進捗ポーリング / SignalR で
解決する方が適切な場合が多い
(HTTP リクエストを長時間保持するのは避ける)。
-
ajaxの使いどころ
http://okwave.jp/qa/q8112782.html -
Html.BeginForm() vs Ajax.BeginForm() in MVC3 - CodeProject
http://www.codeproject.com/Articles/429164/Html-BeginForm-vs-Ajax-BeginForm-in-MVC3
画面設計によるが、以下を考慮する。
- 1 View に対して 1 Form?
- 1 View に対して複数 Form?
複数 Form の場合は Form をネストさせないこと。
# HTML の仕様で Form のネストは禁止されている。
移行メモ(誤記): 原文の見出し「From タグの切り方」は
Form の誤記と判断し修正した。
-
Html.xxxxFor
- ポスト時に Model データを復元する場合。
- 例えばエラー発生時に、自画面の再表示(≒ポストバック)をする場合。
-
Html.xxxx
上記以外は、Html.xxxx で良い。
ASP.NET MVC で一覧(グリッド)のある View を作成する場合、以下の 3 種類が考えられる。
-
WebGrid クラスを使用する
ソートやページングが容易に実装できる反面、レンダリング部分は多少ブラックボックスになる -
<table> タグを自前で生成し、<tr> タグをループで実装する
ASP.NET の Repeater コントロールのような処理の実装方法(Razor なぶん楽)。 -
jqGrid など、OSS の JavaScript ライブラリを使用する。
補足(現在の選択肢): WebGrid(
System.Web.Helpers)は
ASP.NET Core には無い。現在の一覧表示の選択肢は次の通り。
手段 特徴 foreachで<table>を書く最も素直。制御が完全 JS のグリッド ライブラリ AG Grid、Tabulator、DataTables(jQuery) Blazor の QuickGrid.NET 8 標準。C# だけで書ける ページング支援 X.PagedList、自前原文の 3 分類はそのまま有効で、
**「ブラックボックスを避け、自前で書く」**が
現在の主流である点も変わっていない。
DisplayFor や EditorFor などモデル定義に応じて出力を自在に変えられる Html ヘルパー。
- Html ヘルパー
-
DisplayFor、DisplayForModel:データの表示
- DisplayFor:
- DisplayForModel:Model 単位にテンプレート・ヘルパーを決定する。
-
EditorFor、EditorForModel:データ編集項目の生成
- EditorFor:
Html ヘルパーの第二引数でテンプレート・ヘルパーを指定できる。
UIHint 属性で指定したテンプレート・ヘルパーが使用される。
属性の優先度は、UIHint 属性 -> DataType 属性 -> 実際のデータ型 - EditorForModel:Model 単位にテンプレート・ヘルパーを決定する。
- EditorFor:
-
移行メモ(表記): 原文の「EditFor、EditForModel」は
EditorFor/EditorForModelの誤記と判断し修正した。
-
テンプレート・ヘルパー名とテンプレート・ヘルパー配置場所
- DisplayTemplates
- ~/View/Shared/DisplayTemplates/DataType名 or Model名.cshtml
- ~/View/Controller名/DisplayTemplates/DataType名 or Model名.cshtml
- EditorTemplates
- ~/View/Shared/EditorTemplates/DataType名 or Model名.cshtml
- ~/View/Controller名/EditorTemplates/DataType名 or Model名.cshtml
- DisplayTemplates
-
参考
- [Razor] - Build Insider
- テンプレート関連のビューヘルパー|テンプレートの標準の挙動
https://www.buildinsider.net/web/bookaspmvc5/040401 - テンプレートのカスタマイズ|テンプレートを決定する方法
https://www.buildinsider.net/web/bookaspmvc5/040402 - モデル単位にテンプレートを決定する - DisplayForModel/EditorForModelメソッド
https://www.buildinsider.net/web/bookaspmvc5/040404
- テンプレート関連のビューヘルパー|テンプレートの標準の挙動
- [Razor] - Build Insider
補足(この仕組みは ASP.NET Core にもある):
DisplayTemplates/
EditorTemplatesは ASP.NET Core でも同じ規約で動く
(置き場所がViews/Shared/配下である点も同じ)。Views/Shared/EditorTemplates/DateTime.cshtml Views/Shared/DisplayTemplates/Boolean.cshtmlただし現在は、タグ ヘルパーや
Blazor のコンポーネントの方が
「部品を作って使い回す」手段として自然であり、
テンプレート・ヘルパーの出番は減っている。
タイムスタンプ型など、ユーザに見えない情報だが
内部処理に必要な情報を引き継ぐ場合に使用する。
補足(Hidden は改竄される前提で扱う): ViewState と違い、
MVC の Hidden には改竄検知が無い。<input type="hidden" name="Price" value="1000"> → 開発者ツールで書き換えて POST できる金額・権限・所有者 ID などを Hidden に置いてはならない。
**楽観的同時実行制御のタイムスタンプ(RowVersion)**のように、
改竄されても害が無い(サーバ側で必ず検証する)ものに限る。// RowVersion は改竄されても「更新が失敗する」だけなので安全 [Timestamp] public byte[] RowVersion { get; set; }
- 参考
-
ASP.NET MVC 5 開発メモ > ビューヘルパーを自作する
http://aspmvc.tokumori-domain.com/view/crt_viewhelper/ -
第6回 テンプレート機能でビュー開発を効率化(3/3) - @IT
http://www.atmarkit.co.jp/fdotnet/aspnetmvc3/aspnetmvc3_07/aspnetmvc3_07_03.html
-
モジュール化の要約で紹介した、
共に、画面遷移の処理フローは以下のようになる。
- View から、対応する Controller にリクエストを送る
- Controller はリクエストを受け付け、Model に処理を指示する
- Model は業務ロジックを実行し、データを更新する
- Controller は View に表示を指示する
なお、1 つの View が、任意の Controller に POST リクエストを送る
ASP.NET Web Formsの「ページ間ポスティング」的な実装も書けるが、
View と Controller の関係が複雑になるのでオススメしない。
従って、上記の「4.」で、どの View に表示の指示をするかによって、
使用する ActionResult クラスを下記のように変える様に実装する。
- その Controller に対応する View に、表示を指示する場合
- ViewResult を使用する
- 別の Controller に対応する View に、表示を指示する場合
- RedirectToActionResult または RedirectToRouteResult を使用する
補足(Post/Redirect/Get パターン): この節が述べているのは、
一般に PRG(Post/Redirect/Get)パターンと呼ばれる定石である。POST /Order/Create → 成功したら RedirectToAction("Index") ← 302 を返す → ブラウザが GET /Order/Index を実行 【これをしないと】 POST の結果を直接 View で返す → ブラウザで F5(再読み込み)すると「再送信しますか?」 → OK すると二重登録更新系は必ずリダイレクトで終わる、というのが原則である。
リダイレクト先へのメッセージ受け渡しには
TempData(ASP.NET MVCの用語)を使う。
「モバイル アプリケーション」テンプレートを使用して、
モバイルデバイス向けアプリケーションを作成できる。
このテンプレートでは、jQuery Mobile をベースとしており、
タッチ操作に最適化した UI を構築できる。
Bootstrap によるレスポンシブデザインに頼らず、
PC 向けサイト、モバイル向けサイトの画面を分けるアプローチ。
-
Desktop か Mobile かは、UserAgent や UserHostAddress を使用して判別している。
-
通常、XXXXX.cshtml と XXXXX.Mobile.cshtml の2つの View スクリプトを作成する。
上記は、マスタページや部分 View などの View スクリプトにも適用することができる。- XXXXX.cshtml (PC 向けサイト)
- XXXXX.Mobile.cshtml (モバイル向けサイト)
-
以下のコードを追加してデバイスごとに XXXXX.YYYYY.cshtml と View スクリプトを追加する。
- Global.asax の Application_Start メソッドに定義する。
protected void Application_Start()
{
(中略)
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone")
{
ContextCondition = (context => context.GetOverriddenUserAgent()
.IndexOf("iPhone", StringComparison.OrdinalIgnoreCase) >= 0)
});
}この時、
- 「XXXXX.cshtml」と同じフォルダに
- 「XXXXX.iPhone.cshtml」を作成し、
iPhone 向けのレイアウトを定義すると、
http://server/application/Controller/Index/id
という同じ URL に対し、
- PC のブラウザからアクセスした場合、「Index.cshtml」が使用され、
- iPhone からアクセスした場合は「Index.iPhone.cshtml」が使用されるようになる。
移行メモ(表記): 原文の「Mobil」は Mobile の
綴りの誤りと判断し修正した。
補足(この方式は現在は使われない/最新化):
DisplayModesは
ASP.NET Core MVC には移植されなかった。【当時】PC 用サイトとモバイル用サイトを分ける → UserAgent で判別(m.example.com 方式も同時代) → 画面が 2 系統になり、保守が倍になる 【現在】レスポンシブ デザイン 1 本 → CSS のメディア クエリで出し分ける → Google も「モバイル ファースト インデックス」で 単一 URL・単一 HTML を推奨UserAgent による判別自体が推奨されない
(新機種のたびに条件が増える、偽装される、
ブラウザ側も UA 文字列の縮小を進めている)。jQuery Mobile も 2021 年に開発終了しており、
「モバイル アプリケーション テンプレート」も現存しない。どうしても出し分けが必要な場合は、
- CSS のメディア クエリ / コンテナ クエリ(第一候補)
- クライアント ヒント(
Sec-CH-UA-Mobileヘッダー)を使う。
このクラスで設定・追加したプロパティが、
各 View ファイル(.cshtml や .vbhtml)の開発時に使用出来るようになる。
- ASP.NET MVC 5 / Core MVCで独自のビューページのクラスを使用する - Qiita
https://qiita.com/taiga_takahari/items/1c788ac91b62324a9140
補足: ASP.NET Core では
RazorPage<TModel>を継承し、
_ViewImports.cshtmlの@inheritsで指定する。
ただし現在は、@injectによる DI の方が
素直な解決手段であることが多い
(ASP.NET Core における DI)。
利用不可能
-
必要であれば、ViewState相当の状態保存処理を独自実装する必要がある。
-
For 付きの Html ヘルパー(Html.xxxxFor)を使用すれば、
双方向バインディングによりポストバックの範囲で値は維持される)。
使用可能
使用可能
補足(MVC で ViewState が無いことの意味): これは
欠落ではなく設計判断である。
ViewState あり(Web Forms) 無し(MVC) 状態の保持 自動(フレームワークが隠蔽) 明示的に書く ページ サイズ 肥大する(数百 KB になることも) 小さい 改竄検知 あり(MAC 署名) 無し(自分で検証) ステートレス性 低い 高い(スケールしやすい) 学習コスト 低い(が、内部が見えない) 高い(が、見通しが良い) MVC が ViewState を持たないことで、
HTTP のステートレス性に素直な設計になった。
これは ASP.NET Core のスケーラビリティにも繋がっている
(ASP.NET Coreの分散キャッシュ)。なお、Session を使うとステートレス性が損なわれるため、
複数台構成では分散キャッシュが必要になる。
可能なら Session も使わない(トークンに載せる、DB から引く)
というのが、より現代的な設計である。
Action メソッド内で個別にチェックロジックを実装するか、
ModelMetadata を使用したチェックが可能。
以下の属性を使用して検証可能。
| 項番 | 属性 | 概要 |
|---|---|---|
| 1 | DataType 属性(enumDataType, errmsg) | 指定のデータ型に変換可能か |
| 2 | Required 属性(errmsg) | 必須入力 |
| 3 | StringLength 属性(max, errmsg) | 文字列の最大長 |
| 4 | MaxLength 属性(max, errmsg) | 文字列の最大長 |
| 5 | MinLength 属性(min, errmsg) | 文字列の最小長 |
| 6 | Range 属性(min, max, errmsg) | 範囲 |
| 7 | RegularExpression 属性(pattern, errmsg) | 正規表現 |
| 8 | MembershipPassword 属性() | パスワード |
| 9 | Compare 属性(targetname, errmsg) | 比較(e-mail や passwd の確認用フィールドに使用する) |
| 10 | Remote 属性(actionMethod, errmsg) | Ajax を使用したチェック |
| 11 | CustomValidation 属性() | カスタム |
各属性の errmsg プロパティに何も設定しなくても既定でエラーメッセージが出る。
- 参考
- ASP.NET MVCのモデルでよく使う属性(アノテーション)リスト - Qiita
http://qiita.com/mrpero/items/607c31895d77815a77cb - よく使われる入力検証をまとめてみる - しばやん雑記
http://blog.shibayan.jp/entry/20110708/1310132392
- ASP.NET MVCのモデルでよく使う属性(アノテーション)リスト - Qiita
補足(
StringLengthとMaxLengthの違い): 表の 3・4 は
概要が同じに見えるが、用途が違う。
属性 主な用途 StringLength入力検証(最小長も指定できる) MaxLengthDB スキーマの生成(EF のカラム長)+検証 [StringLength(50, MinimumLength = 3)] // 検証用 [MaxLength(50)] // EF が nvarchar(50) を作る public string Name { get; set; }EF Core では
MaxLengthが列長に反映されるため、
両方付けるか、MaxLength+Requiredで済ませることが多い。
RegularExpressionを使う場合は ReDoS に注意する
(正規表現 の該当節を参照)。
ユーザー入力を対象にするため、入れ子の量指定子を避ける。
DataType 属性も検証属性として機能する。
-
参考
ざっくり、以下のようにして、CustomValidation 属性を使用する。
-
単項目チェック処理
- プロパティに CustomValidation 属性を設定し、呼び出す検証メソッドと関連付ける。
- CustomValidation 属性に設定した、static の検証メソッドを定義する。
- 検証メソッドでは、検証結果として、ValidationResult を返す。
-
関連チェック処理
- モデルに CustomValidation 属性を設定し、呼び出す検証メソッドと関連付ける。
- CustomValidation 属性に設定した、static の検証メソッドを定義する。
- 検証メソッドでは、検証結果として、ValidationResult を返す。
ざっくり、以下のようにして、自作 Validation 属性を定義して使用する。
-
単項目チェック処理
- プロパティに自作 Validation 属性を指定して検証パラメタやエラーメッセージを指定する。
-
自作 Validation 属性の作成
-
ValidationAttribute クラスを継承した自作 Validation を定義する。
-
コンストラクタを実装
検証パラメタとエラーメッセージを準備(必要に応じてパラメタライズ) -
メソッドを override して実装する。
- FormatErrorMessage メソッドでエラーメッセージを生成
- IsValid メソッドで検証処理を実装し、検証結果として、bool を返す。
-
-
自作 Validation 属性をクライアント側検証に対応させる。
-
IClientValidatable インターフェイスを実装する。
- 属性と View の橋渡しを行なう GetClientValidationRules メソッドを実装する。
- GetClientValidationRules メソッドでは、
IEnumerable<ModelClientValidationRule> を返す。 - これにより、検証名に対応する検証パラメタとエラーメッセージが
HTML 側に属性として出力される。
-
ModelClientValidationRule のプロパティ
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.modelclientvalidationrule- ValidationType(検証名)
- ValidationParameters(検証パラメタの Key/Value)
- ErrorMessage(エラーメッセージ)
-
jQuery Validation プラグインとの関連付け。
検証ロジックを独自 js ファイルに定義する。$.validator.addMethod('検証名', ~$.validator.unobtrusive.adapters.addSingleVal('検証名', '検証パラメタのKey');- BundleConfig.cs に、この独自 js ファイルを jqueryval に include する
バンドル定義を追加する。
-
移行メモ(表記): 原文の「IClientValidation インターフェイス」
「GetClientValidationRule メソッド」は、
実際の型名・メソッド名に合わせて
IClientValidatable/GetClientValidationRulesに修正した。
また$.Validator.addMethodは$.validator(小文字)が正しい。
補足(ASP.NET Core での自作検証): 仕組みは大きく変わっている。
ASP.NET MVC 5 ASP.NET Core サーバ側 ValidationAttributeを継承同じ クライアント側 IClientValidatableIClientModelValidator属性の出力 ModelClientValidationRulecontext.Attributes.Add(...)public class MyAttribute : ValidationAttribute, IClientModelValidator { public void AddValidation(ClientModelValidationContext context) { context.Attributes["data-val"] = "true"; context.Attributes["data-val-my"] = FormatErrorMessage(...); } }より現代的な代替として FluentValidation がある。
属性ではなく専用クラスに検証ルールを書く方式で、
- 検証ロジックがモデルから分離される
- 条件付き検証・複雑なルールが書きやすい
- 単体テストしやすい
という利点があり、実務での採用例が多い。
ざっくり、以下のようにして、IValidatableObject を使用する。
複数の検証結果を返せるのは、IValidatableObject だけである。
- 関連チェック処理(IValidatableObject)
- モデルで IValidatableObject を実装して IValidatableObject.Validate メソッドを実装する。
- 検証メソッドでは、検証結果として、IEnumerable<ValidationResult> を返す。
補足(実行順序に注意):
IValidatableObject.Validateは
属性による検証がすべて成功した後にのみ呼ばれる。① 各プロパティの検証属性(Required、StringLength …) → 1 つでも失敗したら ② は呼ばれない ② IValidatableObject.Validate(項目間の関連チェック)このため、**「必須項目が空のとき、関連チェックが走らない」**のは
仕様通りである(むしろ null 参照を避けられて都合が良い)。
Action Method の実行前に、モデル・バインディングが
行われたタイミング。
-
概要
- Controller.ModelState でアクセスできる。
- モデルの状態とモデル・バインディングの検証結果を含む。
-
Property
- IsValid プロパティ
モデル・バインディングの検証結果として、モデルの有効・無効を返す。 - Keys プロパティ
- Values プロパティ
- IsValid プロパティ
-
Method
- ModelState.AddModelError()
エラー情報を追加する。
- ModelState.AddModelError()
補足(
IsValidの確認は必須): 検証は自動で実行されるが、
結果の確認は自動ではない。[HttpPost] public ActionResult Create(OrderVm vm) { if (!ModelState.IsValid) // ← これを書き忘れると検証が無意味 return View(vm); ... }ASP.NET Core の
[ApiController]を付けた場合は自動化される
(検証失敗時に自動で 400 +ValidationProblemDetailsを返す)。[ApiController] // ← これで ModelState.IsValid の確認が不要になる [Route("api/[controller]")] public class OrdersController : ControllerBase { }MVC(画面あり)では自動化されないため、
明示的な確認が引き続き必要である。
- Method
- Html.ValidationSummary
すべての検証結果のエラー メッセージを要約する。 - Html.ValidationMessage
個々の検証結果のエラー メッセージを表示する。 - Html.ValidationMessageFor
個々の検証結果のエラー メッセージを表示する。
- Html.ValidationSummary
-
検証後に適用される CSS クラス
これらの CSS クラスを Site.css ファイルに定義しておく。- validation-summary-errors
ValidationSummary メソッドによって出力されたメッセージの<div>要素(エラー有) - validation-summary-valid
ValidationSummary メソッドによって出力されたメッセージの<div>要素(エラー無) - field-validation-error
ValidationMessage メソッドによって出力されたメッセージの<span>要素(エラー有) - field-validation-valid
ValidationMessage メソッドによって出力されたメッセージの<span>要素(エラー無) - input-validation-error
検証エラーが発生した要素
- validation-summary-errors
-
JavaScript でのクライアント側検証
- jQuery Validation プラグインなども使用可能。
- Controller 追加で、スキャフォールディングする際、
スクリプトライブラリの参照にチェックを入れると JavaScript ライブラリを
インポートできる。
補足(クライアント側検証は「補助」である): 前提として、
クライアント側検証はサーバ側検証の代わりにならない。クライアント側検証 … UX の向上(往復を減らす) → 開発者ツールで無効化できる サーバ側検証 … セキュリティ・整合性の担保 → 必ず実施する
ModelState.IsValidを省略してはならない理由がここにある。ASP.NET Core でのタグ ヘルパー版:
<div asp-validation-summary="ModelOnly"></div> <input asp-for="Name" /> <span asp-validation-for="Name"></span>CSS クラス名(
field-validation-error等)は同じである。
CustomValidation 属性ではクライアント側検証が実行されない。
整合性をとるために、クライアント側検証の無効化を行なう。
<add key="ClientValidationEnabled" value="false" />若しくは、自作 Validation 属性にクライアント側検証を実装する。
クライアント側検証に「控えめな JavaScript」のパラダイムが導入されている。
- *.html ファイル側に JavaScript ではなく、属性を書く。
- JavaScript は *.js ファイル側に書いて、HTML と JavaScript を分離する。
- 「控えめな JavaScript」は以下のパラメタで制御できる。
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>- 控えめなJavaScript - Wikipedia
https://ja.wikipedia.org/wiki/%E6%8E%A7%E3%81%88%E3%82%81%E3%81%AAJavaScript
補足(「控えめな JavaScript」の実体): HTML 側に
data-val-*属性を出力し、JavaScript がそれを読んで検証する方式である。<input name="Age" type="text" data-val="true" data-val-required="年齢は必須です。" data-val-range="0〜150 で入力してください。" data-val-range-min="0" data-val-range-max="150" />HTML と JavaScript が分離されるため、
- サーバ側の属性から自動生成できる(二重管理を避けられる)
- JavaScript を差し替えても HTML は変わらない
という利点がある。
ASP.NET Core でもこの方式がそのまま使われている
(jquery.validate.unobtrusive.js)。なお、jQuery 依存を外したい場合は、
aspnet-client-validation等の代替ライブラリがある。
-
方法: DataAnnotations 属性を使用してモデル データを検証する
https://learn.microsoft.com/ja-jp/previous-versions/aspnet/ee256141(v=vs.100) -
お楽しみはこれからだ!
- ASP.NET MVC RCの入力検証
http://takepara.blogspot.jp/2009/02/aspnet-mvc-rc.html - DataAnnotationsだけでの入力検証の盲点
http://takepara.blogspot.jp/2009/02/dataannotations.html
- ASP.NET MVC RCの入力検証
-
Entity Frameworkと検証処理。
- ASP.NET MVC 3における検証まわりの改善点 (3-4):CodeZine
http://codezine.jp/article/detail/6176?p=3 - ASP.NET MVC 4 ことはじめ(5)モデルと足場 - アーキテクチャをスマートに。
http://d.hatena.ne.jp/architect-wat/20130512/1368360044
- ASP.NET MVC 3における検証まわりの改善点 (3-4):CodeZine
スキャフォールディング(Scaffolding)。
日本語で「足場」、「骨組み」の意味。
- M(Model)を元に、
- C(Controller)
- CRUD を行う Action Method
- V(View)
- C(Controller)
を自動で生成する機能。
-
通常、
- CRUD の Action Method だけが自動生成される。
-
Entity Frameworkによるデータ操作を行う場合、
Action Method に加えて View も自動生成できる。
-
その他
- 使用する Model を選択する。
- Model の新規作成も可能
-
M(Model)の複数形 + Controller という名称になる。
-
Action Method
- Index
- Details
- Create
- Edit
- Delete
- Entity Frameworkを使用すると、以下のように View まで自動生成される。
- また、Entity Frameworkを使用しない場合も、個別に、
ModelMetadata を定義した Model を指定し View 追加(スキャフォールディング)ができる。
| 項番 | View 名 | 対応する Action Method 名 | 使用される Template 名 |
|---|---|---|---|
| 1 | Index.cshtml | Index | List |
| 2 | Details.cshtml | Details | Details |
| 3 | Create.cshtml | Create | Create |
| 4 | Edit.cshtml | Edit | Edit |
| 5 | Delete.cshtml | Delete | Delete |
- ASP.NET MVC - 既定のスキャフォールディング テンプレートをオーバーライドする
https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2014/june/asp-net-mvc-overriding-the-default-scaffolding-templates
補足(スキャフォールディングは「叩き台」): 生成されたコードを
そのまま本番に使わないのが実務上の前提である。生成されるコードの問題点 ・エンティティを直接バインドしている(オーバーポスティング) ・同期メソッド(async でない)※ 現在の生成は async ・DbContext を Controller から直接使っている ・エラー処理・ログが無い ・ページングが無い(全件取得)**「動く雛形を素早く得て、そこから作り替える」**という
使い方が適切である。
原文が概要で「肥大しやすい傾向がある」と警告しているのは、
生成されたものをそのまま育ててしまう危険を指している。ASP.NET Core での生成:
dotnet tool install -g dotnet-aspnet-codegenerator dotnet aspnet-codegenerator controller -name OrdersController \ -m Order -dc AppDbContext --relativeFolderPath Controllers \ --useDefaultLayout --referenceScriptLibraries
- Html ヘルパーを使用すると、自動的にサニタイジングが行われる。
- Model のプロパティを直接 View に表示する場合は、自前でのサニタイジングが必要。
移行メモ(補足): Razor では
@による出力も自動エスケープされる
(ASP.NET MVCの用語 の Razor 系の節を参照)。
原文の「自前でのサニタイジングが必要」は、
@Html.Raw()を使う場合に限った話と読むのが妥当である。
- requestValidationMode を MVC でも利用可能。
http://stackoverflow.com/questions/6206540/what-does-requestvalidationmode-2-0-actually-do
補足(リクエスト検証は補助的な機構):
requestValidationは
<を含む入力などを一律で拒否する仕組みで、
「A potentially dangerous Request.Form value was detected」という
例外の正体である。// 特定のプロパティだけ許可する [AllowHtml] public string Body { get; set; } // アクション全体で無効化する(非推奨) [ValidateInput(false)]これは XSS 対策の本体ではない(出力時のエスケープが本体)。
リッチ テキスト入力などで無効化する場合は、
HtmlSanitizer 等で明示的にサニタイズすること。ASP.NET Core にはリクエスト検証機構が無い
(「出力時に必ずエスケープする」設計に一本化された)。
ASP.NET MVCのテンプレートでは、
- ASP.NET Identityを使用するが、
- 従来の、基本的な、ASP.NET Forms 認証も使用可能。
- ASP.NET MVC で フォーム認証 の実装方法
https://garafu.blogspot.jp/2013/11/aspnet-mvc.html
-
ASP.NET Forms 認証より高度な認証基盤。
-
また、ASP.NET Identityを使用すると、
OAuthのクライアント & サーバ機能を実装できる。-
OAuthのクライアント : ASP.NET Identityの外部ログイン
-
Tags: 移行, .NET開発, ASP.NET, ASP.NET MVC
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。