在ASP.NET调试期间使用Activator.CreateInstanceFrom时,类型不匹配

本文关键字:CreateInstanceFrom Activator 不匹配 类型 NET ASP 调试 | 更新日期: 2023-09-27 18:25:45

在我正在处理的项目中,我创建了一个AppDomain,并告诉它加载当前正在执行的程序集。然后,我调用Activator.CreateInstanceFrom来创建内部类(类型为MyType)的编组实例(您可以看到它也使用相同的程序集位置):

        ObjectHandle handle = Activator.CreateInstanceFrom(
            Factory.operatingDomain,
            Assembly.GetExecutingAssembly().Location,
            typeof(MyType).FullName,
            false,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            new object[] { config },
            null,
            null);

当我对这个程序集进行单元测试时,一切都正常,但在ASP.NET调试环境中使用它时,它会抛出一个MissingMethodException。我花时间创建了自己的扩展Binder的类,看看会发生什么。唯一的区别似乎是,在调试环境中,传入的参数(config)从位于常规目录中的程序集获取其类型,而ParameterInfo对象从位于ASP.NET临时文件夹中的程序集中获取其类型。

即使我通过了typeof(config).Assembly.Location,我也会得到同样的结果。因此,它是框架内部的东西。

除了保留一个小众使用的内部可访问的自定义绑定器之外,我能做些什么来纠正这一点吗?当参数类型根据FullName匹配时,该绑定器会返回true?

更新

我已经尝试从应用程序域加载的程序集传递程序集的位置,但仍然没有骰子。我的自定义绑定器也不起作用——它需要我实现binder.ChangeType,在那里我发现我甚至不能从一种类型转换到另一种类型(尽管如前所述,它们之间的唯一区别是程序集的位置。)

更新2

我尝试了以下代码,以确保应用程序域从正确的位置加载正确的程序集:

        if (binPath != String.Empty)
        {
            Factory.operatingDomain.SetData("assemblyLocation", Assembly.GetExecutingAssembly().Location);
            Factory.operatingDomain.DoCallBack(() =>
            {
                String location = AppDomain.CurrentDomain.GetData("assemblyLocation").ToString();
                String filename = System.IO.Path.GetFileName(location);
                List<String> paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';').ToList();
                foreach (String path in paths.ToArray())
                {
                    paths.Remove(path);
                    paths.AddRange(System.IO.Directory.GetFiles(path, filename));
                }
                Assembly.LoadFrom(paths[0]);
            });
        }

这是对的!它加载了正确的程序集!但在这里,稍晚:

String location = Factory.operatingDomain
                         .GetAssemblies()
                         .Single(a => a.FullName == Assembly.GetExecutingAssembly().FullName)
                         .Location;

这将返回.NET临时文件夹路径。这是不对的。我认为这是框架中的一个错误!

在ASP.NET调试期间使用Activator.CreateInstanceFrom时,类型不匹配

您的应用程序是什么?ASP.NET应用程序?如果是这样,您应该知道一个名为assembly shadow-copy的野兽(请参阅http://msdn.microsoft.com/en-us/library/ms404279.aspx)。在ASP.NET中默认使用此功能。简而言之,当您将位于bin文件夹中的程序集与ASP.NET应用程序集一起使用时,它会被复制到ASP.NET临时文件夹中并从中加载。

若要确保这是导致您遇到这种行为的原因,请尝试将定义"config"参数类型的程序集放在某个单独的文件夹中,并通过捕获AppDomain_AssemblyResolve事件来加载程序集,或者在使用它之前手动加载它。这样,您将忽略程序集卷影复制行为。

当您向应用程序域询问其程序集位置时,.NET框架似乎无法正确解析程序集位置。为了解决这个问题,我不得不自己探索组装:

        // This code I was already using:
        String binPath = String.Empty;
        if (!String.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
        {
            String[] paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';');
            for (var i = 0; i < paths.Length; i++)
            {
                paths[i].Remove(0, AppDomain.CurrentDomain.BaseDirectory.Length);
            }
            binPath = String.Join(";", paths);
        }
        Factory.operatingDomain = AppDomain.CreateDomain("my_remote_domain", null,
            new AppDomainSetup
            {
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                // Sometimes, like in a web app, your bin folder is not the same
                // as the base dir.
                PrivateBinPath = binPath
            });
        // The new code that solved my problem begins here:
        if (binPath != String.Empty)
        {
            Factory.operatingDomain.SetData("assemblyLocation", Assembly.GetExecutingAssembly().Location);
            Factory.operatingDomain.DoCallBack(() =>
            {
                String location = AppDomain.CurrentDomain.GetData("assemblyLocation").ToString();
                String filename = System.IO.Path.GetFileName(location);
                List<String> paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';').ToList();
                foreach (String path in paths.ToArray())
                {
                    paths.Remove(path);
                    paths.AddRange(System.IO.Directory.GetFiles(path, filename));
                }
                Assembly.LoadFrom(paths[0]);
                AppDomain.CurrentDomain.SetData("assemblyLocation", paths[0]);
            });
            Factory.realAssemblyLocation = Factory.operatingDomain.GetData("assemblyLocation").ToString();
        }
        else
        {
            Factory.operatingDomain.Load(Assembly.GetExecutingAssembly().FullName);
        }

然后,当我尝试创建远程对象的实例时,我会这样做:

        String location = Factory.realAssemblyLocation ?? Assembly.GetExecutingAssembly().Location;
        ObjectHandle handle = Activator.CreateInstanceFrom(
            Factory.operatingDomain,
            location,
            typeof(MyType).FullName,
            false,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            new object[] { config },
            null,
            null);

更新日期:2014年4月16日

事实证明,I真正所要做的就是将新AppDomain的ApplicationBase设置为包含当前执行程序集的目录。简单得多:)

相关文章:
  • 没有找到相关文章