Keith Barrows Keith Barrows - 2 days ago 4
C# Question

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

I have a console app that resides in C:\MyApp.

I have several libraries that are NOT referenced by the app. I use an Activator.CreateInstance() to use them. They reside in C:\MyLibrary\Job001, C:\MyLibrary\Job002, etc. Each of these libraries have multiple dependencies and can be different versions of dependencies already found in the main app.

When I try to run this I am seeing this error:

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.
This is one of the common dependencies for most jobs. I checked the directory and it does exist with the library.

How do I activate the library AND have it use the references as found in it's own directory?

I am using the following (extension) code to activate a library:

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...
}
}


I am looking at system.appdomain.assemblyresolve but am not making sense of how to use this in the library project.

Thoughts?




ADDITIONAL INFO (29 NOV 2016)

Server App References:


  • Library.Infrastructure

  • QueueApp.Core

  • Hangfire

  • OWIN



Job Library References:


  • Library.Infrastructure

  • Library.Persistence

  • Library.SQL.Database01

  • Library.SQL.Database02

  • QueueApp.Job.Core

  • EntityFramework



We have several Jobs that follow the same pattern BUT can be built with different versions of the Job Library References. This is due to a slow creep over time. If a job written last year is still working why would we take the time to open up that solution, update all the references, recompile, then spend a month going back through QA and acceptance when we can just leave it alone?

The challenge I am running into is the JOB cannot find the referenced files, expecting them to be in the Server App directory. Instead, they are in that Job's directory. Using Fuslogvw.exe just confirms that it is NOT looking in the DLL's directory but rather the hosting app's directory.

** I currently get the same behavior whether I use Assembly.LoadFrom() or Assembly.LoadFile().

FUSLOGVW log results:

*** 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.


The APP is looking for all files in:

D:\Dev\QueueApp\Source\QueueApp\bin\Debug

The JOB exists in:

D:\Dev\QueueApp\Source\Job.AgileExport\bin\Debug

Answer

This is what I came up with so far. These classes are in the main server app, not found in any of the JOBs. We have several different types of JOBs, Ad Hoc being one of the types. By placing the code in the base class, all JOB handlers now inherit it.

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;
    }
}

Seems to work well so far.

Comments