在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应用程序?如果是这样,您应该知道一个名为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设置为包含当前执行程序集的目录。简单得多:)