Home
Options

Reflection works only in LINQPad

edited June 2020

I have a complicated application, that has kernel and adapters, and depends on third-party libs.
I want to make utility, that will inspect it with reflection and find WCF services: classes, that implements interfaces, that are marked with [System.ServiceModel.ServiceContractAttribute] attribute.
For that purpose I load .dll files with Assembly.LoadFile(...) method and inspect classes with .GetTypes() method of Assembly instance.

But here is the porblem: the sample, that I wrote in LINQPad works fine, but when I run the same code in console application, .GetTypes() fails with ReflectionTypeLoadException. This can be handled with

                catch (ReflectionTypeLoadException e)
                {
                    types.AddRange(e.Types);
                }

but not all types are loaded after all.
Some kernel libraries depends on other kernel libraries, and if in console application they are loaded in specific order, than less exceptions are thrown, but they still are, and still not all types are loaded.
At the same time, in LINQPad order doesn't matter and I don't even have to load thirdparty libs.
I guess, in LINQPad it works because it starts AppDomain with specific configuration.
Can anybody give me any hint, how can I create AppDomain in my application, similar to LINQPad, so reflection would work?
I don't need to create instances of reflected types or run code, just view service names and contracts.

code:

void Main()
{
    AppDomain.CurrentDomain.Dump(0);

    var libs = new List<System.Reflection.Assembly>();
    foreach (var lib in Directory.GetFiles(@"<kernel binaries directory>", "*MyCompany*.dll"))
        libs.Add(System.Reflection.Assembly.LoadFile(lib));

    foreach (var lib in Directory.GetFiles(@"<adapter binaries directory>", "*MyCompany*.dll"))
    {
        libs.Add(System.Reflection.Assembly.LoadFile(lib));
    }
    libs
        .Count
        .Dump("assemblies loaded");

    var types = new List<Type>();
    foreach (var lib in libs)
    {
        try
        {
            types.AddRange(lib.GetTypes());
            $"Succesfully loaded types from {lib.FullName}".Dump();
        }
        catch (ReflectionTypeLoadException e)
        {
            $"Catch on {lib.FullName}".Dump();
            types.AddRange(e.Types);
        }
    }

    var services = types
        .Where(t => t != null && t.SelfAndInterfaces().Any(ti => ti.GetCustomAttributes(true).Any(a => a.GetType() == typeof(System.ServiceModel.ServiceContractAttribute))))
        .Where(t => !t.IsInterface)
        .ToList()
        .Dump("Services", 0);


    Helper.SelfAndInterfaces(services[5])
        .Where(t => t.GetCustomAttributes<System.ServiceModel.ServiceContractAttribute>().Any())
        .Dump("Contracts");

    services
        .Single(t => t.FullName == "Sphaera.Android.MosOblGaz.Adapter")
        .Dump("adapter")
        .GetInterfaces()
        .Where(x => x.GetCustomAttributes<System.ServiceModel.ServiceContractAttribute>().Any())
        .Dump("adapter contracts");


}

static class Helper
{
    public static IEnumerable<Type> SelfAndInterfaces(this Type t)
    {
        if (t == null)
            yield break;
        yield return t;
        foreach (var i in ((t as TypeInfo)?.ImplementedInterfaces) ?? Enumerable.Empty<Type>())
            foreach (var ii in i.SelfAndInterfaces())
                if (ii != null) yield return ii;
    }
}

Comments

  • Options
    edited June 2020

    I take it you are using LINQPad 5 (and therefore .NET Framework)?

    It sounds like transitive dependencies are failing to load.

    Are the assemblies that you want to load all in the same folder? If so, use LoadFrom instead of LoadFile.

    If not, is there any possibility that any of the folders may contain copies of the same assembly? If not, you can also use LoadFrom instead of LoadFile.

    Otherwise, you will need to perform assembly resolution yourself by handling the AssemblyResolve event before you start loading assemblies:

    AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
    {
        // First ensure that the assembly hasn't already been loaded:
        var existing = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault (
            x => x.GetName ().Name.Equals (args.Name, StringComparison.OrdinalIgnoreCase));
    
        if (existing != null) return existing;
    
        // Try and locate assembly in the correct folder
        string potentialPath = Path.Combine (someFolder, args.Name + ".dll");
        if (File.Exists (potentialPath)) return Assembly.LoadFile (potentialPath);
    
        // Keep trying more folders. Make sure that your logic is consistent and you only ever
        // load one copy of the same assembly (i.e., with the same simple name).
    
        // Cannot resolve assembly
        return null;
    };
    

    Once complete, you can then load assemblies that you want with Assembly.LoadFile(path) - or simply with Assembly.Load(string simpleName)

Sign In or Register to comment.