-
Notifications
You must be signed in to change notification settings - Fork 0
MS_ASPNETMVCModernization
コチラの内容をまとめた。
- Modify template : Add the Startup class to template to correspond to the OWIN pipeline.
https://github.com/OpenTouryoProject/OpenTouryo/issues/198 - Modify template : Link Bootstrap and jQuery from the template.
https://github.com/OpenTouryoProject/OpenTouryo/issues/199 - Modify template : Correspond the JavaScript and CSS to bundle and minify, or to CDN.
https://github.com/OpenTouryoProject/OpenTouryo/issues/200 - Modify template : Maintenance of the master page.
https://github.com/OpenTouryoProject/OpenTouryo/issues/201
補足(本ページの位置付け): 本ページは
Open 棟梁のテンプレートを近代化した際の実作業記録である。
対象は .NET Framework 上の ASP.NET MVC 5。【近代化の内容(当時)】 ① OWIN 化 → [OWIN化手順] ② Bootstrap / jQuery のリンク ③ Bundle & Minification、CDN フォールバック ④ マスタ ページ(レイアウト)の整備 ⑤ Model Binding、非同期コントローラーの採用①・⑤ は現在も有効な方向である(Core への布石)。
②・③ はフロントエンドの潮流が変わったため、
現在は別の手段を採る(各節で補足する)。
基本的な事項は、ASP.NET の BundleConfig を参照。
必要に応じて、追加の BundleConfig を行う。
共通項でインストールしたパッケージに加え以下をインストールする。
-
jQuery Validation Plugin ( MVC の Validation で使っている )
-
既存のフォームのドロップイン検証を提供する。
アプリケーションに合わせてあらゆるカスタマイズを簡単できる。 -
インストール方法
Install-Package jQuery.Validation
-
-
jQuery Unobtrusive Ajax
https://github.com/aspnet/jquery-ajax-unobtrusive-
公式サイトにも殆ど情報が無いが、Ajax.BeginForm を実行するのに必要らしい。
-
インストール方法
Install-Package Microsoft.jQuery.Unobtrusive.Ajax
-
-
jQuery Unobtrusive Validation
https://github.com/aspnet/jquery-validation-unobtrusive-
公式サイトにも殆ど情報が無いが、Ajax.BeginForm での Validation を実行するのに必要らしい。
-
インストール方法
Install-Package Microsoft.jQuery.Unobtrusive.Validation
-
補足(Unobtrusive の意味と仕組み): 「公式サイトにも殆ど情報が無い」
という原文の観察は正しく、当時から資料が乏しかった。
仕組みを補っておく。**
Unobtrusive(控えめ、非侵入的)**とは、
HTML に JavaScript を直接書かないという設計方針を指す。<!-- ✗ Obtrusive(HTML に JS が混ざる) --> <input onblur="if(this.value=='') alert('必須です');"> <!-- ○ Unobtrusive(data-* 属性でメタ情報だけ持たせる) --> <input name="Name" data-val="true" data-val-required="名前は必須です" data-val-length-max="50"> <!-- ↑ JS 側がこの属性を読んで検証を組み立てる -->【動作の流れ(Validation)】 ① ViewModel にデータ注釈を付ける [Required(ErrorMessage="名前は必須です")] public string Name { get; set; } ↓ ② Html.TextBoxFor(m => m.Name) が data-val-* 属性を出力 ↓ ③ jquery.validate.unobtrusive.js が data-val-* を読む ↓ ④ jquery.validate.js に検証ルールとして登録 ↓ ⑤ 【クライアント側で検証】される(サーバ側でも当然検証される)依存関係の順序が重要である。
jquery.js └ jquery.validate.js └ jquery.validate.unobtrusive.js ← この順に読む → 順序を間違えると【何も起きない】(エラーも出ない) → [ASP.NET の BundleConfig] で「順序を明示せよ」と述べた理由
Ajax.BeginFormも同じ仕組みである。<!-- Ajax.BeginForm が出力する属性 --> <form data-ajax="true" data-ajax-method="POST" data-ajax-update="#result" data-ajax-mode="replace"> <!-- ↑ jquery.unobtrusive-ajax.js がこれを読んで Ajax 送信に変える -->移行メモ(現在の状況):
ライブラリ 現況 jQuery Validation 現役。ASP.NET Core でも使われる jquery-validation-unobtrusive 現役( Microsoft.jQuery.Unobtrusive.Validation)。Core のテンプレートにも入るjquery-ajax-unobtrusive ASP.NET Core のテンプレートからは外れた Ajax.BeginFormASP.NET Core には存在しない ★
Ajax.BeginFormの現在の代替:・ASP.NET Core MVC … fetch API を自分で書く ・Razor Pages … 同上 ・Blazor … コンポーネントのイベントで完結 ・htmx … 属性駆動という点で Unobtrusive Ajax の思想に近い ★<!-- htmx(Unobtrusive Ajax の現代版と言える) --> <form hx-post="/api/save" hx-target="#result" hx-swap="innerHTML">「HTML に属性を書くと JS が動く」という発想自体は
現在も生きている——という点で、この節の内容は
技術の変遷を追ううえで示唆的である。
- BundleConfig を追加する。
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
// see : https://www.asp.net/ajax/cdn
string jqueryVersion = "3.1.1";
BundleTable.EnableOptimizations = true;
BundleTable.Bundles.UseCdn = true; // same as: bundles.UseCdn = true;
// ( new ScriptBundle("~/XXXX") のパスは実在するpathと被るとRender時にバグる。
// なので、bundlesと実在しないpathを指定している(CSSも同じbundlesを使用する)。
bundles.Add(new ScriptBundle("~/bundles/app").Include(
"~/Scripts/app/Site.js"));
bundles.Add(new ScriptBundle("~/bundles/otr").Include(
"~/Scripts/otr/common.js",
"~/Scripts/otr/ie_key_event.js",
"~/Scripts/otr/else.js"));
bundles.Add(new ScriptBundle(
"~/bundles/jquery",
string.Format("//ajax.aspnetcdn.com/ajax/jquery/jquery-{0}.min.js", jqueryVersion))
{
CdnFallbackExpression = "window.jQuery"
}.Include(string.Format("~/Scripts/jquery-{0}.js", jqueryVersion)));
bundles.Add(new ScriptBundle(
"~/bundles/jqueryval",
"//ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js")
{
CdnFallbackExpression = "window.jQuery.validator"
}.Include("~/Scripts/jquery.validate.js"));
bundles.Add(new ScriptBundle(
"~/bundles/jqueryvaluno",
"//ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js")
{
CdnFallbackExpression = "window.jQuery.validator.unobtrusive"
}.Include("~/Scripts/jquery.validate.unobtrusive.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryunoajax").Include(
"~/Scripts/jquery.unobtrusive-ajax.js")); // CDNで提供されていない。
// 開発と学習には、Modernizr の開発バージョンを使用します。次に、実稼働の準備ができたら、
// http://modernizr.com にあるビルド ツールを使用して、必要なテストのみを選択します。
bundles.Add(new ScriptBundle(
"~/bundles/modernizr",
"//ajax.aspnetcdn.com/ajax/modernizr/modernizr-2.8.3.js") // min 無し
{
CdnFallbackExpression = "window.Modernizr"
}.Include("~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle(
"~/bundles/bootstrap",
"//ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js")
{
CdnFallbackExpression = "window.jQuery.fn.modal"
}.Include("~/Scripts/bootstrap.js"));
bundles.Add(new ScriptBundle(
"~/bundles/respond",
"//ajax.aspnetcdn.com/ajax/respond/1.4.2/respond.min.js")
{
CdnFallbackExpression = "window.respond"
}.Include("~/Scripts/respond.js"));
bundles.Add(new StyleBundle("~/bundles/css").Include(
"~/Content/bootstrap.css",
"~/Content/themes/base/all.css",
"~/Content/themes/base/jquery-ui.css",
"~/Content/otr/Style.css",
"~/Content/app/Site.css"));
}
}補足(このコードの読みどころ): 実務的な工夫が詰まっているので、
要点を拾っておく。①
CdnFallbackExpressionの値の選び方ライブラリごとに「読み込めていれば定義されるグローバル変数」を指定する jQuery → window.jQuery jQuery Validate → window.jQuery.validator jQuery Validate Unobtrusive → window.jQuery.validator.unobtrusive Bootstrap(JS) → window.jQuery.fn.modal ★ 巧い Modernizr → window.Modernizr Respond.js → window.respondBootstrap の
window.jQuery.fn.modalが特に巧みで、
Bootstrap は独自のグローバル変数を作らないため、
jQuery プラグインとして登録される関数の存在で判定している。②
// CDN で提供されていない。というコメント
jquery.unobtrusive-ajax.jsだけ CDN 指定がない。
実際に aspnetcdn では配信されていなかったためで、
調べた結果を残している点が有用である。③
modernizr-*というワイルドカード.Include("~/Scripts/modernizr-*") → バージョン番号を書かずに済む → ただし【複数マッチすると順序が不定】になる(1 つだけ置くこと)④
EnableOptimizations = trueを明示している
debug="true"でも最適化を有効にする、という意思表示である
(ASP.NET の BundleConfig の
「本番相当で検証せよ」に対応する)。移行メモ(このコードに含まれるライブラリの現況):
ライブラリ 現況 jQuery 3.1.1 3.x 系は現役だが、3.1.1 には既知の脆弱性(XSS)。3.5 以上へ Bootstrap 3.3.7 サポート終了。5.x へ(4 で jQuery 依存が減り、5 で不要に) Modernizr 2.8.3 現在はほぼ不要(対象ブラウザが機能を備えているため) Respond.js 不要(IE8 の Media Query 対応用。IE 自体が終了) ajax.aspnetcdn.com提供終了(Microsoft Ajax CDN は 2022 年に廃止)★
ajax.aspnetcdn.comの廃止は重要である。
このコードをそのまま動かすと、CDN が 404 になり、
フォールバックが毎回発動する(動くが遅い)。
既存アプリを保守しているなら、CDN 指定を外すか、
別の CDN(jsDelivr、cdnjs)に差し替える必要がある。
ModernizrとRespond.jsは削除して構わない。
IE のサポートが終了した現在、存在意義がない。
- BundleConfig を呼び出す。
public class Startup
{
public void Configuration(IAppBuilder app)
{
// アプリケーションの設定方法の詳細については、http://go.microsoft.com/fwlink/?LinkID=316888 を参照してください
// アプリケーションのスタートアップで実行するコードです
//
AreaRegistration.RegisterAllAreas();
//
WebApiConfig.Register(GlobalConfiguration.Configuration);
// グローバルフィルタの登録
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
// URLルーティングの登録
RouteConfig.RegisterRoutes(RouteTable.Routes);
// バンドル&ミニフィケーションの登録
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}補足(Startup へ寄せた形になっている): OWIN化手順 の
「Global.asax から移動しても良いが必要性はない」という記述に対し、
ここでは実際に Startup へ寄せている。【この判断の利点】 起動時の処理が 1 箇所に集まる → ASP.NET Core の Program.cs / Startup.cs に近い形になる ★ → 移行時に対応関係が見えやすいASP.NET Core での対応形:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(options => { options.Filters.Add<MyGlobalFilter>(); // ← FilterConfig 相当 }); var app = builder.Build(); app.UseStaticFiles(); // ← BundleConfig 相当は消滅 app.UseRouting(); app.MapControllerRoute( // ← RouteConfig 相当 name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapControllers(); // ← WebApiConfig 相当 app.Run();
AreaRegistration.RegisterAllAreas()に相当するものは Core にはない
(エリアは属性[Area("Admin")]とルート パターンで表現する)。
CDNフォールバック
-
ASP.NET MVC の CDN フォールバックは、
ScriptBundle クラスで行なう。 -
ASP.NET Web Forms では、コレに加えて、
ScriptManager に CDN フォールバック機能がある。 -
ASP.NET MVC の CDN フォールバックの設定方法など、
詳しくは、コチラを参照。
@* マスタ・ページに相当 *@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
@Styles.Render("~/bundles/css")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/jquery")
@RenderSection("HeaderSection", required: true)
</head>
<body onload="Fx_Document_OnLoad(); Fx_AdjustStyle();">
@RenderBody()
@Scripts.Render("~/bundles/jqueryval")
@Scripts.Render("~/bundles/jqueryuno")
@Scripts.Render("~/bundles/otr")
@Scripts.Render("~/bundles/app")
@RenderSection("FooterSection", required: true)
</body>
</html>移行メモ(バンドル名の不一致): レイアウトが参照している
~/bundles/jqueryunoは、
BundleConfig 側の定義(~/bundles/jqueryvaluno/
~/bundles/jqueryunoajax)と一致していない。【BundleConfig で定義されているもの】 ~/bundles/jqueryval (jquery.validate) ~/bundles/jqueryvaluno (jquery.validate.unobtrusive) ~/bundles/jqueryunoajax (jquery.unobtrusive-ajax) 【レイアウトが参照しているもの】 ~/bundles/jqueryval ○ ~/bundles/jqueryuno ✗ ← 定義されていない
Scripts.Renderは、存在しないバンドル名を渡されると
それを「仮想パス」として<script src="…">を出力する
(例外にはならない)。
結果、404 になる<script>タグが出力される。意図としては
~/bundles/jqueryvalunoと読める
(Validation の Unobtrusive を本文末尾で読み込む構成のため)。この種の誤りは実行しても気付きにくい
(検証が効かなくなるだけで、エラーは出ない)。
ブラウザの開発者ツールのネットワーク タブで 404 を確認する
のが確実である。
補足(レイアウトの現在的な改善点): 移行元のマークアップには、
現在なら直すべき点がいくつかある。<!-- ✗ 原文 --> <html> <body onload="Fx_Document_OnLoad(); Fx_AdjustStyle();"><!-- ○ 現在 --> <html lang="ja"> <!-- ① 言語を明示(読み上げ・フォント選択・SEO) --> <body> ... <script> // ② onload 属性ではなく、スクリプト側で登録する(Unobtrusive) document.addEventListener('DOMContentLoaded', () => { Fx_Document_OnLoad(); Fx_AdjustStyle(); }); </script>
箇所 改善 <html>lang="ja"を付ける(国際化対応項目)<body onload>属性に JS を書かない。CSP を導入すると動かなくなる ★ <meta charset><head>の先頭に置く(エンコーディング)viewport <meta name="viewport">がない(レスポンシブに必要)required: trueセクション未定義のビューでエラーになる。 required: falseの方が扱いやすい
onload属性は CSP(Content Security Policy)と衝突する。
script-src 'self'のような CSP を設定すると、
インライン ハンドラは実行されない。
セキュリティ強化を進めるなら、属性の JS は必ず外に出す。
ASP.NET MVC では、既定で設定ができている。
Model Binding や非同期コントローラーを使用するように修正する。
- Modify template : Modernization of the implementation of ActionMethod of MVC.
Issue #215 · OpenTouryoProject/OpenTouryo
https://github.com/OpenTouryoProject/OpenTouryo/issues/215
補足(この 2 点が最も本質的な近代化): Bundle や CDN は
潮流とともに古びたが、Model Binding と非同期化は現在も正しい方向である。① Model Binding
// ✗ 古い書き方(Request から直接取る) public ActionResult Save() { var name = Request.Form["Name"]; var price = decimal.Parse(Request.Form["Price"]); // 例外の危険 ... } // ○ Model Binding [HttpPost] [ValidateAntiForgeryToken] public ActionResult Save(ProductEditViewModel model) { if (!ModelState.IsValid) return View(model); // 検証も自動 ... }【得られるもの】 ・型変換とその失敗の扱いが自動 ・データ注釈による検証(クライアント・サーバ両方) ・単体テストが書ける(引数を渡すだけ) ・[ASP.NET MVCでDataTableを使用する。] で述べた 「双方向バインディングには POCO が要る」と同じ話② 非同期コントローラー
// ✗ 同期(DB / API を待つ間、スレッドを占有する) public ActionResult Index() { var data = _repository.GetAll(); return View(data); } // ○ 非同期 public async Task<ActionResult> Index() { var data = await _repository.GetAllAsync(); return View(data); }【効果】 I/O 待ちの間、【スレッドがスレッド プールに返る】 → 同時接続数に対してスレッドが足りる → スループットが上がる(レイテンシは改善しない)★ 【注意】 ・.Result / .Wait() を混ぜると【デッドロック】する ・「全部 async にすれば速くなる」わけではない → CPU バウンドな処理には効果がない詳細は async/await を参照。
ASP.NET Core では非同期が既定であり、
同期版の API がむしろ削られている(Streamの同期読み書きが
既定で禁止される等)。
この方向への修正は、Core 移行の準備としても有効である。
-
jQuery UI
- ASP.NET Razor Jqueryuiの使い方 | Yutaka Hirata
http://rich.hrd.co.jp/asp-net-razor-jueryui%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9/ - クライアント側検証の無効化
http://surferonwww.info/BlogEngine/post/2015/02/17/disable-client-side-validation-in-mvc4-internet-application.aspx
- ASP.NET Razor Jqueryuiの使い方 | Yutaka Hirata
-
Ajax.BeginForm
- MVC5 Ajax.BeginForm()を利用するための手順 - Qiita
https://qiita.com/m-otoguro/items/1250015aed6907a8d9e7 - asp.net mvc 5 - Ajax.BeginForm doesn't fire AJAX script, falls back on postback - Stack Overflow
https://stackoverflow.com/questions/21831451/ajax-beginform-doesnt-fire-ajax-script-falls-back-on-postback
- MVC5 Ajax.BeginForm()を利用するための手順 - Qiita
CDNフォールバック
(ASP.NET の Modernization を参照)
・・・
- ASP.NET Core でのモデル バインド
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding - ASP.NET Core MVC のフィルター
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/controllers/filters - クライアント側の検証(jQuery Unobtrusive Validation)
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/validation
Tags: 移行, .NET開発, ASP.NET, ASP.NET MVC, OWIN, NuGet
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。