C#,获取 Activator.CreateInstance(assemblyType) 对象引用的 DLL

C#, Get DLLs referenced by Activator.CreateInstance(assemblyType) object

我有一个控制台应用程序位于 C:\MyApp.

我有几个应用程序未引用的库。我使用 Activator.CreateInstance() 来使用它们。它们位于 C:\MyLibrary\Job001、C:\MyLibrary\Job002 等 。这些库中的每一个都有多个依赖项,并且可以是已在主应用程序中找到的不同版本的依赖项。

当我尝试 运行 时,我看到了这个错误:Could not load file or assembly 'Persistence.Database, Version=1.7.2.67, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. 这是大多数作业的常见依赖项之一。我检查了目录,它确实存在于库中。

如何激活库 AND 让它使用在它自己的目录中找到的引用?

我正在使用以下(扩展)代码激活库:

public static IJob ConcreteJob(this JobInfoPayload src)
{
    if (src.AssemblyFile.IsNullOrEmpty())
        throw new Exception("AssemblyFile cannot be empty or null!");
    if (src.AssemblyName.IsNullOrEmpty())
        throw new Exception("AssemblyName cannot be empty or null!");

    try
    {
        var assembly = Assembly.LoadFile(src.AssemblyFile);
        var assemblyType = assembly.GetType(src.AssemblyName);
        var job = Activator.CreateInstance(assemblyType) as IJob;
        return job;
    }
    catch (Exception ex)
    {
        Serilog.Log.Logger.Fatal(ex, "JOB was not able to be created!!");
        throw; // bubble this up to the top...
    }
}

我正在查看 system.appdomain.assemblyresolve,但不明白 如何 在图书馆项目中使用它。

想法?


附加信息(2016 年 11 月 29 日)

服务器应用参考:

职位库参考资料:

我们有几个遵循相同模式的作业但是可以使用不同版本的作业库构建参考文献。这是由于随着时间的推移缓慢蠕变。如果去年编写的作业仍然有效,为什么我们要花时间打开该解决方案,更新所有参考资料,重新编译,然后花一个月的时间重新进行质量检查和验收,而我们可以不理会它?

我面临的挑战 运行 是 JOB 找不到引用的文件,希望它们位于 Server App 目录中。相反,它们位于该作业的目录中。使用 Fuslogvw.exe 只是确认它不是在 DLL 的目录中查找,而是在托管应用程序的目录中查找。

** 无论我使用 Assembly.LoadFrom() 还是 Assembly.LoadFile(),我目前都会得到相同的行为。

FUSLOGVW 日志结果:

*** Assembly Binder Log Entry  (11/29/2016 @ 10:20:21 AM) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Running under executable  D:\Dev\QueueApp\Source\QueueApp\bin\Debug\QueueApp.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = Job.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (Fully-specified)
LOG: Appbase = file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = QueueApp.exe
Calling assembly : Job.AgileExport, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: D:\Dev\QueueApp\Source\QueueApp\bin\Debug\QueueApp.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/Job.Core.DLL.
LOG: Attempting download of new URL file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/Job.Core/Job.Core.DLL.
LOG: Attempting download of new URL file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/Job.Core.EXE.
LOG: Attempting download of new URL file:///D:/Dev/QueueApp/Source/QueueApp/bin/Debug/Job.Core/Job.Core.EXE.
LOG: All probing URLs attempted and failed.

APP 正在查找以下所有文件:
D:\Dev\QueueApp\Source\QueueApp\bin\Debug
该工作存在于:
D:\Dev\QueueApp\Source\Job.AgileExport\bin\Debug

我认为有两种解决方案。

一个解决方案是创建一个新的 AppDomain 来托管动态加载的程序集。当您创建一个新的 AppDomain 时,您可以选择为 AppDomain 提供一个设置对象,并且在该对象中您可以提供 AppDomain 将用来解析程序集的路径。您不能更改 existing AppDomain 中的路径,因为它已经存在。

另一种解决方案是处理您当前的 AppDomain 的 AssemblyResolve 事件,该事件将在正常程序集解析失败的情况下引发。然后您可以采取自定义步骤来帮助解决程序集问题。

.NET 中有一个 feature/bug,当 .NET 托管在各种容器(如 IE、COM+ 等)中并且 BinaryFormatter 用于反序列化 应该可用,但实际上找不到。

我在这里有一个挂钩和解析 AssemblyResolve 事件的示例: https://github.com/MarimerLLC/csla/blob/V1-5-x/cslacs10/NetRun/Launcher.cs

对于您的情况,您可能可以更改我的 ResolveEventHandler 方法以在您最初加载动态程序集的文件夹中查找 "missing" 程序集。

使用 Assembly.LoadFrom,无法在同一 AppDomain 中加载 同一 程序集的多个版本。

因此,如果 Job001 需要 LibraryA, 1.0.0.0(并且不能在运行时使用较新的版本)并且 Job002 需要 LibraryA, 2.0.0.0,您将必须加载 Job001Job002 每个都在自己的 AppDomain 中。

请注意,动态加载程序集的顺序非常重要:

  • 当你载入Job001时,它会自动载入LibraryA, 1.0.0.0,如果你载入Job002之后,它就无法载入加载 LibraryA, 2.0.0.0LibraryA, 1.0.0.0 将保留在域中。

  • 同样,当你载入Job002时,如果找到它会自动载入LibraryA, 2.0.0.0,如果你载入Job001,它不会能够加载 LibraryA, 1.0.0.0 并且 LibraryA, 2.0.0.0 将保留在域中。

你最好的选择是使用 Assembly.LoadFile + AppDomain.AssemblyResolve 自己加载依赖项(然后你可以在同一个 AppDomain 中拥有同一个程序集的多个版本),或者你创建一个为每个 JobXXX 程序集单独的 AppDomain,并让依赖项自动加载。

这是我到目前为止想出的。这些 classes 在主服务器应用程序中,在任何作业中都找不到。我们有几种不同类型的 JOB,Ad Hoc 是其中一种。通过将代码放在基 class 中,所有 JOB 处理程序现在都继承它。

public class JobAdHocHandler : BaseHandler, IJobHandler
{
    public MinimumResultModel Handle(MinimumCommandModel message)
    {
        var result = new MinimumResultModel {Id = "-1", PayloadAsString = message.FullPayloadString};

        try
        {
            var info = message.MinimumPayload.JobInfo;

            SetupInstance(info); // <<-- SOLUTION (in BaseHandler)
            var job = JobHandler.GetJob(info); // <<-- SOLUTION (in BaseHandler)

            result.Id = BackgroundJob.Enqueue(() => job.Execute(null, message.FullPayloadString, JobCancellationToken.Null));
        }
        catch (Exception ex)
        {
            Log.Logger.Fatal(ex, ex.Message);
            result.Exception = ex;
        }

        AppDomain.Unload(JobAppDomain);
        return result;
    }
    public bool AppliesTo(JobType jobType) => jobType == JobType.AdHoc;
}

public class BaseHandler : MarshalByRefObject
{
    protected internal AppDomain JobAppDomain;
    protected internal BaseHandler JobHandler;

    protected internal void SetupInstance(JobInfoPayload info)
    {
        var ads = new AppDomainSetup
        {
            ApplicationBase = new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName,
            DisallowBindingRedirects = false,
            DisallowCodeDownload = true,
            PrivateBinPath = info.JobClassName,
            ApplicationName = info.JobName,
        };
        JobAppDomain = AppDomain.CreateDomain(info.JobName, null, ads);
        JobHandler = (BaseHandler)JobAppDomain.CreateInstanceAndUnwrap(typeof(BaseHandler).Assembly.FullName, typeof(BaseHandler).FullName);
    }

    protected internal IJob GetJob(JobInfoPayload info)
    {
        var assembly = Assembly.LoadFrom(info.JobClassName + @"\" + info.JobClassName + ".dll");
        var assemblyType = assembly.GetType(info.AssemblyName);
        var job = Activator.CreateInstance(assemblyType) as IJob;
        if (job == null)
            throw new Exception("Unable to create job: " + info.JobClassName);
        return job;
    }
}

到目前为止似乎运行良好。