過去寫過不少 .NET 版本導向的文章,主要處理 ODP.NET 版本相容累積的經驗: (題外話:學會 Managed ODP.NET 後前途有光明一點,但許多老專案仍以 ODP.NET 為主,三不五時要處理版本茶包)

有這麼多經驗撐腰,我自以為是組件繫結導向老手,可以搞定各種狀況... 直到我膝蓋中了兩箭。

在 A 網站做了元件調整,其中需要將 ODP.NET 9207 導向 2.112.1,看了一下 web.config,有加組件繫組導向沒錯: (網站是 ASP.NET 3.5,故加了 MSAJAX System.Web.Extensions 導向內建版本的導向設定)

  <runtime>
    <assemblyBinding>
      <dependentAssembly>
        <assemblyIdentity name="Oracle.DataAccess" publicKeyToken="89B483F429C47342"/>
        <bindingRedirect oldVersion="0.0.0.0-9.9.9.9" newVersion="2.112.4.0"/>
      </dependentAssembly>
    </assemblyBinding>
    <assemblyBinding appliesTo="v2.0.50727" xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

但實際執行卻會拋出找不到 ODP.NET 9207 的錯誤訊息,同樣做法在另一台測試主機上卻完全沒問題。

比對再比對,測試再測試(我寫了一個 ChkOdpNetVer.aspx 快速檢查,附於下),發現問題出在 runtime 下有兩段 asseblyBinding,ODP.NET 導向所在的第一段少了 xmlns="urn:schemas-microsoft-com:asm.v1",補上導向才會生效。

<%@ Page Language="C#" %>

<script runat="server">
    void Page_Load(object sender, EventArgs e) 
    {
        Response.Write(
            typeof(Oracle.DataAccess.Client.OracleConnection).Assembly.CodeBase);
        Response.End();
    }
</script>

爬到官方文件 - Redirecting Assembly Versions,本草綱目有記確,assemblyBinding 的 xmlns 必須指定為 urn:schemas-microsoft-com:asm.v1。

> To bind an assembly, you must specify the string "urn:schemas-microsoft-com:asm.v1" with the xmlns attribute in the &lt;assemblyBinding&gt; tag.

文章寫好還沒發文,再接獲同事報案,某專案冒出「無法載入檔案或組件'log4net, Version=1.2.10.23127,Culture=neutral,PublicKeyToken=null'或其相依性的其中之一。找到的組件資訊清單定義與組件參考不符。(發生例外狀況於HRESULT: 0x80131040)」,原因是有元件參照了舊版 log4net,但專案由 NuGet 安裝了新版 2.0.8.0,web.config 有設 bindingRedirect,但顯然沒生效。

      <dependentAssembly>
        <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" curture="neutral"/>
        <bindingRedirect oldVersion="0.0.0.0-2.0.8.0" newVersion="2.0.8.0"/>
      </dependentAssembly>

注意到錯誤訊息 publicKeyToken="null",非強式名稱(Strong Name)組件,而試著將 assemblyIdentity 的 publicKeyToken 改為 null,也沒生效。 最後查到一篇 Stackoverflow 相關討論,提到官方文件有說:

Note You cannot redirect versions for assemblies that are not strong-named. The common language runtime ignores the version for assemblies that are not strong-named.

查詢結果,在.NET 1.1 版 還保留有此一說明,但新版文件已移除,是否某個版本起行為有改不得而知。總之,這次實測結果是我們無法將 publicKeyToken="null" 的 log4net 組件導向新版。

又學了一課,未來遇 bindingRedirect 無效時,先檢查:1. assemblyBinding 的 xmlns="urn:schemas-microsoft-com:asm.v1" 有沒有加? 2. web.config 如果有 <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">,要將 xmlns 拿掉。參考 3. 是否非強式名稱組件(publicKeyToken="null")。

Cases of failed .NET assembly binding redirection caused by missing xmlns attribute and non strong name assembly.


Comments

# by Shawn

黑大您好 我剛轉到.net core 遇到共用Library的問題 以前.net framework都可以透過此篇的方式來讓共用Library不用每個應用程式目錄底下都一份 但查了好久 .net core似乎無法用相同的方式達成 想請問您有沒有這方面的經驗能分享 謝謝

# by Jeffrey

to Shawn,這篇講的是版本重導,我不太能跟"不需每個應用程式目錄下一份"關聯在一起,需要你再補充更多描述。

# by Shawn

黑大您好 感謝您抽空回覆 不好意思 我沒表達清楚 過去寫.net framework時 遇到參考共用的Library時 (如:公司內的權限控管機制) 我會在config加上: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="AuthLibrary" culture="neutral" publicKeyToken="ddf2f95b2c78cbdb"/> <bindingRedirect oldVersion="0.0.0.0-1.0.0.0" newVersion="1.0.0.0" /> <codeBase version="1.0.0.0" href="file://D:/Library/AuthLibrary.dll"/> </dependentAssembly> </assemblyBinding> 利用codeBase的設定 來達到不需每個應用程式的目錄下都要一份AuthLibrary.dll 統一參考D:/Library/AuthLibrary.dll就好 我查了幾天 這樣的用法在.net core似乎已不支援 連結為其中一篇: https://stackoverflow.com/questions/46111749/adding-a-bindingredirect-to-a-net-standard-library/46120907#46120907 所以想請問您有沒有遇過這樣的情境? 或是在.net core有其他的做法? 謝謝

# by Jeffrey

to Shawn,.NET Core 的確沒有完全對映的做法,想知道你們用 CodeBase 限制同一個 DLL 應該不是為了省空間吧?若目的是確保版本一致,當今的主流做法會是透過 NuGet Package 管理實現,我猜可以滿足你們的需求。

# by Shawn

感謝黑大回覆 確保版本一致的同時也是為了更新方便 我再研究一下自建NuGet Server的作法 感謝分享

# by ShaoYu

可在 csproj 設定 assembly 的搜尋順序 <PropertyGroup> <AssemblySearchPaths > {CandidateAssemblyFiles}; $(ReferencePath); {HintPathFromItem}; {TargetFrameworkDirectory}; {Registry:$(FrameworkRegistryBase),$(TargetFrameworkVersion),$(AssemblyFoldersSuffix)$(AssemblyFoldersExConditions)}; {AssemblyFolders}; {GAC}; {RawFileName}; $(OutputPath); c:/pch; </AssemblySearchPaths> </PropertyGroup> <!-- The SearchPaths property is set to find assemblies in the following order: (1) Files from current project - indicated by {CandidateAssemblyFiles} (2) $(ReferencePath) - the reference path property, which comes from the .USER file. (3) The hintpath from the referenced item itself, indicated by {HintPathFromItem}. (4) The directory of MSBuild's "target" runtime from GetFrameworkPath. The "target" runtime folder is the folder of the runtime that MSBuild is a part of. (5) Registered assembly folders, indicated by {Registry:*,*,*} (6) Legacy registered assembly folders, indicated by {AssemblyFolders} (7) Look in the application's output folder (like bin\debug) (8) Resolve to the GAC. (9) Treat the reference's Include as if it were a real file name. -->

# by Jeffrey

to ShaoYu, 感謝分享,已筆記。

Post a comment