2017年10月31日 星期二

[.Net]Dll明明有在,為什麼出現找不到DLL錯誤 - 一次搞懂如何處理Dll版本問題(Dll Hell)

image
圖片來源:https://pixabay.com/en/despair-alone-being-alone-archetype-513528/

當系統越來越模組化,大量library開始透過nuget方式組成的時候,開始會遇到一個情況,那就是Dll版本問題(Dll Hell)。

如果對於Dll版本問題沒有一些了解的情況下,常常會遇到明明Dll存在bin下面,但是還是出現找不到的錯誤訊息:

image
System.IO.FileLoadException: Could not load file or assembly 'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference.

這篇將對於.Net如何處理Dll Hell的問題做一些介紹。

解決方式 - 手動設定config - TL;DR

要解決這個問題基本上是透過在config設定bindingRedirect,以上面為例子是找不到Newtonsoft.Json 6.0.0.0版本,因此設定:

打開config - 如果是web就是web.config,如果是console就是app.config
加入以下在configuration ->下runtime面:
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
 <dependentAssembly>
   <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed"/>
   <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
 </dependentAssembly>
</assemblyBinding>
每一個dll版號就加入一個dependentAssembly
  • assemblyIdentity - 用來設定那個dll要對應。其他還蠻直覺,不過publicKeyToken可以透過:
    1. Google 搜索
    2. 用powershell - 例如 ([system.reflection.assembly]::loadfile("c:\Newtonsoft.Json.dll")).FullName 會取得 Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed
    3. 使用 sn工具 - sn -T "c:\Newtonsoft.Json.dll" - 會取得 Public key token is 30ad4fe6b2a6aeed
  • bindingRedirect設定版號對應
    1. oldVersion - 那些版號要對應 - 可以用 - 來包含版號區間。
    2. newVersion - 在oldVersion符合的版號要對應到那個版號
    注意,newVersion不代表一定比oldVersion。換句話說也有可能高版本全部要用低版本的dll(不過這種情景比較少 ,因為一般都是高版本會向下兼容)
    注意版號取得的方式,用Powershell最準,如果用 檔案總管 點 右鍵 檢查的版號不一定準。

解決方式 - 自動設定 - TL;DR

Visual Studio (簡稱VS) 可以幫忙自動設定正確的binding,這邊又有分console和web差異。

Console自動設定binding

把console的csproj unload並且開啟修改畫面
image
開啟csproj修改
加入 AutoGenerateBindingRedirectsPropertyGroup

直接加入<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>

image
加完方式
重新rebuild專案
image
bin下面的app.config自動加入bindingRedirect
target 4.5.2的.net framework 之後的專案預設都有幫忙加入了,所以應該不用手動設定。

Web自動設定binding

和console不同,VS不會自動調整Web.config,但是VS會有提示。

當build完之後,檢查Warning頁簽

image
可以看到有告知版本問題

可以直接對warning點兩下,會自動問說要不要加入,如果要加入,會自動修改Web.confi。

image
是否自動加入的畫面
以上就是解決Dll版本問題的處理方式,如果對於如何重現問題有興趣可以往下看。

問題發生的原因

我們都知道同一個版本的dll同一時間只能夠有一個版本。

假設專案A使用library C的1.0版本,然後專案B使用library C的2.0版本,當build的時候,坑定只會剩下一個library C。換句話說要嘛是1.0要嘛是2.0.

那麼問題來了,假設留下來的是1.0,而專案B需要的是library 2.0版本,.Net runtime怎麼run的起來,2.0不存在啊!因為這樣所以炸掉。

因此我們需要有個東西告訴runtime,到底要用什麼版本。

dll Reference那個版本的dll其實是寫在dll裡面的,如果我們用工具像是 justDecompile 可以看到 Reference的版號資訊是寫在manifest裡面的。
image
看到library使用6.0.8的json.net

問題重現

要能夠了解問題和如何解決就要有一個能夠重現問題的專案,因此接下來要打造一個有問題的專案。會分廠兩個部分,console和web。

Console

console的部分大概是:

建立個console專案
image
建立出一個console專案
使用Newtonsoft.Json7.0版本
image
加入nuget專案
建立一個library專案
image
建立一個library專案
在library專案使用Newtonsoft.Json6.0版本 - 並且建立一個方法
image
加入json.net 6.0.8

Class1加入一個Convert的方法:

public string Convert()
{
 return JsonConvert.SerializeObject(new { From = "Lib"});
}
在console使用library並且run起來
console的main邏輯如下:
static void Main(string[] args)
{
 Console.WriteLine(JsonConvert.SerializeObject(new { From = "Console"}));
 Console.WriteLine(new Class1().Convert());
 Console.ReadLine();
}
image
執行沒有問題
拿掉AutoGenerateBindingRedirects

檢查產出的bin\DllHellProblem.exe.config發現有自動產生bindingRedirect

還記得上面自動設定提到VS專案會自動加入的部分,因此我們直接修改DllHellProblem.csproj,然後把AutoGenerateBindingRedirects拿掉。

記得先cleanrun,發現直接出錯:

image
出現找不到dll

發現bin下面的config,bindingRedirect沒有自己產生所以出錯。手動加入就對了。

console範例完成可以再githu看到:https://github.com/alantsai-samples/dotnet-dll-hell-problem/tree/sample/console

git標籤:sample/console

兩種方式取得:

    • git clone https://github.com/alantsai-samples/dotnet-dll-hell-problem.git
    • git checkout sample/console
  1. 從github release下載:下載鏈接

Web

加入一個Asp .net Mvc的專案
image
加入一個Mvc專案
HomeController使用class library並且把web.config的bindingRedirect拿掉

HomeControllerIndex加入new ClassLibrary1.Class1().Convert();

Web.config先把Newtonsoft.jsonbindingRedirect拿掉。

build專案並且檢查Error List

build專案,檢查Error List

image
點兩下自動加入bindingRedirect

console範例完成可以再githu看到:https://github.com/alantsai-samples/dotnet-dll-hell-problem/tree/sample/web

git標籤:sample/web

兩種方式取得:

    • git clone https://github.com/alantsai-samples/dotnet-dll-hell-problem.git
    • git checkout sample/web
  1. 從github release下載:下載鏈接

結語

在Console其實非常方便,因為VS會自動幫忙處理bindingRedirect,但是web不會。

不過web會在Error List裡面以warning的方式告訴,並且方便自動加入。

這個故事告訴我們,要好好看Warning資訊

希望透過這篇對於如何處理不同library使用到相同但是版號不同的套件不會出現找不到dll的錯誤。

參考資料

官方介紹
https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/redirect-assembly-versions
官方介紹自動binding Redirect
https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/how-to-enable-and-disable-automatic-binding-redirection
黑大介紹用檔案總管檢查版號的小陷阱 - 建議還是用Powershell比較不會有問題
http://blog.darkthread.net/post-2015-11-25-assemblyinformationversion.aspx
標籤: ,

沒有留言 :

張貼留言