Skip to content

MS_ASPNETWebFormsModernization

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

ASP.NET Web Forms の Modernization

概要

コチラの内容をまとめた。

補足(Web Forms 側は MVC より複雑になる): 本ページは
ASP.NET MVC の Modernization と同じ Issue を
元にしているが、ASP.NET Web Forms 側は
手順が明らかに多い
。理由は 1 つに集約される。

【Web Forms には ScriptManager という【もう 1 つの仕組み】がある】

   ・ScriptManager      … Web Forms 固有。スクリプト参照と CDN を管理
   ・Bundle / ScriptBundle … ASP.NET 全体で共通の仕組み

   → 【2 つが並立し、しかも内部で絡み合っている】★
   → 本ページの後半「ScriptBundle との関連」が、まさにこの調査

原文の結論——
ScriptManager の中から ScriptBundle を組み合わせて使っている
——が、この混乱に対する明快な答えになっている。
本ページの最大の価値はここである。

共通項

ASP.NET の Modernization を参照。

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

初期設定

Optimization.WebFormsをインストールする。

ASP.NET Web Forms では、追加で以下のインストールが必要になる。

Install-Package Microsoft.AspNet.Web.Optimization.WebForms

Web.config に定義を追加する。

また、インストール後に以下の定義を Web.config に追加する必要がある。

  <pages>
    <namespaces>
      <add namespace="System.Web.Optimization" />
    </namespaces>
    <controls>
      <add assembly="Microsoft.AspNet.Web.Optimization.WebForms" namespace="Microsoft.AspNet.Web.Optimization.WebForms" tagPrefix="webopt" />
    </controls>
  </pages>
</system.web>

補足(この 2 つの定義がそれぞれ何を可能にするか):

【<namespaces> の <add namespace="System.Web.Optimization" />】
   → .aspx / .master で using なしに Scripts / Styles を書けるようにする
      <%: Scripts.Render("~/bundles/jquery") %>
      (ページごとに <%@ Import %> を書かずに済む)

【<controls> の tagPrefix="webopt"】
   → <webopt:BundleReference> というサーバ コントロールを使えるようにする
      CSS の参照に使う(後述)

MVC では Views/Web.config<namespaces> に相当する仕組みがある
(Razor の @using を暗黙化する)。
Web Forms では <controls> の登録が追加で要る点が違いである。

移行メモ: 原文のコード片は </system.web> で終わっているが、
<pages><system.web> の中の要素である。
実際の Web.config では以下の位置に入る。

<configuration>
  <system.web>
    <pages>
      <namespaces>…</namespaces>
      <controls>…</controls>
    </pages>
  </system.web>
</configuration>

Bundle & Minification

以下のように、ASP.NET Web Forms 用の追加の
BundleConfig を行う。

インストール

共通項でインストールしたパッケージに加え以下をインストールする。

Install-Package Microsoft.AspNet.ScriptManager.MSAjax
Install-Package Microsoft.AspNet.ScriptManager.WebForms

定義方法

  • 定義対象

    • WebFormsJs
      詳細は不明。情報が殆ど無い。

      • WebForms.js
      • WebUIValidation.js
      • MenuStandards.js
      • Focus.js
      • GridView.js
      • DetailsView.js
      • TreeView.js
      • WebParts.js
    • MsAjaxJs
      JS file related to ASP.NET Ajax

      • MicrosoftAjax.js
      • MicrosoftAjaxApplicationServices.js
      • MicrosoftAjaxTimer.js
      • MicrosoftAjaxWebForms.js

補足(「詳細は不明」への回答:これらは何をしているか): 原文が
「情報が殆ど無い」と書いている WebFormsJs の中身を補っておく。
これらは、Web Forms の各機能をブラウザ側で動かすための
ランタイム スクリプト
である。

ファイル 役割
WebForms.js __doPostBack の実装。ポストバックの中核
WebUIValidation.js <asp:RequiredFieldValidator> 等のクライアント側検証
MenuStandards.js <asp:Menu> のキーボード操作・アクセシビリティ
Focus.js SetFocus / Page.SetFocus の実装
GridView.js <asp:GridView>行選択・並べ替えのクライアント処理
DetailsView.js <asp:DetailsView>
TreeView.js <asp:TreeView>展開・折りたたみ
WebParts.js Web パーツのドラッグ&ドロップ
【なぜ「情報が殆ど無い」のか】
   ・これらは【ASP.NET の内部実装】であり、開発者が直接呼ぶものではない
   ・従来は【WebResource.axd 経由でアセンブリから配信】されていた
      → ファイルとして存在せず、ドキュメントも不要だった
   ・ASP.NET 4.5 で【物理ファイルとして配置できる】ようになった
      → バンドルできる=本ページの話

**MsAjaxJs(Microsoft Ajax Library)**は、
UpdatePanel(部分描画)の基盤である
部分描画とJavaScript)。

MicrosoftAjax.js                     … 基盤(Sys 名前空間)
MicrosoftAjaxApplicationServices.js  … 認証・プロファイルの Ajax 呼び出し
MicrosoftAjaxTimer.js                … <asp:Timer>
MicrosoftAjaxWebForms.js             … 【UpdatePanel の部分描画】★

原文のコード中のコメント——
これらのファイルには明示的な依存関係があり、
ファイルが動作するためには順序が重要です
」——は、
MicrosoftAjax.jsSys 名前空間を作り、
残りがそれに依存するためである。

  • 設定
    • BundleConfig を追加する。
      (最新の ASP.NET Web Forms プロジェクト・テンプレートから
      BundleConfig を自動生成して入手)。
public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        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"));

        // こちらのCDNフォールバック設定はScriptManager内で行われているため不要
        bundles.Add(new ScriptBundle("~/bundles/WebFormsJs").Include(
                "~/Scripts/WebForms/WebForms.js",
                "~/Scripts/WebForms/WebUIValidation.js",
                "~/Scripts/WebForms/MenuStandards.js",
                "~/Scripts/WebForms/Focus.js",
                "~/Scripts/WebForms/GridView.js",
                "~/Scripts/WebForms/DetailsView.js",
                "~/Scripts/WebForms/TreeView.js",
                "~/Scripts/WebForms/WebParts.js"));

        // こちらのCDNフォールバック設定はScriptManager内で行われているため不要
        // これらのファイルには明示的な依存関係があり、ファイルが動作するためには順序が重要です
        bundles.Add(new ScriptBundle("~/bundles/MsAjaxJs").Include(
                "~/Scripts/WebForms/MsAjax/MicrosoftAjax.js",
                "~/Scripts/WebForms/MsAjax/MicrosoftAjaxApplicationServices.js",
                "~/Scripts/WebForms/MsAjax/MicrosoftAjaxTimer.js",
                "~/Scripts/WebForms/MsAjax/MicrosoftAjaxWebForms.js"));

        // 開発と学習には、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-*"));

        ScriptManager.ScriptResourceMapping.AddDefinition(
            "respond",
            new ScriptResourceDefinition
            {
                Path = "~/Scripts/respond.min.js",
                DebugPath = "~/Scripts/respond.js",
                CdnPath = "//ajax.aspnetcdn.com/ajax/respond/1.4.2/respond.min.js",
                CdnDebugPath = "//ajax.aspnetcdn.com/ajax/respond/1.4.2/respond.js",
                CdnSupportsSecureConnection = false,
                LoadSuccessExpression = "window.respond"
            });
    }
}

補足(このコードで注目すべき 2 点):

BundleConfig の中で ScriptManager.ScriptResourceMapping を触っている

BundleConfig(Bundle の仕組み)の中で
ScriptManager(Web Forms の仕組み)の設定をしている

  → 【2 つの仕組みが混在する】という本ページの主題が、
    コードにそのまま表れている

CdnSupportsSecureConnection = false

Respond.js だけ false になっている
  → 当時、この CDN パスが HTTPS に対応していなかった
  → HTTPS のページでは【CDN を使わずローカルを読む】動作になる

【現在】 HTTPS が当然になったため、この設定自体が過去の遺物

移行メモ(ajax.aspnetcdn.com は廃止済み): ASP.NET MVC の Modernization
でも述べた通り、Microsoft Ajax CDN(ajax.aspnetcdn.com)は
2022 年に提供を終了
した。

【既存アプリへの影響】
   ・CDN のパスが 404 になる
   ・CdnFallbackExpression が働き、ローカルのファイルが読まれる
      → 【動作はするが、毎回 404 を 1 回踏む】
      → 表示が遅くなる、コンソールにエラーが出る

【対応】
   ① UseCdn = false にする(最も簡単・確実)★
   ② 別の CDN(jsDelivr / cdnjs / unpkg)に差し替える
// ① 最も簡単な対応
BundleTable.Bundles.UseCdn = false;
  • BundleConfig を呼び出す。
    Global.asax or Startup から BundleConfig.RegisterBundles メソッドを呼び出す。
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // アプリケーションの設定方法の詳細については、http://go.microsoft.com/fwlink/?LinkID=316888 を参照してください

        // アプリケーションのスタートアップで実行するコードです

        // URLルーティングの登録
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        // バンドル&ミニフィケーションの登録
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}
  • CSS については、Bundle.config に定義する。
<?xml version="1.0" encoding="utf-8" ?>
<bundles version="1.0">
  <styleBundle path="~/bundles/css">
    <include path="~/Content/bootstrap.css" />
    <include path="~/Content/Site.css" />
  </styleBundle>
</bundles>

補足(なぜ CSS だけ Bundle.config(XML)なのか): これは
Microsoft.AspNet.Web.Optimization.WebForms が提供する機能である。

【Bundle.config の利点】
   ・【再コンパイルなしで】バンドル構成を変更できる
   ・非開発者(デザイナ)でも編集できる
   ・Web Forms の「宣言的に書く」文化に合っている

【欠点】
   ・型チェックがない(パスの誤りは実行時まで分からない)
   ・CdnFallbackExpression のような高度な設定ができない
   ・【JS と CSS で定義場所が分かれる】= 分かりにくい ★

Bundle.config は CSS 専用ではない<scriptBundle> も書ける)が、
CDN フォールバックが要る JS はコード側で書く必要があるため、
結果として原文のような**「JS はコード、CSS は XML」**という
分担になったと読める。

CDNフォールバック

ScriptManager

ASP.NET Web Forms では ScriptManager に CDN フォールバック機能が実装されている。

  • アセンブリから取得する場合は CDN フォールバックの定義は不要。
  • 以下の場合は、CDN フォールバックの定義が必要になる。
    • 自前の ScriptManager 定義を使用する場合
    • CDN フォールバック動作をカスタマイズする場合

ScriptBundle

その他、ASP.NET MVC と同様に、ScriptBundle クラスで、
個別に CDN フォールバックの定義を行なうこともできる。

定義方法

詳しくは、コチラ

  • ScriptManager の場合(後述の「カスタマイズ」を参照)

  • ScriptBundle の場合(ASP.NET の Modernization を参照)

リンクのさせ方

初期設定が完了していること。

JS

Scripts.Render メソッドと <asp:ScriptManager> タグを使用する方法がある。

  • Scripts.Render

    • Header

      <title>・・・</title>
      <asp:PlaceHolder runat="server">
          <%: Scripts.Render("~/bundles/modernizr") %>
          <%: Scripts.Render("~/bundles/jquery") %>
      </asp:PlaceHolder>
    • Footer

      <%: Scripts.Render("~/bundles/XXXX") %>
  • ScriptManager

<body>
    <form runat="server">
        <asp:ScriptManager EnableCdn="true" runat="server">
            <Scripts>
                <%--ScriptManager のバンドル スクリプトの詳細については、http://go.microsoft.com/fwlink/?LinkID=301884 を参照してください --%>
                <%--Framework スクリプト--%>
                <asp:ScriptReference Name="MsAjaxBundle" />
                <asp:ScriptReference Name="jquery" />
                <asp:ScriptReference Name="bootstrap" />
                <asp:ScriptReference Name="respond" />
                <asp:ScriptReference Name="WebForms.js" Assembly="System.Web" Path="~/Scripts/WebForms/WebForms.js" />
                <asp:ScriptReference Name="WebUIValidation.js" Assembly="System.Web" Path="~/Scripts/WebForms/WebUIValidation.js" />
                <asp:ScriptReference Name="MenuStandards.js" Assembly="System.Web" Path="~/Scripts/WebForms/MenuStandards.js" />
                <asp:ScriptReference Name="GridView.js" Assembly="System.Web" Path="~/Scripts/WebForms/GridView.js" />
                <asp:ScriptReference Name="DetailsView.js" Assembly="System.Web" Path="~/Scripts/WebForms/DetailsView.js" />
                <asp:ScriptReference Name="TreeView.js" Assembly="System.Web" Path="~/Scripts/WebForms/TreeView.js" />
                <asp:ScriptReference Name="WebParts.js" Assembly="System.Web" Path="~/Scripts/WebForms/WebParts.js" />
                <asp:ScriptReference Name="Focus.js" Assembly="System.Web" Path="~/Scripts/WebForms/Focus.js" />
                <asp:ScriptReference Name="WebFormsBundle" />
                <%--サイト スクリプト--%>
            </Scripts>
        </asp:ScriptManager>

補足(<asp:PlaceHolder runat="server"> で囲む理由): 一見不要に
見えるが、必要な場合がある

【理由】
   <head runat="server"> の中では、
   <%: %> のようなコード式が【直接は書けない】ことがある
     → PlaceHolder(サーバ コントロール)で囲むと、
       その中身がコントロールのテンプレートとして解釈され、
       コード式が使えるようになる

<%: %><%= %> の違いも重要である。

<%= 式 %>   … そのまま出力(HTML エスケープしない)
<%: 式 %>   … 【HTML エスケープして出力】★ ASP.NET 4 で追加

  → XSS 対策として、原則 <%: %> を使う
    ([エンコーディング] 参照)

Scripts.RenderIHtmlString を返すため、
<%: %> を使ってもエスケープされない(二重エスケープにならない)。
よくできた設計である。

CSS

webopt:bundlereference と言うタグを使用するもよう。

<title>・・・</title>
<asp:PlaceHolder runat="server">
    <%: Scripts.Render("~/bundles/modernizr") %>
</asp:PlaceHolder>
<webopt:bundlereference runat="server" path="~/bundles/css" />

移行メモ(原文の見出しの誤字): 原文は「webopt:bundlereference と
言うタブ」となっていたため、「タグ」と読み替えて記載した。

ScriptReference

カスタマイズ

ScriptReference は、jQuery などのファイルのバージョンと
CDNを指定できない。
ただし、次のようにマッピングをカスタマイズすれば、
動作をカスタマイズして任意の CDNから任意のバージョンを取得できる。

var mapping = ScriptManager.ScriptResourceMapping;
// Map jquery definition to the Google CDN
mapping.AddDefinition("jquery", new ScriptResourceDefinition
{
    Path = "~/Scripts/jquery-2.0.0.min.js",
    DebugPath = "~/Scripts/jquery-2.0.0.js",
    CdnPath = "http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js",
    CdnDebugPath = "https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.js",
    CdnSupportsSecureConnection = true,
    LoadSuccessExpression = "window.jQuery"
});

補足(AddDefinition の名前を上書きする形になっている): このコードは
既定の "jquery" 定義を、自前の定義で置き換えている

【既定】 ScriptManager は "jquery" という名前で
         Microsoft.jQuery パッケージが登録した定義を持つ

【上書き】 AddDefinition("jquery", ...) で【同じ名前】を登録すると
           後勝ちで置き換わる

  → <asp:ScriptReference Name="jquery" /> の解決先が変わる

PathCdnPathDebugPathCdnDebugPath の使い分け:

                   debug=true            debug=false
CDN 使用(EnableCdn) CdnDebugPath        CdnPath
ローカル              DebugPath           Path

  → 4 通りの組み合わせがある
  → CDN が失敗すれば LoadSuccessExpression 経由でローカルへ落ちる

移行メモ: サンプルの CdnPathhttp://(平文)に
なっている点は、現在は問題である。
HTTPS のページから HTTP のスクリプトを読むと、
混在コンテンツ(Mixed Content)としてブラウザにブロックされる
**//ajax.googleapis.com/...(プロトコル相対)**か、
https:// を明示する。

ScriptBundleとの関連

同じような疑問を持つ人がいる。

以下に回答がある。

  • ASP.NET 4.5 ScriptManager Improvements in WebForms | .NET Web Development and Tools Blog
    https://devblogs.microsoft.com/dotnet/asp-net-4-5-scriptmanager-improvements-in-webforms/

  • ASP.NET 4.5 ScriptManager Improvements in WebForms – My.Ramblings
    https://devblogs.microsoft.com/pranav_rastogi/asp-net-4-5-scriptmanager-improvements-in-webforms/

    • MsAjaxJs

      • ScriptManager

        ScriptManager.ScriptResourceMapping.AddDefinition("MsAjaxBundle", new ScriptResourceDefinition
        {
            Path = "~/bundles/MsAjaxJs",
            CdnPath = "http://ajax.aspnetcdn.com/ajax/4.5/6/MsAjaxBundle.js", 
            LoadSuccessExpression = "window.Sys", 
            CdnSupportsSecureConnection = true
        });
        PreApplicationStartCode.AddMsAjaxMapping("MicrosoftAjax.js", "window.Sys && Sys._Application && Sys.Observer");
      • ScriptBundle

        bundles.Add(new ScriptBundle("~/bundles/MsAjaxJs").Include(
                    ・・・
    • WebFormsJs

      • ScriptManager

        ScriptManager.ScriptResourceMapping.AddDefinition("WebFormsBundle", new ScriptResourceDefinition
        {
            Path = "~/bundles/WebFormsJs", 
            ・・・
      • ScriptBundle

        bundles.Add(new ScriptBundle("~/bundles/WebFormsJs").Include(
                    ・・・
    • PreApplicationStartCode

結論としては、

  • ScriptManager の中から、ScriptBundle を組み合わせて使っている。
  • PreApplicationStartCode は、ScriptManager 内部の実装であり、
    ScriptManager 内で CDN フォールバックも定義されている。

となる。

補足(この結論が示す全体像): 原文がたどり着いた結論は正しく、
図にすると理解しやすい

【Web Forms のスクリプト配信の全体像】

  <asp:ScriptReference Name="MsAjaxBundle" />
         │
         ▼
  ScriptManager.ScriptResourceMapping(名前 → 定義の辞書)
         │  "MsAjaxBundle" → ScriptResourceDefinition
         │      Path    = "~/bundles/MsAjaxJs"    ← 【バンドルの仮想パス】★
         │      CdnPath = "//ajax.aspnetcdn.com/..."
         ▼
  Bundle の仕組み(System.Web.Optimization)
         │  "~/bundles/MsAjaxJs" のバンドルを解決
         ▼
  実際の JS ファイル群を連結・最小化して配信
【つまり】
   ScriptManager は「名前と CDN を管理する層」
   Bundle は「ファイルを連結・最小化する層」
     → 【上下に積み重なっている】。競合しているわけではない ★

【だから原文のコードで】
   "こちらのCDNフォールバック設定はScriptManager内で行われているため不要"
     というコメントが正しい
     → CDN の面倒は上の層(ScriptManager)が見る
     → 下の層(ScriptBundle)は連結だけすればよい

PreApplicationStartCode について:
引用の通り「独自に作成したコードから直接使用するためのものではない
——にもかかわらず、参考記事のサンプルが呼んでいる

【なぜ呼んでいるのか】
   ScriptManager の既定のマッピングを組み立てる内部メソッドで、
   公式ブログが【内部実装を説明するために示した】ものと読める

【実務での扱い】
   ・自分のコードから呼ぶべきではない(将来変わり得る)
   ・NuGet パッケージ(Microsoft.AspNet.ScriptManager.MSAjax 等)が
     【PreApplicationStartMethod 属性経由で自動的に呼ぶ】
      → だから「インストールするだけで動く」

原文が本節で行っている「なぜ 2 つあるのか」の調査は、
フレームワークの内部構造を追う良い例である。

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

機能概要

ASP.NET Web FormsRouteConfig では、
ファイルの拡張子(*.aspx など)を URL に付与しなくても済むよう、
「ASP.NET Friendly URLs」と言う機能を追加できる。

設定方法

インストール

「ASP.NET Friendly URLs」のインストール

Install-Package Microsoft.AspNet.FriendlyUrls

定義方法

  • RouteConfig を追加する。
    (最新の ASP.NET Web Forms プロジェクト・テンプレートから
    RouteConfig を自動生成して入手)。
public static class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        //var settings = new FriendlyUrlSettings();
        //settings.AutoRedirectMode = RedirectMode.Permanent;
        //routes.EnableFriendlyUrls(settings);
    }
}
  • RouteConfig を呼び出す。
    Global.asax or Startup から RouteConfig.RegisterRoutes メソッドを呼び出す。
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // アプリケーションの設定方法の詳細については、http://go.microsoft.com/fwlink/?LinkID=316888 を参照してください

        // アプリケーションのスタートアップで実行するコードです

        // URLルーティングの登録
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        // バンドル&ミニフィケーションの登録
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

補足(サンプルがコメント アウトされている点と、AutoRedirectMode: 原文の
RegisterRoutes中身がすべてコメント アウトされている。
テンプレートの原型を残しつつ、機能は有効にしていない
という状態と読める(Open 棟梁のテンプレートは
独自のルーティングを持つため、と推測される)。

AutoRedirectMode の意味:

settings.AutoRedirectMode = RedirectMode.Permanent;
動作
Off(既定) 両方の URL で動く/Default.aspx/Default も)
Permanent /Default.aspx301 で /Default へリダイレクト
Temporary 同上、ただし 302
【Permanent を使う理由】
   ・【URL を 1 つに正規化】する(SEO で重複コンテンツを避ける)
   ・利用者のブックマークを新しい URL に寄せる

【注意】
   ・301 は【ブラウザが強くキャッシュする】
     → 誤った設定を入れると、取り消しが効きにくい
     → 検証環境で十分に確認してから本番へ

移行メモ(ASP.NET Friendly URLs の現況):
Microsoft.AspNet.FriendlyUrls
更新が止まっており、.NET(Core 系)版はない
ASP.NET Core では、
ルーティングが標準機能なので、そもそも不要である
ASP.NET の RouteConfig)。

参考

Bundle と Minification

CDN フォールバック

Microsoft Learn


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

NetDevInfraWiki

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

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally