Skip to content

MS_ASPNETAssemblyLoading

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

ASP.NET でのアセンブリ DLL 読み込み

概要

システムの運用中に、誤って ASP.NET の bin フォルダに DLL ファイルを
コピーしてしまうなどの問題でシステム障害を発生させてしまうという
可能性があるようです。
このため、ココでは、ASP.NET での bin ファイルからのアセンブリ(DLL ファイル)
読み取りについての仕組みを説明します。

補足(本ページが解こうとしている問題): 一言でいうと、
**「bin に置いただけの DLL が、勝手に読み込まれてしまう」**という
ASP.NET 固有の挙動である。

【Windows フォーム等】
   参照設定した DLL を、必要になった時点で読み込む(遅延ロード)
     → 置いてあるだけの DLL は読まれない

【ASP.NET】
   起動時に bin フォルダの DLL を「全部」読み込む(既定)
     → 置いてあるだけの DLL も読まれる
     → 壊れた DLL / 別プラットフォーム向けの DLL があると起動に失敗する

なぜ全部読むのかは、プリコンパイル
表裏の関係にある。
ASP.NET は .aspx実行時に動的コンパイルするため、
コンパイラに渡す参照アセンブリの一覧をあらかじめ揃えておく必要がある
その手段が「bin の中身を全部参照扱いにする」だった。

詳細

1. はじめに

  • ASP.NET のアセンブリ [^1] DLL 読み込みは、
    Windows フォームなど他の技術によるアプリケーションのアセンブリ DLL 読み込みとは
    異なる方式が採用されている。
    この点を配慮せず ASP.NET のローカル bin フォルダ内へ
    むやみにアセンブリ DLL を大量登録すると、思わぬ弊害を生じる場合がある。
  • ここでは ASP.NET のアセンブリ DLL 読み込み方式と、
    それにより生じうる弊害などについて説明する。
  • 特記なき限り、以下は ASP.NET Web アプリケーション および
    ASP.NET Web サイト の両方が該当する内容である。

[^1]: .NET Framework 環境で動作する、EXE・DLL の各実行ファイルをアセンブリと呼ぶ。
ここではクラス ライブラリ等として使用する DLL ファイルが対象である
(EXE ファイルは対象外である) ため、「アセンブリ DLL」と表記する。

移行メモ(脚注の扱い): 原文は PukiWiki の脚注記法
(( )))を多用していたが、GitHub Markdown では
脚注記法([^n])または本文への展開で表現する。
短いものは本文に展開し、注釈として意味のあるものは脚注とした。

2. ASP.NET 実行開始時点でアセンブリ DLL を読み込むことにより生じうる弊害

2.1 既定の場合

  • ASP.NET のローカル bin フォルダ内に、
    ASP.NET 環境での動作が想定されていないアセンブリ DLL がコピーされていても、
    それを ASP.NET から読み込んでしまう。
    場合によっては、実行時エラーとなることもある。
    • 例) 開発環境専用の
      (たとえば Visual Studio 自体から使用されること以外を想定していない) クラス ライブラリ
    • 例) ASP.NET 以外の環境を前提とする
      (たとえば Silverlight 専用などの) クラス ライブラリ
  • 不必要なものも含む大量のアセンブリ DLL を
    ASP.NET のローカル bin フォルダ内へコピーしている場合は、
    その不必要な読み込みにより性能に影響が出るおそれがある。

補足(実際に起きる症状): 概要が言う「システム障害」は、
具体的には次のような形で現れる。

症状 原因
アプリ全体が起動しない(500) 読み込めない DLL が 1 つでもあると失敗
BadImageFormatException x86 / x64 の不一致、または .NET 以外の DLL
FileLoadException 依存 DLL の版が合わない
型の重複エラー 同じ型を含む DLL が 2 つある
起動が遅い 大量の DLL を読み込んでいる

BadImageFormatException は特に典型的で、
「ネイティブ DLL(C++ 製)を bin に置いた」
「x86 用の DLL を 64bit のアプリ プールで読んだ」
といった場合に起きる。

ネイティブ DLL を bin に置く必要がある場合(P/Invoke で使う等)
  → 全読込の対象になり、起動が失敗する
  → 後述 5.5 の <remove> で除外する、
    または bin 直下ではなく別フォルダに置いて
    明示的に LoadLibrary する

2.2 読み込み設定を指定し直した場合

  • 設定次第では、パーサー エラー[^2] や
    コンパイル エラー^3 となる。

[^2]: 当該 Web アプリケーション自身または当該 Web サイト自身の
アセンブリ DLL を指定しなかった場合は、ASP.NET コンテンツ (*.aspx 等) の
閲覧時にパーサー エラーとなる。
「パーサー エラー メッセージ: 型 '『名前空間の名称』.Global' を読み込めませんでした。」

%SystemRoot%\Microsoft.NET\Framework\『バージョン番号』\Config\web.config
<system.web><pages><namespaces>《あらかじめインポートする名前空間の定義》</namespaces></pages></system.web>
で指定されている名前空間によっては、それに対応するアセンブリ DLL を
(5.3 により) 明示的に読み込まないと、ASP.NET コンテンツ (*.aspx 等) の
閲覧時にコンパイル エラーとなる場合がある。
「コンパイル エラー メッセージ: CS0234: 型または名前空間名 'yyyyy' は
名前空間 'xxxxx' に存在しません。アセンブリ参照が不足しています。」

補足(既定で必要になる代表的な組): 原文が脚注で挙げている
対応関係を表にしておく。全読込を無効にした場合
これらを明示的に追加しないとコンパイル エラーになる。

ルートの web.config が既定で import する名前空間 必要なアセンブリ
System.Linq System.Core
System.Web.DynamicData System.Web.Extensions
System.Xml.Linq System.Xml.Linq

使わない名前空間は、逆に取り除くという手もある。

<system.web>
  <pages>
    <namespaces>
      <remove namespace="System.Web.DynamicData" />
    </namespaces>
  </pages>
</system.web>

なお、CS0234 や CS0246 と症状が似ているが、
こちらは実行時(動的コンパイル)に出るコンパイル エラーである点が
異なる。ビルドは通るのにブラウザで開くとエラー、という形で現れる。

3. ASP.NET 実行開始時点で読み込む対象のアセンブリ DLL

3.1 既定の場合

  • 既定では後述 5.6
    全読込設定が有効なため、以下のアセンブリ DLL 読み込みをする。
    • ASP.NET のローカル bin フォルダ内にあるすべてのアセンブリ DLL を読み込む[^4]。
      アセンブリ DLL の対象プラットフォームを問わない。
    • 既定値 (後述 5.2) として指定されているアセンブリ DLL を読み込む。

[^4]: MSDN ライブラリ「ASP.NET Web サイト内の共有コード フォルダ」
https://learn.microsoft.com/ja-jp/previous-versions/aspnet/t990ks23(v=vs.100)\ 「Bin フォルダのアセンブリは登録する必要がありません。
Bin フォルダに .dll ファイルが存在するだけで、ASP.NET は認識できます。」

3.2 読み込み設定を指定し直した場合

  • 全読込設定を後述 5.4 で無効にした上で、
    後述 5.3 により特定のアセンブリ DLL を指定すると、
    その指定したアセンブリ DLL のみ読み込む。
    たとえ ASP.NET のローカル bin フォルダ内に格納されているアセンブリ DLL であっても、
    5.3 で記されていないアセンブリ DLL は読み込まない。

4. ASP.NET 実行開始時点の読み込みを終えた後の、アセンブリ DLL 逐次読み込み

  • 3. はアプリケーション実行開始時点で
    一斉に読み込むアセンブリ DLL の話である。
    初期読込完了後はどのようなタイミングであっても、
    読み込んでいないアセンブリ DLL を必要とした時点で、当該アセンブリ DLL を読み込むことになる。

補足(ここが対策の要点): 4. が意味するのは、
**「全読込を切っても、実際に使う DLL はちゃんと読まれる」**ということである。

全読込 ON(既定)
  起動時: bin の全 DLL を読む → 使わないものまで読む(弊害)
  実行時: 必要に応じて読む

全読込 OFF + 明示指定
  起動時: 指定した DLL だけ読む
  実行時: 必要に応じて読む  ← ここは変わらない
    → 動的コンパイルで名前を解決できないものだけが問題になる

つまり、「動的コンパイル(.aspx の解析)に必要なもの」だけを
明示すればよく、通常のコードから使う DLL は明示不要
である。
これが 5.3 の設定を現実的にしている。

5. ASP.NET 実行開始時点で読み込むアセンブリ DLL の定義

5.1 定義場所

<system.web>
    <compilation>
        <assemblies>
            ★★★
        </assemblies>
    </compilation>
</system.web>

にある <assemblies></assemblies>[^5] の子要素として、★★★ に定義する
(要素数は 0 個以上)。

  • なお、この定義は本来コンパイルで使用されるコンパイル設定値[^6] に
    分類されているが、実際はコンパイルに限らずアプリケーション実行開始時点でも使用される。

[^5]: MSDN ライブラリ「compilation の assemblies 要素 (ASP.NET 設定スキーマ)」
https://learn.microsoft.com/ja-jp/previous-versions/dotnet/netframework-4.0/bfyb45k1(v=vs.100)

[^6]: MSDN ライブラリ「compilation 要素 (ASP.NET 設定スキーマ)」
https://learn.microsoft.com/ja-jp/previous-versions/dotnet/netframework-4.0/s10awwz0(v=vs.100)

補足(この一文が重要): 「本来コンパイル設定なのに、
アプリケーション実行開始時点でも使用される」という原文の指摘が、
本ページ全体の肝である。

<compilation><assemblies> という名前から
  「コンパイル時にしか関係ない」と誤解される
    ↓
実際には「起動時にどの DLL を読むか」も決めている
    ↓
bin に余計な DLL を置くと起動に影響する

5.2 読み込み設定の既定値

  • 5.1 の定義を省略した場合や、
    5.1 で指定すべき子要素の個数が 0 の場合は、既定値として
%SystemRoot%\Microsoft.NET\Framework\『バージョン番号』\Config\web.config
(※『バージョン番号』は、アプリケーションの使用ランタイムに依存する。)

にある

<system.web><compilation><assemblies>★★★</assemblies></compilation></system.web>

の設定内容が適用される。

5.3 読み込むアセンブリ DLL を指定する

<add assembly="アセンブリ名" />

と指定されたアセンブリ DLL が、読み込み対象となる。

補足: 原文が脚注で注意している通り、
指定するのは「アセンブリ名」であり「ファイル名」ではない

<!-- 正: アセンブリの表示名(拡張子なし、または完全修飾名) -->
<add assembly="MyCompany.Common" />
<add assembly="System.Core, Version=4.0.0.0, Culture=neutral,
               PublicKeyToken=B77A5C561934E089" />

<!-- 誤: ファイル名 -->
<add assembly="MyCompany.Common.dll" />

アセンブリ で述べた通り、
厳密名付きのアセンブリは完全修飾名で指定できる

5.4 読み込み設定の既定値をすべて削除する方法

  • 5.2 の設定既定値を全部削除するには、
    個別の Web.config にて 5.1 で指定する子要素の
    冒頭に <clear /> を記述する。

5.5 読み込み設定の既定値を個別に削除する方法

  • 5.2 の設定既定値を一つ一つ削除するには、
    個別の Web.config にて 5.1 で指定する子要素に
    <remove assembly="アセンブリ名" /> を記述する。
  • 既定のまま後述 5.6
    全読込設定となっている場合に、
    特定の (ASP.NET のローカル bin フォルダに配置する) アセンブリ DLL を
    個別に読み込み対象から削除した場合は、当該アセンブリ DLL に限り全読み込みの対象から外れる。
    (この場合も、当該アセンブリ DLL を必要とした時点で逐次読み込むのは、
    4. の通りである。)

補足(実務ではこれが最も使いやすい): 5.4(全部消す)より、
5.5(問題のある 1 つだけ除外する)方が現実的である。

<!-- ネイティブ DLL や、読み込ませたくないものだけを除外 -->
<system.web>
  <compilation>
    <assemblies>
      <remove assembly="SomeNativeInterop" />
    </assemblies>
  </compilation>
</system.web>

5.4 で <clear /> する運用は、必要なアセンブリを
すべて列挙する責務が発生する
ため、
2.2 のコンパイル エラーを踏みやすく、保守も重い。

**本来の対処は「bin に余計なものを置かない」**ことであり、
VSソリューション プロジェクトの構成検討
配置手順(ASP.NETの配置(Web Deploy・Web Deployパッケージ))で
配置物を統制するのが筋の良い解決になる。

5.6 ASP.NET のローカル bin フォルダにあるアセンブリ DLL の全読込設定

  • 全読込設定は既定値としてあらかじめ指定されているため、原則として指定不要である。
  • 5.3 の、アセンブリ名部分にワイルドカード
<add assembly="*" />

を指定すると、ASP.NET のローカル bin フォルダにある
アセンブリ DLL をすべて読み込む。

6. ASP.NET で読み込むアセンブリ DLL の定義と、ビルドの関係

  • ここでは Visual Studio 上の「ビルド」操作をしたときのビルド、
    および IIS 上の動的コンパイルについて記す。
    (ASP.NET の事前コンパイルは、ここでは説明の範囲外とする。)

6.1 ASP.NET Web アプリケーションの場合

  • ソリューション (*.sln) 上でビルドする時点では、
    コンパイルに必要なアセンブリ DLL が 5.1 での設定から漏れていた場合であっても
    コンパイル エラーとされない。
    (コンパイルそのものは、ASP.NET Web アプリケーションではコード部分のみ実施する。)
  • IIS に配置し、それを初めて (またはコンパイル エラーが修正されないまま)
    ブラウザ等から参照した時点で、コンテンツ ファイル (*.aspx 等) の動的コンパイルが走る。
    (コンパイルに必要なアセンブリ DLL が 5.1 での設定から漏れていた場合は、
    ここでコンパイル エラーになる。)

6.2 ASP.NET Web サイトの場合

  • ソリューション (*.sln) 上でビルドする時点で、コンパイル エラーに関するチェックがなされる。
    (コンパイルそのものは、ASP.NET Web サイトではビルド時に実施しない。)
  • コンパイルに必要なアセンブリ DLL が 5.1 での設定から漏れていた場合に、
    コンパイル エラーとされる。
  • IIS に配置し、それを初めて (またはコンパイル エラーが修正されないまま)
    ブラウザ等から参照した時点で、コンテンツ ファイル (*.aspx 等) の動的コンパイルが走る。
    (コンパイルに必要なアセンブリ DLL が 5.1 での設定から漏れていた場合は、
    ここでもコンパイル エラーになる。)

補足(この違いの背景): 6.1 と 6.2 の差は、
ASP.NETの構成(Webサイト・Webアプリ) で述べた
「いつコンパイルするか」の違いそのものである。

Web アプリケーション Web サイト
ビルド時にコンパイルするもの コードのみ (実際にはしない。検証のみ)
.aspx のコンパイル 実行時 実行時
ビルド時にエラーを検出できるか コードのみ .aspx も検証される

どちらにせよ .aspx の実際のコンパイルは実行時であるため、
本番に配置して初回アクセスするまで分からないという
危うさが残る。

対策は事前コンパイルプリコンパイル)で、
aspnet_compiler.exe を CI に組み込めば
配置前に .aspx のコンパイル エラーを検出できる

aspnet_compiler.exe -v / -p <発行フォルダ> <出力先>
  → エラーがあればここで落ちる

なお、ASP.NET Core ではこの問題が存在しない
.cshtml を含めてビルド時にコンパイルされ、
bin の全読込という挙動も無い)。

参考資料

ASP.NET のローカル bin フォルダ

ASP.NET のコンパイルのタイミング

移行メモ(外部画像): 原文は @IT の図(2-16.gif)を
&ref() で外部参照していたが、
外部サイトの画像を直接埋め込むことはせず
リンクとして掲載した。


Tags: 移行, .NET開発, ASP.NET, 障害対応

NetDevInfraWiki

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

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally