We have recently migrated our Prism application from .NET Framework 4.7 to .NET 5. We observed this issue initially with Prism 4 (which doesn't have a .NET 5 compatible target) but we also see this with Prism 8.
To simplify the situation the setup of the application is this
- ModuleA -> EventsLib
- ModuleB -> EventsLib
- Application (configures Modules A and B)
We are using an XML file to define the module assembly and type names to be loaded and pass this information packaged as a ModuleInfo instance to the module catalog. All binaries are in the same folder.
For .NET Framework 4.7 no additional code was necessary to ensure that the indirect reference (EventLib) was also loaded and types could be resolved from ModuleA and ModuleB.
For .NET 5 this doesn't work anymore: I get a ModuleTypeLoaderNotFoundException when trying to launch the application. This occurs in the Prism.Modularity.ModuleManager.GetTypeLoaderForModule method. From digging a bit into Prism sources I think this due to the fact that Type.GetType() (which is called at some point for the module types) no longer loads dependencies unless they are already listed in the deps.json of the application (which makes the modularity concept completely fall apart). This post and its comment helped me understand the differences in assembly loading for .NET Framework and .NET Core/.NET 5/6.
I can resolve this problem by adding the following for the .NET 5 target of our application:
- as part of the initialization of our custom catalog I set up a handler for assembly resolve events:
#if NET5_0_OR_GREATER
System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += OnResolving;
#endif
- in this handler I try to load the requested assembly from the local directory (with additional handling for resources)
private Assembly OnResolving(AssemblyLoadContext loadContext, AssemblyName assemblyName)
{
if (this.failedAssemblies.Contains(assemblyName.Name))
{
return null;
}
if (assemblyName.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
{
return this.LoadResourceAssembly(loadContext, assemblyName);
}
string assemblyRootPath = System.AppDomain.CurrentDomain.BaseDirectory;
var assemblyPath = System.IO.Path.Join(assemblyRootPath, assemblyName.Name) + ".dll";
var assembly = this.LoadAssemblyFromPath(loadContext, assemblyPath);
if (assembly == null)
{
this.failedAssemblies.Add(assemblyName.Name);
}
return assembly;
}
My (maybe naive) assumption was that with version 8 of Prism (which supports .NET 5 targets) this should no longer be necessary.
My question: isn't it necessary and we need to configure something to enable some built-in assembly resolution mechanism or is it now always necessary to handle assembly resolution in the custom module catalog for Prism applications that run as .NET 5 (or later) applications?