I've published an ASP.NET Core project to a folder and I'm trying to dynamically load the assembly containing the ASP.NET Core Startup
class. I then want to instantiate the Startup
class and hand it over to the TestHost
API, so I can start the site in memory. I've written the following code:
```c#
var directoryPath = @"C:\Dlls";
var assemblyFilePath = Path.Combine(directoryPath, "Foo.dll");
var assemblyName = AssemblyLoadContext.GetAssemblyName(assemblyFilePath);
var assembly = new AssemblyLoader(directoryPath).LoadFromAssemblyName(assemblyName);
var startupType = assembly.ExportedTypes
.FirstOrDefault(x => string.Equals(x.Name, "Startup"));
var webHostBuilder = new WebHostBuilder()
.UseStartup(startupType)
.UseUrls(new string[] { "http://localhost" });
using (var testServer = new TestServer(webHostBuilder))
{
var response = testServer.CreateClient().GetAsync("/");
}
public class AssemblyLoader : AssemblyLoadContext
{
private readonly string directoryPath;
public AssemblyLoader(string directoryPath) =>
this.directoryPath = directoryPath;
protected override Assembly Load(AssemblyName assemblyName)
{
var dependencyContext = DependencyContext.Default;
var compilationLibraries = dependencyContext
.CompileLibraries
.Where(x => x.Name.Contains(assemblyName.Name))
.ToList();
if (compilationLibraries.Count > 0)
{
return Assembly.Load(new AssemblyName(compilationLibraries.First().Name));
}
else
{
var file = new FileInfo($"{this.directoryPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
if (File.Exists(file.FullName))
{
var asemblyLoader = new AssemblyLoader(file.DirectoryName);
return asemblyLoader.LoadFromAssemblyPath(file.FullName);
}
}
return Assembly.Load(assemblyName);
}
}
This works when I publish the app as a self contained application, however, I'd like to avoid dealing with runtime identifiers (RID's) if possible. When I publish normally the code throws a `TypeLoadException` when `assembly.ExportedTypes` is called. The full stack trace:
System.TypeLoadException occurred
HResult=0x80131522
Message=Method 'ConfigureServices' in type 'Foo.Startup' from assembly 'Foo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
Source=
StackTrace: at
System.Reflection.RuntimeAssembly.GetExportedTypes(RuntimeAssembly assembly, ObjectHandleOnStack retTypes) at
System.Reflection.RuntimeAssembly.GetExportedTypes()
```
How can I dynamically load the .NET Core runtime in the AssemblyLoader
class? Related StackOverflow question.
[EDIT] @karelz changes: formatting code with c# syntax (```c#) and stack trace in ``` block.
@Petermarcu who is working in CoreCLR loader these days?
@kouvel can probably take a look soon.
@RehanSaeed did you ever figure this out? @kouvel in case he didn't can you please help?
No, I planned to upgrade to .NET Core 2.0 and see if that helps in any way. I should be able to try with that in the next few days.
that would be great. Please do let us know what you find.
@RehanSaeed Do you have any update on this?
I am facing the same kind of "issue"
https://github.com/softeering/NETStandardSQLClientRepro
Thanks
Not yet, maybe this weekend.
At .Net core 2.0 I have dynamically loaded a .net core dll ( which is not directly referenced into the project, loaded from a folder). from all types of that loaded dll assembly, tried to select a type which has implemented a specific interface, could not find that type. But that type contains in the types list. Also if I find by name and try to create instance using Activator then create instance but cast using the the implemented interface that throws invalid cast exception. It does not happen when i reference the dll directly into the project. But throws exception when it is loaded from a seperate dll file using Assembly Loader. Problem conclustion is - When I directly referance a dll into .net core project then can find types by the implemented interface and instance creation and cast by implemented interface works fine. But if I load that dll from a folder using assembly loader then it cannot find the type using the implemented interface. Also if i select the type by searching type name and create instance then also that instance cannot cast by his implemented interface.
Tried my code with .NET Core 2.0.5 and still unable to dynamically load external assemblies that are not referenced. The problem is that System.*
and Microsoft.*
assemblies are scattered in the store
or sdk
folders under the dotnet
installation directory. I tried writing this very hacky code to load the right assemblies at runtime but it still doesn't work:
namespace Boilerplate.Templates.Test
{
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
public class AssemblyLoader : AssemblyLoadContext
{
private readonly string directoryPath;
public AssemblyLoader(string directoryPath) =>
this.directoryPath = directoryPath;
protected override Assembly Load(AssemblyName assemblyName)
{
var dependencyContext = DependencyContext.Default;
var compilationLibraries = dependencyContext
.CompileLibraries
.Where(x => x.Name.Contains(assemblyName.Name))
.ToList();
if (compilationLibraries.Count > 0)
{
return Assembly.Load(new AssemblyName(compilationLibraries.First().Name));
}
else
{
var assemblyFilePath = Path.Combine(this.directoryPath, $"{assemblyName.Name}.dll");
if (File.Exists(assemblyFilePath))
{
return this.LoadFromAssemblyPath(assemblyFilePath);
}
else
{
var dotnetSdkDirectoryPath = @"C:\Program Files\dotnet\store\x64\netcoreapp2.0";
var runtimeStoreDirectoryPath = @"C:\Program Files\dotnet\store\x64\netcoreapp2.0";
var assemblyFileName = $"{assemblyName.Name}.dll";
var dotnetFiles = Directory.GetFiles(dotnetSdkDirectoryPath, assemblyFileName, SearchOption.AllDirectories);
var sdkFiles = Directory.GetFiles(runtimeStoreDirectoryPath, assemblyFileName, SearchOption.AllDirectories);
var dotnetAssemblyFilePath = dotnetFiles.Concat(sdkFiles).FirstOrDefault();
if (dotnetAssemblyFilePath != null)
{
return this.LoadFromAssemblyPath(dotnetAssemblyFilePath);
}
}
}
return Assembly.Load(assemblyName);
}
}
}
I believe this will be fixed as a result of https://github.com/dotnet/core-setup/issues/3634 only in net core 2.1 though :(. You would use the --additional-deps flag on startup.
Still .Net core 2.1 does not support dynamic assembly loading with nuget package dependency and unloading. Even does not support creating a custom app domain. Without dynamic assembly loading and unloading it is not a pure modular architecture. Because for module update full website has to restart which is really tough after going into production. Also after loading a dll into context, it does not allow to delete and create a new version of that dll at same location. So definitely require dynamic dll load and unload feature at .Net core.
@masums you can work around the problem you described on Feb 3 above, see my link. The only problem that still exists in net core 2.0 is if you are trying to dynamically load a netcoreapp assembly that brings in other or different runtime dependencies.
Loading a different version of a dll into an existing running application is a whole different story though, that has never existed for full framework and will probably never exist based on all the complexities that would entail with existing application state etc.
@hvanbakel Thank you for your reply. I have managed dependent nuget packages assembly loading at dynamically assembly loading time using another way. That is working. But if we cannot unload the assembly then we will not be able to replace and reload updated assembly/modules/features of the website, so have to stop the application pool and site then replace the assembly manually and start the application again. Because asp.net application initilize once per app run. If we cannot update modules silently then that is actually another limitation. Will interrupt live service on every module update. No doubt, ASP.Net Core is really a fantastic web application framework by design and performance, so expectations are also higher. :)
Loading a different version of a dll into an existing running application is a whole different story though, that has never existed for full framework
.NET Framework lets you do this. That's one of the reasons you might need a strong name.
@jnm2 do you have a link for that? I would be very interested to know how that works with multiple assemblies having the exact same type and existing application state etc. But that's unrelated to this issue.
@hvanbakel https://docs.microsoft.com/en-us/dotnet/framework/app-domains/strong-named-assemblies#why-strong-name-your-assemblies
- An app needs access to different versions of the same assembly. This means you need different versions of an assembly to load side by side in the same app domain without conflict. For example, if different extensions of an API exist in assemblies that have the same simple name, strong-naming provides a unique identity for each version of the assembly.
That's also why -reference:alias=
exists. For some people, global::
is not the only useful alias.
https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/extern-alias
Interesting, thanks!
On Thu, Apr 19, 2018, 18:32 Joseph Musser notifications@github.com wrote:
@hvanbakel https://github.com/hvanbakel
https://docs.microsoft.com/en-us/dotnet/framework/app-domains/strong-named-assemblies#why-strong-name-your-assembliesAn app needs access to different versions of the same assembly. This means
you need different versions of an assembly to load side by side in the same
app domain without conflict. For example, if different extensions of an API
exist in assemblies that have the same simple name, strong-naming provides
a unique identity for each version of the assembly.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dotnet/corefx/issues/21982#issuecomment-382799509,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AD8FPGu5PngWjsE-_DkdRWMOk8AFddXZks5tqLwHgaJpZM4ORGtc
.
@masums Do you have an example of how you solved your dynamic loading of the assemblies?
We are trying to do similar thing where we want to have a thin startup application which checks for components, packed as nuget, and dynamically loads them. So any examples or ideas would be great.
Thanks.
https://www.codeproject.com/Articles/1194332/Resolving-Assemblies-in-NET-Core
you can follow this example. You have to modify one thing. At loading time of an assembly .net try to load it's referenced assemblies also. And fire an event. At assembly resolve event you have to search the assembly at nuget package global and local cache folder for loading all dependencies. This technique works for us.
@masums That code manages to load the assembly but throws FileNotFoundException
when trying to access Assembly.ExportedTypes
or Assembly.DefinedTypes
.
@masums could you share the code for how you changed the CodeProject example to include search on assemblies at nuget package global and local cache level?
@masums Have you found a solution you commented on 3rd Feb? I experienced the same issue. When referencing to the dll directly, the program can access/casting all types. But when load the dll dynamically, it gives exception saying unable to cast or unable to find XXX method. using net core 2.0
I have also issue related to dynamic assembly loading.
We have a small app which loads applications/assemblies from some folders. We use app contexts to segregate applications. The issue I've got happens if application assembly depends on System.Configuration.ConfigurationManager. Despite the fact that this assembly is loaded to the application's context ( I see this in log) at some point process fails with exception:
System.TypeInitializationException
HResult=0x80131534
Message=The type initializer for 'Common.CommonSettings' threw an exception.
Inner Exception 1:
ConfigurationErrorsException: An error occurred creating the configuration section handler for applicationSettings/Photon.LoadBalancing.Common.CommonSettings: Could not load file or assembly 'System.Configuration.ConfigurationManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified. (...\application.dll.config line 6)
Inner Exception 2:
FileNotFoundException: Could not load file or assembly 'System.Configuration.ConfigurationManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
last message I see from CurrentDomain.OnResolving event:
Resolving of assembly 'System.Configuration.ConfigurationManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51', asking assembly: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.9\System.dll
How does it happen that System.dll tries to load this library?
if I add System.Configuration.ConfigurationManager as a dependency to my application loader project, then I get an exception that class ClientConfigurationSection is not derived from 'IConfigurationHandlerInterface'. What actually means that we have two loaded versions of System.Configuration.ConfigurationManager assembly and there is a conflict of types between them.
How can I get rid of the injecting of System.Configuration.ConfigurationManager (and others) to global/default context ?
There are a set of known issues for .NET Core 2.x
AssemblyLoadContext
documentationAssemblyLoadContext
AssemblyLoadContext
Assembly cache partially brokenThere is a lot of work going into assembly loading and isolation toward the .NET Core 3.0 release. It has resulted in fixes for most of these issues.
See
The work is implemented in the daily .NET Core builds. Some is present in preview4
, the rest should be present in the preview5
release.
@shvez @RehanSaeed @JianpingLiu0225 @masums @1iveowl @simbi
Please take a look at the AssemblyDependencyReolver sample code mentioned above.
I am closing this conglomeration of related issues. It has too many related issues to fully resolve anyone's particular issue. Please feel free to open new issues if they still need to be discussed.
@sdmaclea Thanks a lot for the detailed answer. I very appreciate your help. Will check new builds
Most helpful comment
At .Net core 2.0 I have dynamically loaded a .net core dll ( which is not directly referenced into the project, loaded from a folder). from all types of that loaded dll assembly, tried to select a type which has implemented a specific interface, could not find that type. But that type contains in the types list. Also if I find by name and try to create instance using Activator then create instance but cast using the the implemented interface that throws invalid cast exception. It does not happen when i reference the dll directly into the project. But throws exception when it is loaded from a seperate dll file using Assembly Loader. Problem conclustion is - When I directly referance a dll into .net core project then can find types by the implemented interface and instance creation and cast by implemented interface works fine. But if I load that dll from a folder using assembly loader then it cannot find the type using the implemented interface. Also if i select the type by searching type name and create instance then also that instance cannot cast by his implemented interface.