在dotnet core中如何动态加载程序集

13
我正在构建一个 Web 应用程序,在这里我希望分离关注点,即在不同的项目中具有抽象和实现。
为了实现这一目标,我尝试实现组合根概念,其中所有实现都必须具有 ICompositionRootComposer 的实例来注册服务、类型等。
public interface ICompositionRootComposer
{
    void Compose(ICompositionConfigurator configurator);
}
在直接在构建层次结构中引用的项目中,将调用ICompositionRootComposer的实现,并且服务将正确地注册在底层IoC容器中。
当我尝试在一个项目中注册服务时,出现问题,其中我设置了一个后期构建任务,将已构建的dll复制到Web项目的debug文件夹中:
cp -R $(TargetDir)"assembly and symbol name"* $(SolutionDir)src/"webproject path"/bin/Debug/netcoreapp1.1

我使用以下代码加载程序集:(灵感来自:.net core控制台应用程序中如何加载位于文件夹中的程序集

internal class AssemblyLoader : AssemblyLoadContext
{
    private string folderPath;

    internal AssemblyLoader(string folderPath)
    {
        this.folderPath = Path.GetDirectoryName(folderPath);
    }

    internal Assembly Load(string filePath)
    {
        FileInfo fileInfo = new FileInfo(filePath);
        AssemblyName assemblyName = new AssemblyName(fileInfo.Name.Replace(fileInfo.Extension, string.Empty));

        return this.Load(assemblyName);
    }

    protected override Assembly Load(AssemblyName assemblyName)
    {
        var dependencyContext = DependencyContext.Default;
        var ressource = dependencyContext.CompileLibraries.FirstOrDefault(r => r.Name.Contains(assemblyName.Name));

        if(ressource != null)
        {
            return Assembly.Load(new AssemblyName(ressource.Name));
        }

        var fileInfo = this.LoadFileInfo(assemblyName.Name);
        if(File.Exists(fileInfo.FullName))
        {
            Assembly assembly = null;
            if(this.TryGetAssemblyFromAssemblyName(assemblyName, out assembly))
            {
                return assembly;
            }
            return this.LoadFromAssemblyPath(fileInfo.FullName);
        }

        return Assembly.Load(assemblyName);
    }

    private FileInfo LoadFileInfo(string assemblyName)
    {
        string fullPath = Path.Combine(this.folderPath, $"{assemblyName}.dll");

        return new FileInfo(fullPath);
    }

    private bool TryGetAssemblyFromAssemblyName(AssemblyName assemblyName, out Assembly assembly)
    {
        try
        {
            assembly = Default.LoadFromAssemblyName(assemblyName);
            return true;
        }
        catch
        {
            assembly = null;
            return false;
        }
    }
}

通过这种方式,我能够加载程序集并调用项目的ICompositionRootComposer实现。

但问题在于它似乎无法识别我的任何类型。

当使用以下方法调用我的配置器时:

configurator.RegisterTransiantService<IFoo, Foo>();

应该在IoC中注册IFoo和Foo。
但是在调试时,我无法通过在Visual Studio Code的调试控制台中使用typeof(Foo)来获取类型信息。

3个回答

14

死灵术。


您可以创建一个包装类来使用旧的Assembly.LoadFile方法。

这样做的附加好处是,您可以在旧代码库中应用搜索和替换更改,从而实现向后兼容dotnet-none-core

namespace System.Reflection
{
    public class Assembly2
    {
        public static System.Reflection.Assembly LoadFile(string path)
        {
            System.Reflection.Assembly assembly = null;
            
#if NET_CORE
            // Requires nuget - System.Runtime.Loader
            assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
#else
            assembly = System.Reflection. Assembly.LoadFile(path);
#endif 
            return assembly;
        }
    }
}

您需要通过NuGet添加System.Runtime.Loader


3
我最终连接到 AssemblyLoadContext.Resolving 事件,以帮助加载上下文加载它自己无法加载的程序集。 - ComkeenDk

5
我找到了解决问题的方法。原来,我将属性PreserveCompilationContext设置为true,这就是为什么调试器无法注册我手动复制的程序集的原因。 当我从web项目csproj文件中删除该属性时,一切都正常工作了。

2
您知道 ASP.NET Core 有自己内置的依赖注入机制吗?它可以很容易地切换到其他 IoC 容器以满足您的需求,我认为您不需要重新发明它。
在这里,您需要使用反射来创建一个通用方法,并在调用之后进行一些操作,类似于以下内容:
public void ConfigureServices(IServiceCollection services)
{
    var myAssembly = LoadAssembly();
    // find first interface
    var firstInterfaceType = myAssembly.DefinedTypes.FirstOrDefault(t => t.IsInterface).GetType();
    // find it's implementation
    var firstInterfaceImplementationType = myAssembly.DefinedTypes.Where(t => t.ImplementedInterfaces.Contains(firstInterfaceType)).GetType();

    // get general method info
    MethodInfo method = typeof(IServiceCollection).GetMethod("AddTransient");
    // provide types to generic method
    MethodInfo generic = method.MakeGenericMethod(firstInterfaceType, firstInterfaceImplementationType);
    // register your types
    generic.Invoke(services, null);
}

我了解.NET Core的依赖注入机制,但是我希望在项目中保留实现的注册,即代码所在的地方。你的方法似乎将Web项目与外部项目紧密耦合。 - ComkeenDk
ConfigureService 是 ASP.NET 中依赖注入的组合根。您可以在自己实现的根中使用相同的方法。 - VMAtm
我还是不太相信 - 而且无论如何,我觉得我们有点离题了。我问为什么在我的复制项目中无法加载类型,尽管我可以在同一个项目中调试到我的composer。 - ComkeenDk
正如我所说,你可以在自己的代码中使用相同的方法。你试过了吗? - VMAtm

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