【问题标题】:Reflection not working on assembly that is loaded using Assembly.LoadFrom反射不适用于使用 Assembly.LoadFrom 加载的程序集
【发布时间】:2014-08-09 11:21:30
【问题描述】:

我有一个库,其中包含一些反射代码,它检查 Asp.Net 的主要程序集、任何引用的程序集并执行一些很酷的操作。我试图在控制台应用程序中执行相同的确切代码,同时仍然反映 Asp.Net 的程序集,但我看到了奇怪的结果。我已经把所有东西都连接好并且代码执行了,但是当我知道它应该返回 true 时,反射代码返回 false,因为我正在调试器中单步执行它。它让我发疯,我不知道为什么从控制台应用程序运行时,反射表现出不同的行为。

这是一些反射代码的完美示例,它获取 Asp.Net 应用程序中的所有区域注册类型 (type.IsSubclassOf(typeof(System.Web.Mvc.AreaRegistration)))。当在 Asp.Net 应用程序的应用程序域中执行时,这会为几种类型返回 true,但是在控制台应用程序下执行时,它会为相同类型返回 false,但仍会反映相同的 Asp.Net 类型。

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

我可以尝试什么来完成这项工作?

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

【问题讨论】:

  • 尝试Type.IsAssignableFrom Method 可能会有一致的行为。
  • @pushpraj, Type.IsAssignableFrom 给出相同的结果。我刚刚注意到,如果我在调试时尝试多次执行 IsAssignableFrom 或 IsSubclassOf,我会在即时窗口中收到此错误:The type 'System.Web.Mvc.AreaRegistration' exists in both 'System.Web.Mvc.dll' and 'System.Web.Mvc.dll'

标签: c# reflection


【解决方案1】:

这与LoadFrom 加载程序集的程序集上下文有关。在 Load 上下文中解析“常规”程序集时,不会使用在 LoadFrom 期间加载的依赖项。

ReflectionOnly 重载也是如此,它加载到 ReflectionOnly 上下文中。

有关详细信息,请参阅https://stackoverflow.com/a/2493855/292411Avoid Assembly.LoadFrom; instead use Assembly.Load 与您的问题类似,LoadFrom

当我遇到这个问题时,我转而使用Load 并要求“插件”程序集与可执行文件位于同一路径;如果程序集在不同的路径中,我不知道是否有技巧可以使事情正常进行。

【讨论】:

  • 谢谢,但我不是试图跨上下文解析程序集,只是试图反映已经加载的程序集。
【解决方案2】:

好的,经过大量调试后,我已经可以正常工作了!事实证明,我的库项目是针对 Asp.Net MVC 4.0 编译的,即使 Nuget 和属性窗口声称是 5.1。 Nuget/MS 再次失败。我的库反映的 Asp.Net MVC 应用程序正在使用 MVC 5.1,因此当Assembly.LoadFromAssemblyResolve 事件运行时,它会将System.Web.Mvc.dll 的两个版本加载到LoadFrom 上下文(4.0 和5.1)中,并且这导致 IsSubclassOf() 方法在预期结果应该为 true 时返回 false。

我在调试时在上面的 cmets 中提到的非常奇怪的错误:The type 'System.Web.Mvc.AreaRegistration' exists in both 'System.Web.Mvc.dll' and 'System.Web.Mvc.dll' 现在才有意义,但只有在事后才有意义。

我最终追踪到这一点的方法是写出AssemblyResolve 被要求解决的所有程序集,并注意到System.Web.Mvc.dll 不在列表中。我启动了Assembly Binding Log Viewer 并清楚地看到System.Web.Mvc.dll 被加载了两次。

回想起来,应该跳过所有自定义日志记录并使用程序集绑定日志查看器来验证每个程序集仅加载一个,并且它是您期望的正确版本。


弄清楚如何正确使用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;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-09
    相关资源
    最近更新 更多