ASP.NET 4.8.1(VB)で NuGet を更新したのに、bin の DLL や web.config の bindingRedirect が古い表記に見える――これは .NET Framework 特有の「パッケージ版数」と「アセンブリ版数」のズレが原因です。この記事では現象の正体を分解し、実際にロードされる DLL を最新版へ揃えるための手順、運用で詰まらないための設計・検証ポイントまで一気に整理します。
質問の背景と現象の整理
NuGet で最新化した直後に、画面やファイルごとに「見えるバージョン」が異なることがあります。代表例を下表にまとめます。
| ファイル/画面 | ⾒えるバージョン例 | 期待バージョン例 |
|---|---|---|
| packages.config | Newtonsoft.Json 13.0.3 System.Memory 4.6.3 | – |
| bin フォルダーの DLL web.config の bindingRedirect | Newtonsoft.Json 13.0.0 System.Memory 4.0.5 | – |
| NuGet マネージャー UI | 「最新版インストール済み」 | – |
この「食い違い」は、次の3つの事実が重なった結果です。
- NuGet UI は packages.config を見ているだけ(パッケージ版数)。
- DLL の AssemblyVersion は互換重視で固定される(アセンブリ版数)。
- bindingRedirect はアセンブリ版数で記述する(ファイル版数ではない)。
用語の早見表
| 項目 | どこに出る? | 意味と性質 | よくある勘違い |
|---|---|---|---|
| NuGet パッケージバージョン | packages.config / NuGet UI | 配布単位(.nupkg)の版数。13.0.3 等 | これが DLL の AssemblyVersion と一致するとは限らない |
| AssemblyVersion | DLL のメタデータ(参照解決に使用) | 互換性のためパッチでは固定されがち(例:Newtonsoft.Json 13.0.0.0) | 「13.0.0.0 と表示=古い DLL」とは限らない |
| FileVersion | DLL のプロパティ(ファイル詳細) | ビルドごとに上がる実体版数(例:13.0.3.0) | ランタイムの参照解決には直接使われない |
| bindingRedirect | web.config / app.config | 旧版要求を アセンブリ版数の新しい値へ寄せる規則 | FileVersion を書く場所ではない |
結論:古い表記に見えても「実体は最新版」のことがある
Newtonsoft.Json を例にすると、13.0.3 の DLL でも AssemblyVersion は 13.0.0.0 に据え置かれます。従って、bin にある DLL のプロパティで FileVersion が 13.0.3.x なら、実体は 13.0.3 のファイルです。bindingRedirect にも newVersion="13.0.0.0" と書かれますが、これは「アセンブリ版数」であり正常です。
実際にロードされる DLL を最新版に揃えるための基本手順
- ビルド環境をクリーンにする
- Visual Studio:「クリーン」→「リビルド」。
- その後、必要に応じて
bin/objを手動削除。 - NuGet キャッシュを疑う場合は
nuget locals all -clear。
- 参照の再解決
パッケージ マネージャー コンソールで実行します。Update-Package -Reinstallこれでpackages.configの版数に沿って参照が引き直され、binに正しい DLL が再配置されます。 - bindingRedirect の自動生成/更新
- ビルド時に出る「バインドの競合」警告から自動生成を実行。
- プロジェクト ファイル(.vbproj)に以下の MSBuild プロパティを設けると自動化できます。
<PropertyGroup> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType> </PropertyGroup> - ファイル実体の確認
bin\Newtonsoft.Json.dllのファイル プロパティで ファイル バージョン と 更新日時 を確認。- 必要に応じてランタイムでバージョンを出力(サンプルは後述)。
bindingRedirect の書き方(実践テンプレート)
ポイントは「newVersion は AssemblyVersion」「oldVersion の上限も AssemblyVersion」です。Newtonsoft.Json の場合:
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json"
publicKeyToken="30ad4fe6b2a6aeed"
culture="neutral" />
<!-- 旧版の要求をアセンブリ版 13.0.0.0 へリダイレクト -->
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0"
newVersion="13.0.0.0" />
</dependentAssembly>
備考: Visual Studio の自動生成では oldVersion="0.0.0.0-13.0.0.0" のように上限がアセンブリ版に設定されるのが一般的です。FileVersion(13.0.3.x など)を上限に書く必要はありません。
System.Memory など BCL 系パッケージの見え方
System.Memory や System.Buffers など .NET Standard 対応の BCL 系パッケージは、4.0.x のアセンブリ版を保ったまま、NuGet のパッチ(4.6.3 など)で配布されます。これも同じ理屈で、bin の DLL が 4.0.x に見えても FileVersion が上がっていれば実体は最新です。
「本当に最新版が読み込まれているか」を 100% 確認する方法
ランタイムでの自己診断(VB / ASP.NET)
動いているアプリから読み込まれたアセンブリ名・場所・版数をダンプします。管理者向けの診断ページとして用意すると便利です。
<%@ Page Language="VB" %>
<script runat="server">
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
Dim asm = AppDomain.CurrentDomain.GetAssemblies() _
.Where(Function(a) a.GetName().Name = "Newtonsoft.Json") _
.FirstOrDefault()
If asm IsNot Nothing Then
Dim an = asm.GetName()
Response.Write("Assembly: " & an.Name & "<br/>")
Response.Write("AssemblyVersion: " & an.Version.ToString() & "<br/>")
Response.Write("Location: " & asm.Location & "<br/>")
Dim fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(asm.Location)
Response.Write("FileVersion: " & fvi.FileVersion & "<br/>")
Else
Response.Write("Newtonsoft.Json は読み込まれていません。")
End If
End Sub
</script>
Fusion ログ(ロード トレース)で経路を可視化
- 「開発者用コマンド プロンプト」を管理者で起動し
fuslogvw.exeを実行。 - 「ログの設定」で「ディスクにログを残す(成功/失敗)」をオン、ログ保存先を指定。
- アプリにアクセスして読み込みを発生させ、ログの「Bind Logs」を開く。
ログには「要求されたアセンブリ」「適用された bindingRedirect」「最終的に読み込んだパス(GAC か bin か)」が記録されます。GAC からロードされているのに気付かないケースを検出できます。
CLR の参照解決の優先順位を正しく理解する
強名アセンブリ(Newtonsoft.Json など)は、概ね次の順序で解決されます(簡略化)。
- アプリ/マシンのポリシー(
bindingRedirect/ Publisher Policy) - GAC(グローバル アセンブリ キャッシュ)
- アプリの基準フォルダー(
bin)やcodeBase指定の場所
GAC は bin より優先されます。したがって GAC に 同じ AssemblyVersion の古い DLL(例:13.0.0.0)が残っていると、bin に新しいファイル(FileVersion 13.0.3.x)があっても GAC が勝つことがあります。「ローカル DLL を優先」では解決できません。対策は以下の通りです。
- GAC から当該アセンブリをアンインストールする(運用では GAC に入れないのが原則)。
- どうしても GAC 運用が必要なら、同じ AssemblyVersion の最新版に差し替える。
実運用での安全な更新フロー(チェックリスト)
| カテゴリ | チェック/作業 | 目的 |
|---|---|---|
| 更新前 | テスト環境の GAC を空にする(可能な範囲で) | bin の DLL だけで再現性を確保 |
| 更新 | Update-Package -Reinstall を実行 | 参照の再解決と bin への正しい配置 |
| 構成 | MSBuild の AutoGenerateBindingRedirects 有効化 | 人手ミスを予防 |
| 検証 | 診断ページで AssemblyVersion / FileVersion / 読み込みパスを出力 | GAC 読み込みの早期検知 |
| CI/CD | ビルド前に bin/obj を消去、nuget restore → ビルド → パブリッシュ | 古いアーティファクト混入の防止 |
web.config の実践スニペット集
Newtonsoft.Json の bindingRedirect
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json"
publicKeyToken="30ad4fe6b2a6aeed"
culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0"
newVersion="13.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
System.Memory の bindingRedirect
<dependentAssembly>
<assemblyIdentity name="System.Memory"
publicKeyToken="cc7b13ffcd2ddd51"
culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0"
newVersion="4.0.0.0" />
</dependentAssembly>
packages.config と PackageReference の違いと選択
ASP.NET 4.x でも PackageReference へ移行可能です。メリットは「ソリューション全体での重複削減」「packages フォルダーの膨張抑制」。ただし、Web アプリでは以下に注意します。
- Web.config に触れる一部パッケージは変換後の挙動が異なる場合あり(発行プロファイルでの変換確認が必須)。
- bindingRedirect は引き続き必要(自動生成を有効に)。
- 混在(packages.config と PackageReference の同居)はトラブルの元。どちらかへ統一します。
移行の基本手順は、バックアップ → 変換 → ビルド&発行テスト → 監視強化下で段階的リリース。不安があるなら保守中の Web アプリは packages.config のままでも問題ありません。
よくある落とし穴と対処
| 落とし穴 | 原因 | 対処 |
|---|---|---|
bin に古い DLL が残留 | 以前のビルド成果物がコピーされ続ける | クリーン→bin/obj 手動削除→リビルド |
| GAC が勝ってしまう | 強名アセンブリは GAC 優先 | GAC から削除 or 最新に入れ替え(基本は GAC 不使用) |
bindingRedirect が古い | 手動編集の取りこぼし | 自動生成を有効化し、ビルド警告から適用 |
| PackageReference と packages.config の混在 | プロジェクト間で参照方式がバラバラ | 方式を統一、移行はブランチを切って段階的に |
| サードパーティ DLL が旧版を要求 | 依存ライブラリのバージョン固定 | bindingRedirect を広めに設定し、統合テストで破壊的変更の有無を確認 |
発行(Publish)で「ローカルはOK、サーバーはNG」を防ぐ
- ビルド サーバーでも
nuget restoreを 必ず実行。 - 発行前に
bin/objを削除し、クリーンな出力を作る。 - Web Deploy / Zip 発行どちらでも、出力フォルダーの DLL の FileVersion をチェックするステップを CI に組み込む。
- ロード失敗時の診断用に 診断ページ(前述の VB スニペット)をステージングにだけ常備。
安全に“最新版へそろえる”ためのフル手順(コピペ用)
- VS を閉じる →
bin/objを削除。 - VS を開く → 「ソリューションの NuGet パッケージの管理」で対象を最新へ更新。
- パッケージ マネージャー コンソールで:
Update-Package -Reinstall # 必要なら nuget locals all -clear - ビルド → 警告で促されたら bindingRedirect を生成。
binの DLL の FileVersion と更新日時を確認。- 診断ページで AssemblyVersion / FileVersion / Location を表示、GAC 読み込みがないか確認。
- CI へコミット → 発行物の DLL 版数を自動検査。
トラブルを未然に防ぐ設計のコツ
- GAC を使わない(IIS サーバーでもアプリ ローカル配置を原則に)。
- 診断を埋め込む(Assembly 情報を返すヘルスチェック エンドポイント)。
- パッケージ単位で更新しない(関連パッケージはバンドルで上げる。例:System.Memory と System.Buffers)。
- bindingRedirect の自動生成を常時オンにし、人手編集を最小限に。
- Release 変換(
Web.Release.config)で本番固有設定と衝突しないよう、assemblyBindingセクションに触れない原則を設ける。
ケーススタディ:Newtonsoft.Json 12 → 13 の引き上げ
メジャー更新は API 破壊の可能性があるため、bindingRedirect だけでは救えない場合があります。推奨ステップ:
- プロジェクト内の
Imports Newtonsoft.Json付近のビルド エラーを解消。 - シリアライズ/デシリアライズの
JsonSerializerSettings互換性を重点テスト。 - 依存ライブラリ(古い SDK 等)が 旧 AssemblyVersion を要求していないか確認(Fusion ログ)。
- 問題が出たら、一段階下のパッチで固定(例:13.0.2)→ 安定後に 13.0.3 へ段階更新。
「見かけの不一致」を怖がらないための心構え
ASP.NET 4.8.1 の世界では、「NuGet の版数」「AssemblyVersion」「FileVersion」「bindingRedirect」の四者が 同じ値になるとは限りません。大切なのは、ランタイムがどの DLL をどこから読み込んだかです。FileVersion と読み込みパス(bin or GAC)を確認できれば、表記ゆれに惑わされずに正しい判断ができます。
最後に:要点まとめ
- NuGet UI は packages.config の数字を表示するだけ。DLL/bindingRedirect は自動で完全には揃わない。
- AssemblyVersion 固定は仕様。13.0.0.0 と表示でも FileVersion 13.0.3.x なら最新版の可能性が高い。
- bindingRedirect は AssemblyVersion で書く。Newtonsoft.Json なら
0.0.0.0-13.0.0.0 → 13.0.0.0。 - GAC は bin より優先。GAC 残留は真っ先に疑い、基本は使わない。
- Update-Package -Reinstall と 自動 bindingRedirect で環境ズレを解消。
- 診断コード&Fusion ログで「どこから何が読まれたか」を可視化する。
付録:トラブル時のクイック FAQ
Q1. NuGet では「最新」と出るのに、web.config の bindingRedirect が古い?
A. NuGet は packages.config の数字だけを見ています。ビルド時の自動生成(または手動更新)で bindingRedirect を最新化してください。
Q2. bin の DLL が 13.0.0(古い?)に見える。
A. それは AssemblyVersion の表示です。ファイルのプロパティで FileVersion を確認してください。13.0.3.x なら最新版です。
Q3. 本番だけ動かない。開発機では再現しない。
A. 本番 IIS の GAC を疑ってください。Fusion ログで Location を確認し、GAC 読み込みなら削除 or 更新が必要です。
Q4. bindingRedirect を広く取りすぎるのは危険?
A. メジャーをまたぐと API 差で実行時例外の可能性があります。テストで安全が確認できる範囲にとどめ、メジャーアップ時は実装側の対応も行いましょう。
Q5. PackageReference に移行すべき?
A. 長期的には有利ですが、既存 Web アプリでは慎重に。まずは参照方式を統一し、CI での自動検証を整えたうえで段階移行がおすすめです。
サンプル:複数アセンブリの状態を一覧表示する小さな診断ページ(VB)
<%@ Page Language="VB" %>
<script runat="server">
Private Shared ReadOnly Targets As String() = {
"Newtonsoft.Json",
"System.Memory",
"System.Buffers"
}
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
Response.Write("<table border='1' cellspacing='0' cellpadding='6'>")
Response.Write("<tr><th>Name</th><th>AssemblyVersion</th><th>FileVersion</th><th>Location</th></tr>")
For Each name In Targets
Dim asm = AppDomain.CurrentDomain.GetAssemblies().
FirstOrDefault(Function(a) a.GetName().Name.Equals(name, StringComparison.OrdinalIgnoreCase))
If asm Is Nothing Then
Response.Write($"<tr><td>{name}</td><td colspan='3'>Not Loaded</td></tr>")
Else
Dim an = asm.GetName()
Dim ver = an.Version.ToString()
Dim loc = asm.Location
Dim fver = System.Diagnostics.FileVersionInfo.GetVersionInfo(loc).FileVersion
Response.Write($"<tr><td>{an.Name}</td><td>{ver}</td><td>{fver}</td><td>{loc}</td></tr>")
End If
Next
Response.Write("</table>")
End Sub
</script>
チェックリスト(コピペ保存推奨)
- NuGet 更新後に Update-Package -Reinstall を実行した。
- AutoGenerateBindingRedirects を有効化した。
bin/objの残骸を削除した。- 診断ページで AssemblyVersion / FileVersion / Location を確認した。
- GAC に同一 AssemblyVersion の古い DLL が無いことを確認した。
- CI で「発行物の DLL の FileVersion を検査」する仕組みを入れた。
以上を押さえておけば、「packages.config は新しいのに DLL が古く見える」問題で悩む時間は大幅に減ります。表記ゆれに振り回されず、ランタイムが何をどこから読み込むかという本質に集中しましょう。

コメント