I have a library that contains some reflection code which inspects an Asp.Net's primary assembly, any referenced assemblies and does cool stuff. I'm trying to get the same exact code to execute in a console application while still reflecting on an Asp.Net's assemblies and I'm seeing odd results. I've got everything wired up and the code executes, however the reflection code returns false when I know it should be returning true as I'm stepping through it in the debugger.. It's driving me nuts and I can't figure out why reflection is exhibiting different behavior when running from the console app.
Here's a perfect example of some reflection code that gets all of the types that are area registrations in an Asp.Net application (type.IsSubclassOf(typeof(System.Web.Mvc.AreaRegistration))
). This returns true for several types when executing in the app domain of an Asp.Net application, however it returns false for those same types when executed under the console application, but still reflecting on those same Asp.Net types.
I've also tried using the Assembly.ReflectionOnlyLoadFrom
method but even after writing all the code to manually resolve referenced assemblies the reflection code shown below returns false on types that it should be returning true for.
What can I try to make this work?
public static Assembly EntryAssembly { get; set; } // this is set during runtime if within the Asp.Net domain and set manually when called from the console application.
public CodeGenerator(string entryAssemblyPath = null)
{
if (entryAssemblyPath == null) // running under the Asp.Net domain
EntryAssembly = GetWebEntryAssembly(); // get the Asp.Net main assembly
else
{
// manually load the assembly into the domain via a file path
// e:\inetpub\wwwroot\myAspNetMVCApp\bin\myApp.dll
EntryAssembly = Assembly.LoadFrom(entryAssemblyPath);
}
var areas = GetAreaRegistrations(); // returns zero results under console app domain
... code ...
}
private static List<Type> GetAreaRegistrations()
{
return EntryAssembly.GetTypes().Where(type => type.IsSubclassOf(typeof(System.Web.Mvc.AreaRegistration)) && type.IsPublic).ToList();
}
Ok, after a lot of debugging I've got this working! It turned out that my library project was compiling against Asp.Net MVC 4.0 even though Nuget and the properties window claimed 5.1. Nuget/MS fail again. The Asp.Net MVC application that my library is reflecting on is using MVC 5.1 so when the
Assembly.LoadFrom
and theAssemblyResolve
event ran it was loading two versions ofSystem.Web.Mvc.dll
into theLoadFrom
context (4.0 & 5.1) and this caused theIsSubclassOf()
method to return false when the expected result should have been true.The very odd error I mentioned in the comments above while debugging:
The type 'System.Web.Mvc.AreaRegistration' exists in both 'System.Web.Mvc.dll' and 'System.Web.Mvc.dll'
now makes sense, but only after the fact.The way I finally tracked this down was by writing out all of the assemblies that
AssemblyResolve
was called upon to resolve and noticed thatSystem.Web.Mvc.dll
was not in the list. I fired up the Assembly Binding Log Viewer and was clearly able to see thatSystem.Web.Mvc.dll
was being loaded twice.In retrospect, one should just skip all the custom logging and just use the Assembly Binding Log Viewer to verify only one of each assembly is being loaded and that it's the correct version your expecting.
Figuring out how to use
AssemblyResolve
properly was a nightmare so here is my unfinished, but working code for posterity.This has to do with the assembly context in which
LoadFrom
loads assemblies. Dependencies loaded duringLoadFrom
will not be used when resolving "regular" assemblies in theLoad
context.The same appies the
ReflectionOnly
overloads, which load into the ReflectionOnly context.For detailed information see https://stackoverflow.com/a/2493855/292411, and Avoid Assembly.LoadFrom; instead use Assembly.Load for an issue with
LoadFrom
similar to yours.When I ran into this issue I switched to using
Load
and demanded "plugin" assemblies to be in the same path as the executable; I don't know if there are tricks to make things work if the assemblies are in different paths.