組件繫結重新導向是處理 .NET 組件版本衝突的捷徑, 將特定版號範圍重新導向統一版本,以化解部分程式要用 A.dll 1.1 版,某些要用 A.dll 1.2 的版本衝突。

版本衝突問題在實務上並不算罕見,過去的處理經驗還不少,一併整理在此提供參考:

這篇文章將再繼續補充幾則重新導向設定注意事項以及載入失敗偵錯技巧。

<assemblyBinding> appliesTo 設定

最近遇到一起導向失敗案例,苦主回報確定已在 web.config 設定 assemblyBinding / bindingRedirect 卻未生效。檢查後發現其 assemblyBinding 寫法為:

  <runtime>
    <assemblyBinding appliesTo="v2.0.50727" xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Oracle.DataAccess" publicKeyToken="89b483f429c47342" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-9.9.9.9" newVersion="2.112.3.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

assemblyBinding appliesTo 指向 v2.0.50727 只適用 .NET 2.0/3.5,而 ASP.NET 網站為 .NET 4.0 因此導向無效,移除 appliesTo 後問題排除。

停用發行者原則導向

許多元件安裝時常會一併設定發行者原則,將舊版本統統導向新版。以 ODP.NET 12.1 為例,便會在 GAC 安裝 Policy.2.112.Oracle.DataAccess, 將 2.112.0.0 - 2.112.9999.9999 版都導向 2.121.1.0。

若我們刻意想沿用舊版,可在 dependentAssembly 加入 <publisherPolicy apply="no" />, 如此 .NET 將無視發行者原則,以 <bindingRedirect /> 導向的版本為準。

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
	<publisherPolicy apply="no" />
	<assemblyIdentity name="Oracle.DataAccess" publicKeyToken="89b483f429c47342" culture="neutral" />
	<bindingRedirect oldVersion="0.0.0.0-9.9.9.9" newVersion="2.112.3.0" />
  </dependentAssembly>
</assemblyBinding>

追查組件載入失敗原因

.NET 決定組件載入版本的程序很複雜,跟 GAC、發行者原則、web.config 指定版本、bindingRedirect 設定都有關聯, 遇到「無法載入檔案或組件 'Oracle.DataAccess' 或其相依性的其中之一。 找到的組件資訊清單定義與組件參考不符。 (發生例外狀況於 HRESULT: 0x80131040)」這類錯誤, 只知是版本問題,難以判斷原因。

有個偵錯小技巧,遇到這種狀況時修改 Registry,加入 HKLM/Software/Microsoft/Fusion DWORD EnableLog = 1

加入設定後重啟網站,錯誤訊息會多出繫結歷程,方便診斷錯誤發生原因。

=== 繫結前狀態資訊 ===
記錄: DisplayName = Oracle.DataAccess
 (Partial)
警告: 提供了組件的部分繫結資訊:
警告: 組件名稱: Oracle.DataAccess | 網域 ID: 2
警告: 如果只提供部分的組件顯示名稱,就會發生部分繫結。 
警告: 這可能會使繫結器載入不正確的組件。
警告: 建議為組件提供完全指定的文字識別,
警告: 該識別是由簡單名稱、版本、文化特性和公開金鑰語彙基元組成。
警告: 如需詳細資訊和這個問題的一般解決方法,請參閱白皮書 http://go.microsoft.com/fwlink/?LinkId=109270。
記錄: Appbase = file:///D:/Lab/TestWeb/
記錄: 初始 PrivatePath = D:\Lab\TestWeb\bin
正在呼叫組件 : (Unknown)。
===
記錄: 此繫結在 default 載入內容中開始。
記錄: 正在使用應用程式組態檔: D:\Lab\TestWeb\web.config
記錄: 使用主機組態檔: C:\Users\jeffrey\Documents\IISExpress\config\aspnet.config
記錄: 從 C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config 使用電腦組態檔。
記錄: 目前不會套用原則至參考 (私用、自訂、部分或以位置為主的組件繫結)。
記錄: 正在嘗試從新的 URL file:///C:/Users/jeffrey/AppData/Local/Temp/Temporary ASP.NET Files/vs/2701cdb7/c511ea4f/Oracle.DataAccess.DLL 下載。
記錄: 正在嘗試從新的 URL file:///C:/Users/jeffrey/AppData/Local/Temp/Temporary ASP.NET Files/vs/2701cdb7/c511ea4f/Oracle.DataAccess/Oracle.DataAccess.DLL 下載。
記錄: 正在嘗試從新的 URL file:///D:/Lab/TestWeb/bin/Oracle.DataAccess.DLL 下載。
記錄: 正在使用應用程式組態檔: D:\Lab\TestWeb\web.config
記錄: 使用主機組態檔: C:\Users\jeffrey\Documents\IISExpress\config\aspnet.config
記錄: 從 C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config 使用電腦組態檔。
記錄: 在 C:\WINDOWS\assembly\GAC_32\Policy.2.112.Oracle.DataAccess\2.121.1.0__89b483f429c47342\Policy.2.112.Oracle.DataAccess.config 找到發行者原則檔。
記錄: 找到發行者原則檔重新導向: 重新導向 2.112.1.0 至 2.121.1.0。
記錄: ProcessorArchitecture 鎖定為 x86。
記錄: 原則後參考: Oracle.DataAccess, Version=2.121.1.0, Culture=neutral, PublicKeyToken=89b483f429c47342, processorArchitecture=x86
記錄: 正在嘗試從新的 URL file:///C:/Users/jeffrey/AppData/Local/Temp/Temporary ASP.NET Files/vs/2701cdb7/c511ea4f/Oracle.DataAccess.DLL 下載。
記錄: 正在嘗試從新的 URL file:///C:/Users/jeffrey/AppData/Local/Temp/Temporary ASP.NET Files/vs/2701cdb7/c511ea4f/Oracle.DataAccess/Oracle.DataAccess.DLL 下載。
記錄: 正在嘗試從新的 URL file:///D:/Lab/TestWeb/bin/Oracle.DataAccess.DLL 下載。
警告: 比較組件名稱發現不符項目: 次要版本
錯誤: 無法完成組件的安裝 (hr = 0x80131040)。已終止探查。

以上述案例可知,專案原本參照 2.112.1.0,被發行者原則導向 2.121.1.0,但 2.121.1.0 版未註冊到 GAC,故最後取用 /bin/Oracle.DataAccess.dll, 而它是 2.112.1.0 版本,與預期的 2.121.1.0 不同因而發生次要版本不符錯誤。整個歷程一清二楚,使用 gacutil /i 將 2.121.1.0 安裝至 GAC 後,錯誤消失。

有了詳細資訊,未來抓組件版本問題就不用瞎猜囉~

同場加映 - 組件繫結 Log 檢視工具

上面前篇文章介紹過修改 Fusion/EnableLog Registry 在錯誤訊息顯示繫結失敗詳細過程的偵錯技巧, 但有些情境光在錯誤訊息夾帶明細還不夠,需要更積極蒐集 Log 才能取得足夠資訊。 例如:

  • 來不及顯示錯誤訊息程式就崩潰了,連渣都沒留下
  • 錯誤訊息因不當 try ... catch 忽略且未保留
  • 未抛出例外,但使用元件版本結果與預期不同,想追查原委

除了 HKLM/Software/Microsoft/Fusion 除了 EnableLog 外,還有其他旗標控制資訊搜集方式。 Visual Studio 安裝時有附了一個 Fusionvw.exe, 組件繫結 Log 檢視器,比手動設定 Registry 簡便,並提供 GUI 介面可快速查詢檢視 Log。 Fuslogvw.exe 設定畫面選項剛好對應到 Registry 旗標,我直接用它說明:

  • 停用記錄
    預設情況,Fusion Registry 未啟用任何旗標
  • 在例外狀況文字中記錄
    EnableLog = 1
  • 在磁碟中記錄失敗的繫結
    LogFailures = 1
  • 在磁碟中記錄所有繫結(成功或失敗)
    ForceLog = 1

自訂記錄檔路徑,則對應到 Registry 中的 LogPath 值。

透過上述設定,我們可以由 Log 尋找錯誤記錄,ForceLog = 1 時甚至可以檢查成功載入時的版本決定依據,但啟用後每一個組件載入動作都會產生 Log,不利於效能,建議在偵錯完畢就關閉。

延伸閱讀: Back to Basics: Using Fusion Log Viewer to Debug Obscure Loader Errors

Tips of trouble-shooting .NET bindingRedirect or version conflict problem . Including assemblyBinding appliesTo attribute, publisherPolicy apply="no" to ignore publisher policy. This article also introduce using HKLM/Software/Microsoft/Fusion EnableLog to get detail information of assembly loading exception.


Comments

Be the first to post a comment

Post a comment


74 - 10 =