Skip to content

MS_ASPNETMVCUsage

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

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・属性・検証まわりには差異がある。各節で補足する。

モジュール化の考え方

M・V・C の役割の整理

まず、Model と View と Controller の役割について整理する。

  • Model:

    • アプリケーションの基礎となるデータ構造(ViewModel)、
    • およびそのデータを取得・加工する業務ロジック
  • View:
    Model が保持するデータ(ViewModel)を参照し、ユーザーに表示する。

  • Controller:

    • ユーザーからの入力を受け取り、
    • Model に対してデータの取得・加工を指示する。
    • その結果を受けて、View に表示を指示する。

画面 or データモデル、どちらをベースにするか?

ASP.NET MVC の開発は、ViewModel の ModelMetadata 駆動になる。

このため、ViewModel の設計が重要になるが、これを

  • 画面をベースにするか?
  • データモデルをベースにするか?

どちらをベースにするか?でモジュール化が異なってくる。

補足(この論点は現在も有効): 「画面ベースか、データモデル ベースか」
という問いは、フレームワークを問わず
設計の最初に決めるべき事柄であり続けている。

【データモデル ベース(スキャフォールディング)】
   エンティティ 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 と全体 View の関係

Model (0..1) <---> (1) 全体 View

Model (0) <---> (1) 全体 View

全体 View が、Model が保持するデータを何も表示しない状態。(静的なページなど?)

Model (1) <---> (1) 全体 View

全体 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 の節でも
同じ注意をしている通り、**ビューは「表示するだけ」**にする。

Controller と全体 View の関係

全体 View (1..*) <---> (1) Controller

全体 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.cs
public 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..*) <---> (1) Controller

複数の 全体 View に対して、1 つの Controller を対応させる考え方。

アプリケーションの

対して、1 つの Controller を対応させる考え方。

  • メリット
    • まとまった業務ごとに Controller を作るので、Controller の数を抑えられる。
  • デメリット
    • 複数の 全体 View からの処理をすべて 1 つの Controller で受け付けるため、
      その Controller のコード量が多くなる可能性がある。

移行メモ(重複): 原文はこの節の最初の箇条書きで
「機能や、=『全体View ごとに Controller を作成する方式』」と
記していたが、前節の名称と重複しており文脈に合わないため、
「全体View をまとめる 機能単位の Controller を作成する方式」
(概要節で使われている呼称)に統一した。

スキャフォールディング(scaffolding)方式

たとえば、受注処理を行うアプリケーションを考えると、
「受注作成画面」・「受注内容更新画面」・「受注削除画面」など、
複数の 全体 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 にのみリクエストを送る)
  • しかし、1 つの 全体 View で、CRUD 全てを実現できる
    (CRUD ごとに 全体 View が分かれない) 場合は、以下のようになる。

    • 全体 View (1) <---> (1) Model
    • Controller (1) <---> (1) Model
    • 全体 View (1) <---> (1) Controller

モジュール化の要約

Controller をどのような単位で作成するかにかかっている。

機能やデータ構造に対して 1 つの Controller を対応させる方式

  • 全体 View (1..*) <---> (1) Model
  • Controller (1) <---> (1..*) Model
  • 全体 View (1..*) <---> (1) Controller

全体 View ごとに Controller を作成する方式

  • 全体 View (1..*) <---> (1) Model
  • Controller (1) <---> (1..*) Model
  • 全体 View (1) <---> (1) Controller

Controller の作成

モジュール化

モジュール化の要約で紹介した、

何れかの方針に合わせて、Controller を作成する。

利用可能な属性

HTTP メソッド属性

特定の HTTP メソッドのみを受け入れる属性を付与することができる。

なお、Action Method に HTTP メソッド属性を指定しなかった場合、
Action Method は、すべての HTTP メソッドを受け入れる。

AcceptVerbs 属性は、複数の HTTP メソッドを受け入れる Action Method の定義に使用する。

  • AcceptVerbs 属性は、以下のように利用する。
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult XXXXX(・・・) { ・・・ }

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] と並べる

ActionName 属性

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 で自然に分かれるため
この技巧が不要になる。

ValidateAntiForgeryToken 属性

CSRF 対策に使用する。
複数ブラウザウィンドウ対応がなされているかどうかは未確認。

  • View
@using (Html.BeginForm())
{
    @* トークンを埋め込む *@
    @Html.AntiForgeryToken()
}
  • Controller
// トークンを検証する
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed()
{
    ・・・
}

補足(原文の疑問への回答と、現在の既定): 原文が
「複数ブラウザウィンドウ対応がなされているか未確認」としている点について。

対応している。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]
属性名 ValidateAntiForgeryToken ValidateAntiForgeryToken(同名)
@* ASP.NET Core:これだけでトークンが埋め込まれる *@
<form asp-action="Delete" method="post">

「付け忘れ」が起きにくくなったのが大きな改善である。
API(JSON)で Cookie 認証を使う場合は、
ヘッダー経由で送る設定が別途要る。

builder.Services.AddAntiforgery(o => o.HeaderName = "X-CSRF-TOKEN");

Model の作成

モジュール化

モジュール化の考え方のように、Model には 2 つの意味がある。

  • アプリケーションの基礎となるデータ構造

    • POCO として作成する。
    • XXXXViewModel という名称を付与する。
  • そのデータを取得・加工する業務ロジック

    • 通常の業務ロジック・クラスとして作成する。

ココでは前者について言及する。

補足(ViewModel を分ける意義): 「XXXXViewModel という名称を付与する」
という原文の指針は、ASP.NET MVCの用語 で述べた
オーバーポスティング攻撃の対策でもある。

エンティティ(DB のテーブルに対応)
  ↕ 詰め替え(AutoMapper / 手書き / Mapperless)
ViewModel(画面に必要な項目だけ)
  ↕ モデル バインディング
HTTP リクエスト

エンティティを直接バインドしないことで、

  • 危険な項目(IsAdmin、CreatedAt 等)がそもそも存在しない
  • 画面固有の項目(確認用パスワード、表示用ラベル)を持てる
  • DB のスキーマ変更が画面に波及しない

という利点が得られる。

利用可能な属性

System.ComponentModel.DataAnnotations 属性により、

の動作を制御する事ができる。

DataType 属性

型情報を指定することで、

として使用される。

[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 は「検証」ではない点に注意): 原文は
「汎用的な検証属性として使用される」としているが、
[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 の派生の属性

DataType 属性と同様に利用される。

DisplayName, Display 属性

表示名(Label 表示を行なう Html ヘルパーに使用される)

  • DisplayName

    • [DisplayName("xxxx")]
  • Display

    • Name
      • [Display(Name = "xxxx")]
    • ResourcesType(国際化対応用)
      • [Display(Name = "xxxx", ResourceType = typeof(yyyy))]

補足(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 属性

フォーマットの指定。

[DisplayFormat(DataFormatXXXX="YYYY")]

UIHint 属性

  • 独自の表示/編集テンプレート・ヘルパーを準備する。
  • 例えば 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"> が出力される

DisplayColumn 属性

検証属性

検証属性については、コチラ。

ViewModel の作成方法

AutoMapperを使用する。

View の作成

モジュール化

Razor、ASPX の使い分け

基本、Razor(主流)を使用する。

移行メモ(表記): 原文の「Razer」は Razor の誤記と判断し修正した。

BeginForm の使い分け

ASP.NET MVC には、<form> タグを生成する Html ヘルパーが 2 種類ある。
BeginForm ヘルパーは引数にコントローラー名、アクション名が付与でき、
指定したアクションメソッドにリクエストを送ることができる。

HTML.BeginForm

通常の <form> タグを生成する場合に使用する。

  • 全体更新が多数を占める場合 (アクションメソッドの結果として、View 全体を更新する場合)。

  • 画面リフレッシュにより、リクエスト・レスポンスのステータスを明確にしたい場合。

Ajax.BeginForm

<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 リクエストを長時間保持するのは避ける)。

参考

Form タグの切り方

画面設計によるが、以下を考慮する。

  • 1 View に対して 1 Form?
  • 1 View に対して複数 Form?

複数 Form の場合は Form をネストさせないこと。
# HTML の仕様で Form のネストは禁止されている。

移行メモ(誤記): 原文の見出し「From タグの切り方」は
Form の誤記と判断し修正した。

Html ヘルパーの使い分け

  • Html.xxxxFor

    • ポスト時に Model データを復元する場合。
    • 例えばエラー発生時に、自画面の再表示(≒ポストバック)をする場合。
  • Html.xxxx
    上記以外は、Html.xxxx で良い。

GridView 的な一覧(グリッド)生成用の Html ヘルパー

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 分類はそのまま有効で、
**「ブラックボックスを避け、自前で書く」**が
現在の主流である点も変わっていない。

テンプレート・ヘルパー(テンプレートに対応した Html ヘルパー)

DisplayFor や EditorFor などモデル定義に応じて出力を自在に変えられる Html ヘルパー。

  • Html ヘルパー
    • DisplayFor、DisplayForModel:データの表示

      • DisplayFor:
      • DisplayForModel:Model 単位にテンプレート・ヘルパーを決定する。
    • EditorFor、EditorForModel:データ編集項目の生成

      • EditorFor:
        Html ヘルパーの第二引数でテンプレート・ヘルパーを指定できる。
        UIHint 属性で指定したテンプレート・ヘルパーが使用される。
        属性の優先度は、UIHint 属性 -> DataType 属性 -> 実際のデータ型
      • EditorForModel:Model 単位にテンプレート・ヘルパーを決定する。

移行メモ(表記): 原文の「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
  • 参考

補足(この仕組みは ASP.NET Core にもある): DisplayTemplates /
EditorTemplates は ASP.NET Core でも同じ規約で動く
(置き場所が Views/Shared/ 配下である点も同じ)。

Views/Shared/EditorTemplates/DateTime.cshtml
Views/Shared/DisplayTemplates/Boolean.cshtml

ただし現在は、タグ ヘルパーや
Blazor のコンポーネントの方が
「部品を作って使い回す」手段として自然であり、
テンプレート・ヘルパーの出番は減っている。

Html.Hidden または Html.HiddenFor

タイムスタンプ型など、ユーザに見えない情報だが
内部処理に必要な情報を引き継ぐ場合に使用する。

補足(Hidden は改竄される前提で扱う): ViewState と違い、
MVC の Hidden には改竄検知が無い。

<input type="hidden" name="Price" value="1000">
  → 開発者ツールで書き換えて POST できる

金額・権限・所有者 ID などを Hidden に置いてはならない。
**楽観的同時実行制御のタイムスタンプ(RowVersion)**のように、
改竄されても害が無い(サーバ側で必ず検証する)ものに限る。

// RowVersion は改竄されても「更新が失敗する」だけなので安全
[Timestamp] public byte[] RowVersion { get; set; }

カスタム Html ヘルパー(カスタム・コントロールみたいな)

画面遷移

モジュール化の要約で紹介した、

共に、画面遷移の処理フローは以下のようになる。

処理フロー

  1. View から、対応する Controller にリクエストを送る
  2. Controller はリクエストを受け付け、Model に処理を指示する
  3. Model は業務ロジックを実行し、データを更新する
  4. 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の用語)を使う。

モバイル対応

Mobile Template

「モバイル アプリケーション」テンプレートを使用して、
モバイルデバイス向けアプリケーションを作成できる。
このテンプレートでは、jQuery Mobile をベースとしており、
タッチ操作に最適化した UI を構築できる。

DisplayModes

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 ヘッダー)

を使う。

その他

WebViewPage

このクラスで設定・追加したプロパティが、
各 View ファイル(.cshtml や .vbhtml)の開発時に使用出来るようになる。

補足: ASP.NET Core では RazorPage<TModel> を継承し、
_ViewImports.cshtml の @inherits で指定する。
ただし現在は、@inject による DI の方が
素直な解決手段であることが多い
(ASP.NET Core における DI)。

情報の持ち回り・状態管理方式

利用不可能

Hidden

使用可能

Session

使用可能

補足(MVC で ViewState が無いことの意味): これは
欠落ではなく設計判断である。

ViewState あり(Web Forms) 無し(MVC)
状態の保持 自動(フレームワークが隠蔽) 明示的に書く
ページ サイズ 肥大する(数百 KB になることも) 小さい
改竄検知 あり(MAC 署名) 無し(自分で検証)
ステートレス性 低い 高い(スケールしやすい)
学習コスト 低い(が、内部が見えない) 高い(が、見通しが良い)

MVC が ViewState を持たないことで、
HTTP のステートレス性に素直な設計になった
。
これは ASP.NET Core のスケーラビリティにも繋がっている
(ASP.NET Coreの分散キャッシュ)。

なお、Session を使うとステートレス性が損なわれるため、
複数台構成では分散キャッシュが必要になる。
可能なら Session も使わない(トークンに載せる、DB から引く)
というのが、より現代的な設計である。

検証機能

Action メソッド内で個別にチェックロジックを実装するか、
ModelMetadata を使用したチェックが可能。

Model 側

検証属性

以下の属性を使用して検証可能。

項番 属性 概要
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 プロパティに何も設定しなくても既定でエラーメッセージが出る。

補足(StringLength と MaxLength の違い): 表の 3・4 は
概要が同じに見えるが、用途が違う。

属性 主な用途
StringLength 入力検証(最小長も指定できる)
MaxLength DB スキーマの生成(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 属性を設定し、呼び出す検証メソッドと関連付ける。
    • CustomValidation 属性に設定した、static の検証メソッドを定義する。
    • 検証メソッドでは、検証結果として、ValidationResult を返す。
  • 関連チェック処理

    • モデルに CustomValidation 属性を設定し、呼び出す検証メソッドと関連付ける。
    • CustomValidation 属性に設定した、static の検証メソッドを定義する。
    • 検証メソッドでは、検証結果として、ValidationResult を返す。

自作 Validation 属性

ざっくり、以下のようにして、自作 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 を継承 同じ
クライアント側 IClientValidatable IClientModelValidator
属性の出力 ModelClientValidationRule context.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 を実装して IValidatableObject.Validate メソッドを実装する。
    • 検証メソッドでは、検証結果として、IEnumerable<ValidationResult> を返す。

補足(実行順序に注意): IValidatableObject.Validate は
属性による検証がすべて成功した後にのみ呼ばれる。

① 各プロパティの検証属性(Required、StringLength …)
     → 1 つでも失敗したら ② は呼ばれない
② IValidatableObject.Validate(項目間の関連チェック)

このため、**「必須項目が空のとき、関連チェックが走らない」**のは
仕様通りである(むしろ null 参照を避けられて都合が良い)。

Controller 側

検証タイミング

Action Method の実行前に、モデル・バインディングが
行われたタイミング。

ModelStateDictionary

  • 概要

    • Controller.ModelState でアクセスできる。
    • モデルの状態とモデル・バインディングの検証結果を含む。
  • Property

    • IsValid プロパティ
      モデル・バインディングの検証結果として、モデルの有効・無効を返す。
    • Keys プロパティ
    • Values プロパティ
  • Method

    • 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(画面あり)では自動化されないため、
明示的な確認が引き続き必要である。

View 側

サーバ側

  • Method
    • Html.ValidationSummary
      すべての検証結果のエラー メッセージを要約する。
    • Html.ValidationMessage
      個々の検証結果のエラー メッセージを表示する。
    • Html.ValidationMessageFor
      個々の検証結果のエラー メッセージを表示する。

クライアント

  • 検証後に適用される 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
      検証エラーが発生した要素
  • 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 等)は同じである。

appSettings

クライアント側検証の無効化

CustomValidation 属性ではクライアント側検証が実行されない。
整合性をとるために、クライアント側検証の無効化を行なう。

<add key="ClientValidationEnabled" value="false" />

若しくは、自作 Validation 属性にクライアント側検証を実装する。

控えめな JavaScript

クライアント側検証に「控えめな JavaScript」のパラダイムが導入されている。

  • *.html ファイル側に JavaScript ではなく、属性を書く。
  • JavaScript は *.js ファイル側に書いて、HTML と JavaScript を分離する。
  • 「控えめな JavaScript」は以下のパラメタで制御できる。
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>

補足(「控えめな 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 等の代替ライブラリがある。

参考

スキャフォールディング

概要

スキャフォールディング(Scaffolding)。
日本語で「足場」、「骨組み」の意味。

  • M(Model)を元に、
    • C(Controller)
      • CRUD を行う Action Method
    • V(View)

を自動で生成する機能。

M(Model)

  • 使用する Model を選択する。
  • Model の新規作成も可能

C(Controller)

  • M(Model)の複数形 + Controller という名称になる。

  • Action Method

    • Index
    • Details
    • Create
    • Edit
    • Delete

V(View)

  • 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

テンプレートをオーバーライド

補足(スキャフォールディングは「叩き台」): 生成されたコードを
そのまま本番に使わないのが実務上の前提である。

生成されるコードの問題点
  ・エンティティを直接バインドしている(オーバーポスティング)
  ・同期メソッド(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

ModelMetadata を使用した開発

脆弱性

サニタイジング

  • Html ヘルパーを使用すると、自動的にサニタイジングが行われる。
  • Model のプロパティを直接 View に表示する場合は、自前でのサニタイジングが必要。

移行メモ(補足): Razor では @ による出力も自動エスケープされる
(ASP.NET MVCの用語 の Razor 系の節を参照)。
原文の「自前でのサニタイジングが必要」は、
@Html.Raw() を使う場合に限った話と読むのが妥当である。

リクエスト検証

補足(リクエスト検証は補助的な機構): 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のテンプレートでは、

その他


Tags: 移行, .NET開発, ASP.NET, ASP.NET MVC

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONE / TODO

Clone this wiki locally