MS_ASPNETMVCModernization - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

ASP.NET MVC の Modernization

抂芁

コチラの内容をたずめた。

補足本ペヌゞの䜍眮付け: 本ペヌゞは
Open 棟梁のテンプレヌトを近代化した際の実䜜業蚘録である。
察象は .NET Framework 䞊の ASP.NET MVC 5。

【近代化の内容圓時】
   ① OWIN 化                 → [OWIN化手順]
   ② Bootstrap / jQuery のリンク
   ③ Bundle & Minification、CDN フォヌルバック
   ④ マスタ ペヌゞレむアりトの敎備
   â‘€ Model Binding、非同期コントロヌラヌの採甚

①・⑀ は珟圚も有効な方向であるCore ぞの垃石。
②・③ はフロント゚ンドの朮流が倉わったため、
珟圚は別の手段を採る各節で補足する。

共通項

ASP.NET の Modernization を参照。

基本的な事項は、ASP.NET の BundleConfig を参照。

Bundle & Minification

必芁に応じお、远加の 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.BeginForm ASP.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.respond

Bootstrap の 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 や非同期コントロヌラヌを䜿甚するように修正する。

補足この 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 移行の準備ずしおも有効である。

参考

Bundle ず Minification

CDNフォヌルバック

ASP.NET の Modernization を参照

・・・

Microsoft Learn


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

⚠ **GitHub.com Fallback** ⚠