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

时间:2013-07-30 22:40:44

标签: c# asp.net .net reflection

在我正在处理的项目中,我创建了一个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,我还能做些什么来纠正这个问题吗?

更新

我已经尝试传递从app域加载的程序集中获取的程序集的位置,但仍然没有骰子。我的自定义绑定器也不起作用 - 它需要我实现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临时文件夹路径。那是不对的。我认为这是框架中的一个错误!

2 个答案:

答案 0 :(得分:1)

当您向应用程序域询问其某个程序集位置时,.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);

更新4/16/2014

事实证明,我真正所有必须做的就是将新AppDomain的ApplicationBase设置为包含当前正在执行的程序集的目录。更简单:)

答案 1 :(得分:1)

你的申请是什么? ASP.NET应用程序?如果是,你应该知道一个名为程序集卷影副本的野兽(见http://msdn.microsoft.com/en-us/library/ms404279.aspx)。这个东西在ASP.NET中默认使用。简而言之,当您使用位于bin文件夹中的程序集以及ASP.NET应用程序程序集时,它将被复制到ASP.NET临时文件夹并从那里加载。

要确保这是导致您遇到的行为的原因,请尝试放置定义&#34; config&#34;的类型的程序集。参数进入某个单独的文件夹并通过捕获AppDomain_AssemblyResolve事件加载程序集或在使用它之前手动加载它。这样您就会忽略程序集shadowcopy行为。