RB.
RB.

Reputation: 37172

How can I dynamically load assemblies referenced by project, but not referenced in code

Consider a .NET Core application that references a NuGet package.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MyPackage" Version="1.0.0" />
  </ItemGroup>

</Project>

If my code references a type in MyPackage, then the MyPackage assembly will get loaded. If I print out all the referenced or loaded assemblies then it will appear.

static void Main(string[] args)
{
    // Because I have a reference to a type in MyPackage, the assembly 
    // is loaded and will be printed out by both foreach statements below.
    var throwaway = typeof(MyPackage.Cars);
    foreach (var an in Assembly.GetEntryAssembly().GetReferencedAssemblies())
    {
        WriteLine(an.Name);
    }

    foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
    {
        WriteLine(assembly.FullName);
    }
}

However, if I get rid of that throwaway line, then then assembly is not loaded, and so is not available by either GetReferencedAssemblies or GetAssemblies.

The .NET Framework had this same problem, and the fix was typically to read all the assemblies in the executing folder and load them manually - something like:

Directory
    .GetFiles(executingFolder, "*.dll", SearchOption.TopDirectoryOnly)
    .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath));

However, .NET Core will load assemblies from other places (such as the NuGet cache - I've not found a comprehensive description of the new binding process as of yet), so the above approach won't work.

Question

So, my question is: How can I dynamically load all DLLs that are referenced by my csproj file (as NuGet PackageReferences). My use-case is fairly esoteric so I don't think any other mechanism will do.

Background

Ok, so someones going to ask what my use-case is, so here it is.

We have a set of interfaces that define messages (IAuditEvent, IValidationEvent, that sort of thing). We also have various implementations of these interfaces for different serialization formats (Protobuf, XML, JSON, etc). Each of these is a separate NuGet package (MyMessages.Proto, MyMessages.Xml).

We have a factory that will create the appropriate implementation (factory.Create<IAuditEvent>()) but it does this using reflection - e.g. the proto factory finds a class that implements IAuditEvent but also is a Protobuf generated class. It can't work if the assembly hasn't been loaded in the first place...

Upvotes: 1

Views: 3051

Answers (2)

Kosta_Arnorsky
Kosta_Arnorsky

Reputation: 421

Assembly.GetReferencedAssemblies doesn't return your assembly because it's really not being referenced. If you take a look at the manifest of the exe file, you won't find the reference, looks like the compiler optimize them.

AppDomain.GetAssemblies returns actually loaded assemblies, consider:

static void Main(string[] args)
{
    foreach (var an in Assembly.GetEntryAssembly().GetReferencedAssemblies())
    {
        WriteLine(an.Name);
    }

    foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
    {
        WriteLine(assembly.FullName);
    }

    LoadType();
}

static void LoadType()
{
    typeof(MyPackage.Cars);
}

In this case, the result of GetReferencedAssemblies call is always the same, but GetAssemblies result depends on where you put LoadType - before or after you call GetAssemblies.

On the build server, you won't be building the solution but rather publishing it, so scanning for assemblies is only dev time problem. You can add the following in the post-build event or a dev target in the project and use one of the methods Daniel proposed:

dotnet publish "$(ProjectPath)" --no-build -o "$(TargetDir)"

It's far from ideal though, hope you will come up with something better.

Upvotes: 3

Daniel Pries
Daniel Pries

Reputation: 36

It really comes down to strategy at this point. If you find yourself needed an intermediary to reflect and provide types, its a good time to consider an IoC container.

From here you have a few options:

Upvotes: 1

Related Questions