.Net Core利用反射動態加載DLL類庫的方法(解決類庫不包含Nuget依賴包的問題)
在.Net Framework時代,生成類庫只需將類庫項目編譯好,然后拷貝到其他項目,即可引用或動態加載,相對來說,比較簡單。但到了.Net Core時代,動態加載第三方類庫,則稍微麻煩一些。
一、類庫發布丟失Nuget依賴包
對於大部分類庫來說,項目或多或少會引用第三方程序集,特別是Nuget程序包。通常編譯類庫項目生成的文件中,並不會包含引用的Nuget包相關類庫,而是通過*.deps.json文件來描述類庫所需的依賴。這樣造成一種問題,如果該類庫是通過動態加載的方式引用,則程序運行時,會提示“缺少某某dll”的問題。
解決上述問題,需要在該類庫項目的.csproj文件中,在<PropertyGroup></PropertyGroup>標簽中加入<EnableDynamicLoading>true</EnableDynamicLoading>標志,該屬性將告訴編譯器,該項目是動態加載的組件。相關鏈接:https://docs.microsoft.com/zh-cn/dotnet/core/project-sdk/msbuild-props#enabledynamicloading

二、類庫編譯生成多余的dll
對於類庫項目來說,通常會引用解決方案中其他通用項目,而主體程序也會引用這些通用項目,所以對於類庫來說,在編譯生成的文件中,並不需要這些文件。比如主體程序A和動態類庫項目B,共同引用通用項目C,C項目又有很多第三方類庫的引用,那么在編譯生成動態類庫B的時候,會帶上項目C及其相關依賴的文件,但實際上,對於動態類庫項目B來說,是不需要的。這種情況下,也需要修改.csproj項目文件,如下圖,生成的類庫文件將不會包含pluginBase.csproj類庫及其所有的依賴;

三、利用反射動態加載類庫
如果按照文末參考文獻中的教程,我嘗試過仍會出現找不到“某某.dll”的問題,這邊貼出我的代碼,供參考:
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Reflection;
5 using System.Runtime.Loader;
6
7 namespace LoadDLL
8 {
9 /// <summary>
10 /// 程序集加載器
11 /// </summary>
12 public class AssemblyLoader
13 {
14 private string _basePath;
15 private AssemblyLoadContext context;
16
17
18 public AssemblyLoader(string basePath)
19 {
20 _basePath = basePath;
21 }
22
23 public Type Load(string dllFileName, string typeName)
24 {
25 context = new AssemblyLoadContext(dllFileName);
26 context.Resolving += Context_Resolving;
27 //需要絕對路徑
28 string path = Path.Combine(_basePath, dllFileName);
29 if (File.Exists(path))
30 {
31 try
32 {
33 using (var stream = File.OpenRead(path))
34 {
35 Assembly assembly = context.LoadFromStream(stream);
36 Type type = assembly.GetType(typeName);
37 dicTypes.Add(typeName, type);
38 return type;
39 }
40 }
41 catch (Exception ex)
42 {
43 Console.WriteLine($"加載節點{dllFileName}-{typeName}發生異常:{ex.Message},{ex.StackTrace}");
44 }
45
46 }
47 else
48 {
49 Console.WriteLine($"節點動態庫{dllFileName}不存在:{path}");
50 }
51 return null;
52 }
53
54 /// <summary>
55 /// 加載依賴文件
56 /// </summary>
57 /// <param name="context"></param>
58 /// <param name="assemblyName"></param>
59 /// <returns></returns>
60 private Assembly Context_Resolving(AssemblyLoadContext context, AssemblyName assemblyName)
61 {
62 string expectedPath = Path.Combine(_basePath, assemblyName.Name + ".dll"); ;
63 if (File.Exists(expectedPath))
64 {
65 try
66 {
67 using (var stream = File.OpenRead(expectedPath))
68 {
69 return context.LoadFromStream(stream);
70 }
71 }
72 catch (Exception ex)
73 {
74 Console.WriteLine($"加載節點{expectedPath}發生異常:{ex.Message},{ex.StackTrace}");
75 }
76 }
77 else
78 {
79 Console.WriteLine($"依賴文件不存在:{expectedPath}");
80 }
81 return null;
82 }
83 }
84 }
其中Context_Resolving(),是用於加載類庫文件過程中,處理觸發加載相關依賴文件的事件的方法,通過上述代碼,可以保證將類庫的完整地動態加載進程序,並且不會與其他動態加載類庫項目發生程序集沖突的問題:比如A類庫和B類庫都有共同的依賴C,但兩者的引用的C版本不同,通過AssemblyLoadContext可以保證A/B類庫加載自己需要的版本。
四、調試DLL
在主體程序運行中,通常不會進入類庫代碼中的斷點,這對類庫調試帶來一定麻煩了。這時候需要對類庫的生成作進一步的設置,方法如下圖,拷貝類庫及其所有依賴文件時,注意要帶上相應的.pdb文件

五、結尾
在.Net Core動態加載類庫還是挺多坑的,以上是我踩過來,與大家分享~
參考文獻:
