在 .Net Core 中仅加载反射程序集

5
我有一个 .Net Framework WPF 应用程序,目前正在迁移到 .Net6。在启动时,它会检查可执行文件夹中的某些程序集,查找是否有自定义程序集属性。具有此属性的程序集将被加载到当前应用程序域中。(请注意,其中一些程序集可能已经在应用程序域中,因为它们是运行应用程序解决方案中的项目)。
这是 4.x 代码:
private void LoadAssemblies(string folder)
{
    AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
        (s, e) => Assembly.ReflectionOnlyLoad(e.Name);

    var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
    foreach (var assemblyFile in assemblyFiles)
    {
        var reflectionOnlyAssembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile);
        if (ContainsCustomAttr(reflectionOnlyAssembly))
        {
            var assembly = Assembly.LoadFrom(assemblyFile);
            ProcessAssembly(assembly);
        }
    }
}
  

这段代码查找的自定义程序集特性中包含一个字符串属性,其中包含该程序集中XAML资源文件的路径。ProcessAssembly() 方法会将该资源文件添加到应用程序的合并字典中,类似于以下方式:

var resourceUri = string.Format(
    "pack://application:,,,/{0};component/{1}",
    assembly.GetName().Name,
    mimicAssemblyAttribute.DataTemplatePath);

var uri = new Uri(resourceUri, UriKind.RelativeOrAbsolute);
application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = uri });

再次强调,所有这些在.Net 4.x应用程序中都可以正常工作。

.Net6则不支持纯反射加载,也不能创建第二个应用程序域以加载程序集。我通过将要检查的程序集加载到我认为是一个临时且不可卸载的上下文中,并改写了上述代码:

    private void LoadAssemblies(string folder)
    {
        var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
        using (var ctx = new TempAssemblyLoadContext(AppDomain.CurrentDomain.BaseDirectory))
        {
            foreach (var assemblyFile in assemblyFiles)
            {
                var assm = ctx.LoadFromAssemblyPath(assemblyFile);
                if (ContainsCustomAttr(assm))
                {
                    var assm2 = Assembly.LoadFrom(assemblyFile);
                    ProcessAssembly(assm2);
                }
            }
        }
    }

    private class TempAssemblyLoadContext : AssemblyLoadContext, IDisposable
    {
        private AssemblyDependencyResolver _resolver;

        public TempAssemblyLoadContext(string readerLocation)
            : base(isCollectible: true)
        {
            _resolver = new AssemblyDependencyResolver(readerLocation);
        }

        public void Dispose()
        {
            Unload();
        }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            var path = _resolver.ResolveAssemblyToPath(assemblyName);
            if (path != null)
            {
                return LoadFromAssemblyPath(path);
            }

            return null;
        }

        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
            if (path != null)
            {
                return LoadUnmanagedDllFromPath(path);
            }

            return IntPtr.Zero;
        }
    }

(请注意,ProcessAssembly()方法未更改)。

这段代码“工作”,因为它可以顺利运行而不崩溃。然而,在应用程序开始创建视图的后期阶段,我会遇到以下异常:

组件'。ModeSelectorView'没有由URI'/ ;component/views/modeselector/modeselectorview.xaml'标识的资源。

这个特定的视图位于该应用程序解决方案的一个项目中,因此该程序集已经在应用程序域中。该程序集还包含自定义属性,因此上述代码将尝试加载它,尽管我认为Assembly.LoadFrom()不应再次加载相同的程序集?

以防万一,我修改了LoadAssemblies()方法中的“if”块,以忽略已经存在于应用程序域中的程序集:

if (ContainsCustomAttr(assm) && !AppDomain.CurrentDomain.GetAssemblies().Contains(assm))

确实,断点显示相关的汇编(包含该视图)被忽略且未加载到应用程序域中。但是在代码后面我仍然收到相同的异常。 事实上,我可以注释掉整个“if”块,因此没有任何程序集被加载到应用程序域中,但我仍然会收到异常,这表明它是由将程序集加载到AssemblyLoadContext中引起的。 此外,断点显示,在LoadAssemblies()方法的“using”块退出时通过其Dispose()方法卸载了该上下文。
编辑:即使注释掉“if”块,方法末尾处断点显示ctx.LoadFromAssemblyPath()加载的所有程序集最终都会出现在AppDomain.Current中。我不理解什么。上下文是否是应用程序域的一部分,而不是单独的“区域”?我如何以类似于我在 .Net 4.x 中使用的“仅反射”方法的方式实现这种“隔离”的程序集加载方式?
1个回答

6

好的,我找到了答案,就是使用MetadataLoadContext。这本质上是反射只加载的 .Net Core 替代品:

private void LoadAssemblies(string folder)
{
    // The load context needs access to the .Net "core" assemblies...
    var allAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.Client.dll").ToList();
    // .. and the assemblies that I need to examine.
    var assembliesToExamine = Directory.GetFiles(folder, "NuIns.CoDaq.*.Client.dll");
    allAssemblies.AddRange(assembliesToExamine);

    var resolver = new PathAssemblyResolver(allAssemblies);
    using (var mlc = new MetadataLoadContext(resolver))
    {
        foreach (var assemblyFile in assembliesToExamine)
        {
            var assm = mlc.LoadFromAssemblyPath(assemblyFile);
            if (ContainsCustomAttr(assm))
            {
                var assm2 = Assembly.LoadFrom(assemblyFile);
                AddMimicAssemblyInfo(assm2);
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接