-
Notifications
You must be signed in to change notification settings - Fork 0
MS_ASPNETAssemblyLoading
- 戻る(ASP.NET)
システムの運用中に、誤って 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の中身を全部参照扱いにする」だった。
- 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])または本文への展開で表現する。
短いものは本文に展開し、注釈として意味のあるものは脚注とした。
- ASP.NET のローカル bin フォルダ内に、
ASP.NET 環境での動作が想定されていないアセンブリ DLL がコピーされていても、
それを ASP.NET から読み込んでしまう。
場合によっては、実行時エラーとなることもある。- 例) 開発環境専用の
(たとえば Visual Studio 自体から使用されること以外を想定していない) クラス ライブラリ - 例) ASP.NET 以外の環境を前提とする
(たとえば Silverlight 専用などの) クラス ライブラリ
- 例) 開発環境専用の
- 不必要なものも含む大量のアセンブリ DLL を
ASP.NET のローカル bin フォルダ内へコピーしている場合は、
その不必要な読み込みにより性能に影響が出るおそれがある。
補足(実際に起きる症状): 概要が言う「システム障害」は、
具体的には次のような形で現れる。
症状 原因 アプリ全体が起動しない(500) 読み込めない DLL が 1 つでもあると失敗 BadImageFormatExceptionx86 / 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] や
コンパイル エラー^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.LinqSystem.CoreSystem.Web.DynamicDataSystem.Web.ExtensionsSystem.Xml.LinqSystem.Xml.Linq使わない名前空間は、逆に取り除くという手もある。
<system.web> <pages> <namespaces> <remove namespace="System.Web.DynamicData" /> </namespaces> </pages> </system.web>なお、CS0234 や CS0246 と症状が似ているが、
こちらは実行時(動的コンパイル)に出るコンパイル エラーである点が
異なる。ビルドは通るのにブラウザで開くとエラー、という形で現れる。
- 既定では後述 5.6 の
全読込設定が有効なため、以下のアセンブリ DLL 読み込みをする。- ASP.NET のローカル bin フォルダ内にあるすべてのアセンブリ DLL を読み込む[^4]。
アセンブリ DLL の対象プラットフォームを問わない。 - 既定値 (後述 5.2) として指定されているアセンブリ DLL を読み込む。
- ASP.NET のローカル bin フォルダ内にあるすべてのアセンブリ DLL を読み込む[^4]。
[^4]: MSDN ライブラリ「ASP.NET Web サイト内の共有コード フォルダ」
https://learn.microsoft.com/ja-jp/previous-versions/aspnet/t990ks23(v=vs.100)\
「Bin フォルダのアセンブリは登録する必要がありません。
Bin フォルダに .dll ファイルが存在するだけで、ASP.NET は認識できます。」
- 全読込設定を後述 5.4 で無効にした上で、
後述 5.3 により特定のアセンブリ DLL を指定すると、
その指定したアセンブリ DLL のみ読み込む。
たとえ ASP.NET のローカル bin フォルダ内に格納されているアセンブリ DLL であっても、
5.3 で記されていないアセンブリ DLL は読み込まない。
-
3. はアプリケーション実行開始時点で
一斉に読み込むアセンブリ DLL の話である。
初期読込完了後はどのようなタイミングであっても、
読み込んでいないアセンブリ DLL を必要とした時点で、当該アセンブリ DLL を読み込むことになる。
補足(ここが対策の要点): 4. が意味するのは、
**「全読込を切っても、実際に使う DLL はちゃんと読まれる」**ということである。全読込 ON(既定) 起動時: bin の全 DLL を読む → 使わないものまで読む(弊害) 実行時: 必要に応じて読む 全読込 OFF + 明示指定 起動時: 指定した DLL だけ読む 実行時: 必要に応じて読む ← ここは変わらない → 動的コンパイルで名前を解決できないものだけが問題になるつまり、「動的コンパイル(
.aspxの解析)に必要なもの」だけを
明示すればよく、通常のコードから使う DLL は明示不要である。
これが 5.3 の設定を現実的にしている。
<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 を置くと起動に影響する
%SystemRoot%\Microsoft.NET\Framework\『バージョン番号』\Config\web.config
(※『バージョン番号』は、アプリケーションの使用ランタイムに依存する。)
にある
<system.web><compilation><assemblies>★★★</assemblies></compilation></system.web>の設定内容が適用される。
- 5.1 に
<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.2 の設定既定値を全部削除するには、
個別の Web.config にて 5.1 で指定する子要素の
冒頭に<clear />を記述する。
-
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.3 の、アセンブリ名部分にワイルドカード
<add assembly="*" />を指定すると、ASP.NET のローカル bin フォルダにある
アセンブリ DLL をすべて読み込む。
- ここでは Visual Studio 上の「ビルド」操作をしたときのビルド、
および IIS 上の動的コンパイルについて記す。
(ASP.NET の事前コンパイルは、ここでは説明の範囲外とする。)
- ソリューション (*.sln) 上でビルドする時点では、
コンパイルに必要なアセンブリ DLL が 5.1 での設定から漏れていた場合であっても
コンパイル エラーとされない。
(コンパイルそのものは、ASP.NET Web アプリケーションではコード部分のみ実施する。) - IIS に配置し、それを初めて (またはコンパイル エラーが修正されないまま)
ブラウザ等から参照した時点で、コンテンツ ファイル (*.aspx 等) の動的コンパイルが走る。
(コンパイルに必要なアセンブリ DLL が 5.1 での設定から漏れていた場合は、
ここでコンパイル エラーになる。)
- ソリューション (*.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の全読込という挙動も無い)。
- MSDN ライブラリ「ASP.NET Web サイト内の共有コード フォルダ」
https://learn.microsoft.com/ja-jp/previous-versions/aspnet/t990ks23(v=vs.100)
-
@IT「ASP.NETアプリケーションの構造 ― 第2章 ASP.NET の基礎 2.2 ―」
http://www.atmarkit.co.jp/fdotnet/bookpreview/learnaspnet_0201/learnaspnet_0201_01.html -
ASP.NET の動的コンパイルの概要
https://learn.microsoft.com/ja-jp/previous-versions/aspnet/ms366723(v=vs.100) -
ASP.NET コンパイルの概要
https://learn.microsoft.com/ja-jp/previous-versions/aspnet/ms178466(v=vs.100)\ ※: ただし、ASP.NET Web サイトに特化した説明である。
移行メモ(外部画像): 原文は @IT の図(
2-16.gif)を
&ref()で外部参照していたが、
外部サイトの画像を直接埋め込むことはせず、
リンクとして掲載した。
Tags: 移行, .NET開発, ASP.NET, 障害対応
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。