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 5System.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 処理の内容
Delete Delete 削除画面の初期表示処理
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 リクエスト

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

  • 危険な項目(IsAdminCreatedAt 等)がそもそも存在しない
  • 画面固有の項目(確認用パスワード、表示用ラベル)を持てる
  • 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.BeginFormASP.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)
BlazorQuickGrid .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 /
EditorTemplatesASP.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 すると二重登録

更新系は必ずリダイレクトで終わる、というのが原則である。
リダイレクト先へのメッセージ受け渡しには
TempDataASP.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 側

検証属性

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

項番 属性 概要
DataType 属性(enumDataType, errmsg) 指定のデータ型に変換可能か
Required 属性(errmsg) 必須入力
StringLength 属性(max, errmsg) 文字列の最大長
MaxLength 属性(max, errmsg) 文字列の最大長
MinLength 属性(min, errmsg) 文字列の最小長
Range 属性(min, max, errmsg) 範囲
RegularExpression 属性(pattern, errmsg) 正規表現
MembershipPassword 属性() パスワード
Compare 属性(targetname, errmsg) 比較(e-mail や passwd の確認用フィールドに使用する)
10 Remote 属性(actionMethod, errmsg) Ajax を使用したチェック
11 CustomValidation 属性() カスタム

各属性の errmsg プロパティに何も設定しなくても既定でエラーメッセージが出る。

補足(StringLengthMaxLength の違い): 表の 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

移行管理: DONETODO

Clone this wiki locally