简体   繁体   English

反射不适用于使用Assembly.LoadFrom加载的程序集

[英]Reflection not working on assembly that is loaded using Assembly.LoadFrom

I have a library that contains some reflection code which inspects an Asp.Net's primary assembly, any referenced assemblies and does cool stuff. 我有一个包含一些反射代码的库,该代码检查Asp.Net的主程序集,任何引用的程序集并进行出色的处理。 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. 我试图获取相同的代码以在控制台应用程序中执行,同时仍然反映在Asp.Net的程序集上,并且看到了奇怪的结果。 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. 我已经完成所有工作并执行了代码,但是当我在调试器中逐步执行该反射代码时,当我知道反射代码应该返回true时,反射代码将返回false。这使我发疯,我不知道为什么从控制台应用程序运行时,反射表现出不同的行为。

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)) ). 这是一些反射代码的完美示例,该反射代码获取Asp.Net应用程序中所有类型的区域注册( 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. 在Asp.Net应用程序的应用程序域中执行时,对于几种类型返回true,但是在控制台应用程序中执行时,对于那些相同类型返回false,但是仍然反映那些相同的Asp.Net类型。

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. 我也尝试过使用Assembly.ReflectionOnlyLoadFrom方法,但是即使编写了所有代码以手动解析引用的程序集之后,下面显示的反射代码在应返回true的类型上也返回false。

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

This has to do with the assembly context in which LoadFrom loads assemblies. 这与LoadFrom加载程序集的程序集上下文有关。 Dependencies loaded during LoadFrom will not be used when resolving "regular" assemblies in the Load context. Load上下文中解析“常规”程序集时,将不使用在LoadFrom加载的依赖项。

The same appies the ReflectionOnly overloads, which load into the ReflectionOnly context. 相同的应用ReflectionOnly重载,该重载加载到ReflectionOnly上下文中。

For detailed information see https://stackoverflow.com/a/2493855/292411 , and Avoid Assembly.LoadFrom; 有关详细信息,请参见https://stackoverflow.com/a/2493855/292411 ,并避免Assembly.LoadFrom; instead use Assembly.Load for an issue with LoadFrom similar to yours. 而是使用Assembly.Load解决与您类似的LoadFrom问题。

When I ran into this issue I switched to using Load and demanded "plugin" assemblies to be in the same path as the executable; 当我遇到这个问题时,我切换到使用Load并要求“插件”程序集与可执行文件位于同一路径中。 I don't know if there are tricks to make things work if the assemblies are in different paths. 我不知道如果程序集位于不同的路径下是否有使工作正常的技巧。

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和属性窗口要求5.1,我的库项目仍针对Asp.Net MVC 4.0进行编译。 Nuget/MS fail again. Nuget / MS再次失败。 The Asp.Net MVC application that my library is reflecting on is using MVC 5.1 so when the Assembly.LoadFrom and the AssemblyResolve event ran it was loading two versions of System.Web.Mvc.dll into the LoadFrom context (4.0 & 5.1) and this caused the IsSubclassOf() method to return false when the expected result should have been true. 我的库所反映的Asp.Net MVC应用程序正在使用MVC 5.1,因此,当Assembly.LoadFromAssemblyResolve事件运行时,它将两个版本的System.Web.Mvc.dll加载到LoadFrom上下文(4.0和5.1)中,并且当预期结果应该为true时,这导致IsSubclassOf()方法返回false。

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 type 'System.Web.Mvc.AreaRegistration' exists in both 'System.Web.Mvc.dll' and 'System.Web.Mvc.dll' ,但只有在事实之后。

The way I finally tracked this down was by writing out all of the assemblies that AssemblyResolve was called upon to resolve and noticed that System.Web.Mvc.dll was not in the list. 我最终跟踪此问题的方法是,写出所有需要AssemblyResolve进行解析的程序集,并注意到System.Web.Mvc.dll不在列表中。 I fired up the Assembly Binding Log Viewer and was clearly able to see that System.Web.Mvc.dll was being loaded twice. 我启动了程序集绑定日志查看器 ,很清楚地看到System.Web.Mvc.dll被加载了两次。

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. 回想起来,应该只跳过所有自定义日志记录,而仅使用Assembly Binding Log Viewer来验证每个程序集中只有一个正在加载并且它是您期望的正确版本。


Figuring out how to use AssemblyResolve properly was a nightmare so here is my unfinished, but working code for posterity. 弄清楚如何正确使用AssemblyResolve是一场噩梦,所以这是我未完成的但后代有效的代码。

public class CodeGenerator
{
    public static string BaseDirectory { get; set; }
    public static string BinDirectory { get; set; }

    static CodeGenerator()
    {
        BinDirectory = "bin";
        // setting this in a static constructor is best practice
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }

    public CodeGenerator(string entryAssemblyPath = null, string baseDirectory = null, string binDirectory = null)
    {
        if (string.IsNullOrWhiteSpace(baseDirectory))
            BaseDirectory = AppDomain.CurrentDomain.BaseDirectory;
        else
            BaseDirectory = baseDirectory;

        if (string.IsNullOrWhiteSpace(binDirectory) == false)
            BinDirectory = binDirectory;

        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(); // reflect away!

        ... code ...
    }

    static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        try
        {
            if (args == null || string.IsNullOrWhiteSpace(args.Name))
            {
                Logger.WriteLine("cannot determine assembly name!", Logger.LogType.Debug);
                return null;
            }

            AssemblyName assemblyNameToLookFor = new AssemblyName(args.Name);
            Logger.WriteLine("FullName is {0}", Logger.LogType.Debug, assemblyNameToLookFor.FullName);

            // don't load the same assembly twice!
            var domainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
            var skipLoading = false;
            foreach (var dAssembly in domainAssemblies)
            {
                if (dAssembly.FullName.Equals(assemblyNameToLookFor.FullName))
                {
                    skipLoading = true;
                    Logger.WriteLine("skipping {0} because its already loaded into the domain", Logger.LogType.Error, assemblyNameToLookFor.FullName);
                    break;
                }
            }
            if (skipLoading == false)
            {
                var requestedFilePath = Path.Combine(Path.Combine(BaseDirectory, BinDirectory), assemblyNameToLookFor.Name + ".dll");
                Logger.WriteLine("looking for {0}...", Logger.LogType.Warning, requestedFilePath);
                if (File.Exists(requestedFilePath))
                {
                    try
                    {
                        Assembly assembly = Assembly.LoadFrom(requestedFilePath);
                        if (assembly != null)
                            Logger.WriteLine("loaded {0} successfully!", Logger.LogType.Success, requestedFilePath);
                        // todo: write an else to handle load failure and search various probe paths in a loop
                        return assembly;
                    }
                    catch (FileNotFoundException)
                    {
                        Logger.WriteLine("failed to load {0}", Logger.LogType.Error, requestedFilePath);
                    }
                }
                else
                {
                    try
                    {
                        // ugh, hard-coding, but I need to get on with the real programming for now
                        var refedAssembliesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1");
                        requestedFilePath = Path.Combine(refedAssembliesPath, assemblyNameToLookFor.Name + ".dll");
                        Logger.WriteLine("looking for {0}...", Logger.LogType.Warning, requestedFilePath);
                        Assembly assembly = Assembly.LoadFrom(requestedFilePath);
                        if (assembly != null)
                            Logger.WriteLine("loaded {0} successfully!", Logger.LogType.Success, requestedFilePath);
                        // todo: write an else to handle load failure and search various probe paths in a loop
                        return assembly;
                    }
                    catch (FileNotFoundException)
                    {
                        Logger.WriteLine("failed to load {0}", Logger.LogType.Error, requestedFilePath);
                    }
                }
            }
        }
        catch (Exception e)
        {
            Logger.WriteLine("exception {0}", Logger.LogType.Error, e.Message);
        }
        return null;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM