2016-11-12 161 views
3

我目前正在嘗試開發一種使用反射以編程方式在外部項目中運行測試類的方法。這是一個簡化的應該展示我的問題的代碼塊。(C#.Net Core)使用反射來從外部程序集實例化一個類

string pathToDLL = @"C:\Path\To\Test\Project\UnitTests.dll"; 
IEnumerable<Type> testClasses = assembly.GetExportedTypes(); 
Type testClass = testClasses.First(); 
object testClassInstance = assembly.CreateInstance(testClass.FullName); 

此代碼引發以下異常:

'assembly.CreateInstance(testClass.FullName)' threw an exception of type 'System.Reflection.TargetInvocationException' 
    Data: {System.Collections.ListDictionaryInternal} 
    HResult: -2146232828 
    HelpLink: null 
    InnerException: {System.IO.FileNotFoundException: Could not load file or assembly 'Project.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified. 
File name: 'Project.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' 
    at Project.UnitTests.TestClass..ctor()} 
    Message: "Exception has been thrown by the target of an invocation." 
    Source: "System.Private.CoreLib" 
    StackTrace: " at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)\r\n at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)\r\n at System.Activator.CreateInstance(Type type, Boolean nonPublic)\r\n at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)\r\n at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)\r\n at System.Reflection.Assembly.CreateInstance(String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)\r\n at System.Reflection.Assembly.CreateInstance(String typeName)" 

在堆棧跟蹤它指出它「無法加載文件或程序集Project.Core ......'」。

該項目是目標DLL直接引用的一個項目(它測試的項目)。有誰知道爲什麼這將無法自動拾取這些DLL?


我調查研究解決這個問題的辦法:

1)這可能是因爲這些DLL已編譯的方式 - 這是目前 - 這可以爲我在這個控制改變通過在解決方案級別運行dotnet build */*/project.json。這成功編譯了所有內容,並且所有相關的DLL似乎都被填充到了bin文件夾中。我還調查了是否改爲dotnet publishdotnet build */*/project.json --configuration Release,儘管這兩者似乎都沒有幫助。

2)我也研究過使用不同的編譯方法,如Activator.CreateInstance再次沒有骰子。

3)我似乎沒有看到一種方法將多個DLL加載到相同的Assembly類,以便我可以控制引用。由於AppDomain已經從.Net Core中刪除,這看起來並不可能,儘管我可能弄錯了/看錯了地方。


如果我這樣做似乎並不喜歡將有可能是什麼,沒有人知道是否可以使用不同的方法來實現這種功能?即羅斯林?

回答

3

我只是認爲我會用我設法找到的解決方案來更新這個問題,以防別人遇到同樣的問題。雖然我想感謝@EmrahSüngü指出我朝着正確的方向。

Emrah引起了我的注意,我需要導入我想加載的DLL的依賴關係,以便調用存儲在其中的類。做到這一點的一種方法是擴展你的app.config以導入這些依賴 - 但是我想在運行時執行此操作(有些項目我不知道我要在啓動程序之前運行),所以我需要尋找另一種解決方案。

如果您沒有使用。NET Core這是相對簡單的,因爲AppDomains可以用來加載所有的依賴關係並執行你的代碼。但是,由於這是removed from .NET Core我需要找到另一個兼容的解決方案。

我玩弄與運行一個單獨的進程(或Powershell的),並改變工作目錄,這樣的過程是在存儲所有它所需要的依賴關係的目錄中運行的想法。但是,我找不到這樣做的方式,使我能夠對運行方法的結果做出反應。

後來我調查了操縱AssemblyLoadContext類,但是(at the time of writing)幾乎沒有關於這個類的文檔。我確實找到了這個答案,它能夠顯着幫助... https://stackoverflow.com/a/37896162/6012159

爲了它的工作,我不得不做一個小的改變,而不是每次創建一個新的AssemblyLoader(這會導致異常被拋出當試圖調用Assembly中的方法時),我每次重新使用AssemblyLoader(它刪除了這個問題)。

public class AssemblyLoader : AssemblyLoadContext 
{ 
    private string folderPath; 

    public AssemblyLoader(string folderPath) 
    { 
     this.folderPath = folderPath; 
    } 

    protected override Assembly Load(AssemblyName assemblyName) 
    { 
     var deps = DependencyContext.Default; 
     var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList(); 
     if (res.Count > 0) 
     { 
      return Assembly.Load(new AssemblyName(res.First().Name)); 
     } 
     else 
     { 
      var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll"); 
      if (File.Exists(apiApplicationFileInfo.FullName)) 
      { 
       return this.LoadFromAssemblyPath(apiApplicationFileInfo.FullName); 
      } 
     } 
     return Assembly.Load(assemblyName); 
    } 
} 

哪些可以使用裝載程序集是這樣的:

string directory = @"C:\Path\To\Project\bin\Debug\netcoreapp1.0\publish\"; 

string pathToDLL = @"C:\Path\To\Project\bin\Debug\netcoreapp1.0\publish\project.dll"; 

AssemblyLoader al = new AssemblyLoader(directory); 

Assembly assembly = al.LoadFromAssemblyPath(pathToDLL); 
+0

絕對是一個很好的參考!我還沒有運行代碼,但它真的遞歸加載所有必需的引用? –

+0

它確實 - 只要他們是在目錄中但不知道子目錄(我還沒有面對尚) –

1

我假設「UnitTests.dll」依賴於(引用)其他DLL(S)和您的程序不知道在哪裏尋找那些引用的DLL(S)。你應該(實際上必須)告訴它在哪裏尋找這些dll(s)。默認情況下是與EXE相同的目錄。你可以使用app.config來告訴別的地方看。爲了Load()成功,依賴dll(s)必須存儲在應用程序的探測路徑中。

這就是您收到錯誤的原因。

在這裏你可以找到相關的文章。 https://msdn.microsoft.com/en-us/library/823z9h8w.aspx

+0

這使得有很大的意義 - 這解釋了爲什麼我得到一個稍微不同的錯誤,當我在一個單獨的應用程序所做的這個演示呢!你知道是否有辦法動態地做到這一點(之前.Net核心,你會通過應用程序域做到這一點)?或者你認爲這應該通過在單獨的過程中運行並編輯工作目錄來完成? –

+0

我不完全跟隨你想要達到的目標,但取決於你想要加載哪些DLL(S),其他(相關)DLL(S)已經決定所以只包括他們通過您的app.config - 探測路徑 - –

+0

,我不認爲這是可能的,因爲我的應用程序不知道哪些代碼它要通過反射加載(並且,如果真是那樣然後我就可以直接引用嗎?)。 所以我需要能夠採取其他的代碼庫,並在其中運行測試。在轉到.Net Core之前,我通過將依賴關係加載到AppDomain中實現了這一目標 - 但我沒有看到任何可以在Core中複製的明顯方式。它看起來像我需要,使其在一個進程中運行遷此功能,然後更改目錄這一過程中,但是,這並不顯得很可能 –

相關問題