同类型对象的InvalidCastException - 自定义控件加载

28

我遇到了一个很奇怪的错误,我的一个自定义控件似乎创建了两个编译文件,当我尝试使用LoadControl()动态加载它时,会失败,因为无法将一个转换为另一个 - 即使它们完全相同。我写了一条消息来查看所有内容都是相同的,只有编译后的dll不同。

System.Web.HttpUnhandledException (0x80004005):     
 Exception of type 'System.Web.HttpUnhandledException' was thrown. --->             
    System.InvalidCastException:
[A]ASP.Modules_OneProduct_MedioumImage cannot be cast to
[B]ASP.Modules_OneProduct_MedioumImage.         

   Type A originates from 'App_Web_kg4bazz1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default'
    at location 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\80ed7513\10eb08d9\App_Web_kg4bazz1.dll'.          

   Type B originates from 'App_Web_oneproduct_mediumimage.ascx.d1003923.4xoxco7b, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' 
in the context 'Default'    
    at location 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\80ed7513\10eb08d9\App_Web_oneproduct_mediumimage.ascx.d1003923.4xoxco7b.dll'.

代码

这是我按照MSDN上的说明实现后的代码:

foreach (int OneProductID in TheProductIdArrays)
{
    // here is the throw.
    ASP.Modules_OneProduct_MedioumImage OneProduct = 
        (ASP.Modules_OneProduct_MedioumImage)LoadControl(@"~/mod/OneProduct_MediumImage.ascx");

    // do some work with 
    //OneProduct
}

之前我在不使用ASP.的情况下加载控件,但是在出现这个bug之后,寻找解决方案时,我严格遵循了MSDN上的步骤。但无论我怎么做,这个bug仍然存在。

我已经尝试了这两种方法,分别单独尝试和组合使用(都失败了)

<%@ Register src="~/mod/OneProduct_MediumImage.ascx" tagname="OneProduct_MediumImage" tagprefix="uc1" %>
<%@ Reference Control="~/mod/OneProduct_MediumImage.ascx" %>

配置

我的 web.config 文件中,我尝试过将 maxBatchSize 设置为 20、100、1000,以及将 optimizeCompilations 设置为 true 或 false,但是 bug 仍然会出现。

<compilation debug="false" defaultLanguage="C#" batch="true"
maxBatchSize="800" batchTimeout="10800" optimizeCompilations="false"
targetFramework="4.0">

现在关于一些细节

  • 错误是随机的,有时会出现编译错误,有时不会。
  • 该项目很大,页面每分钟都有很多人在线查看内容,但即使没有用户在线也会出现错误。
  • 运行在64位dot.net 4、集成模式下。
  • 作为Web Garden运行,但也尝试过只用一个进程池(结果还是出现了同样的问题)。
  • 整个项目中已经关闭了Session。
  • 这些页面从2007年开始运行,但这个问题仅发生在最近一个月,不幸的是我无法找到它是如何开始或是由什么触发的,因为我晚了几天才发现。
  • 只有在加载一个自定义控件时才会出现,而这个自定义控件需要耗费大量资源。
  • 我已经改动了代码4次,尝试进行了小的和大的修改,但问题仍然存在。
  • 我已经尝试了optimizeCompilations 的true和false两种方式,但结果都一样。
  • 我也曾停止Web服务,删除了所有临时文件,重新启动服务,但问题依然存在。
  • 我尝试在application启动时在global.asax上添加了一个mutex来锁定同一时刻只编译一个文件,但这个方式也失败了。
  • 一旦出现问题就无法自动修复,直到进行下一次更新之前都会一直出错。
  • 我加载此自定义控件的代码存在于代码中的多个位置,在不同的页面上调用。
  • 其他类似的自定义控件没有任何问题。
  • 此自定义控件已禁用ViewState。
  • 我尝试重新定位了一些代码,使用微小的优化更改了整个函数调用,结果还是失败了。
  • 开发电脑上运行正常。我在web.config中添加了batch="true",然后错误立即出现。
  • 除此之外,没有其他我们无法解决的问题。该系统运行数天,进程池没有被回收,内存稳定,剩余可用内存有充足的空间。该程序已经运行了几年,但我们每天都会进行更新。
  • 在相同的核心代码下,运行多个站点(类似stackexchange),所有站点都遇到了同样的随机问题。
  • AutoEventWireup 是false
  • 同样的方法加载另一个自定义控件也会出现这个问题。

当这个错误出现时,我现在的解决办法是强制重新编译项目,然后错误就会消失,直到下一次更新。

我已经试图解决这个问题三周了,但没有找到原因。我尝试了几乎所有我能想到的方法,但都失败了,并且错误还会出现。所以我在这里发布帖子,希望有人能帮助我找到解决方法。

最后一个问题:这个 bug 真的太疯狂了,自定义控件也是一样,对它所做的任何操作都只能在动态加载时进行,然后编译器因某些只有它知道的原因而在两个不同的时间点上出现了它,毫无规律可言。

更新1

我已经在开发人员的机器上复现了这个 bug。在那里,我发现包含此自定义控件的两个 dll 模块是不同的。

其中一个是四个自定义控件的捆绑包。另一个模块是仅包含该自定义控件。

解决方法

为了修复这个 bug,我尝试了三周,最终发现当编译器批量编译目录并将许多不同的自定义控件捆绑在同一个 dll 中时,就会出现此 bug。因此,当我尝试单独加载它时,会抛出此异常。

所以我将有问题的自定义控件移动到一个不同的目录中,并似乎暂时避免了这个问题。

更新2

问题再次出现,即使我将一些文件移动到不同的目录中。它是随机的,无法找到与它触发的内容之间明确的关联。

更新3

因为我们发现这里的主要问题是批量编译(batch="true"),它在同一个 dll 中编译了许多自定义控件,因此,告诉编译器不要这样做的一种方法是使用 maxBatchGeneratedFileSize 参数。我将其设置为 100 并出现了问题,现在我已将其降低到 40 并进行了测试。

maxBatchGeneratedFileSize="40"

由于我曾建议您尝试batch="false"和虚拟编辑保存(例如https://dev59.com/2mjWa4cB1Zd3GeqPr5Em#12476097和http://stackoverflow.com/a/10212834/1236044),所以恐怕我无法对您的问题提供严肃的答案。如果可以的话,也许您可以自行尝试以下方法:-将此控件转换为codebehind - 将该控件移动到与引用它的所有其他控件和页面相同的文件夹中。 - jbl
@jbl 是的,使用 batch="false" 可以解决问题。但是用户体验会受到很大影响... 因为每个非编译页面都会让用户等待很久,甚至可能导致用户离开... - Aristos
@jbl 我也使用了maxBatchGeneratedFileSize参数,在批量编译时,让编译器不要将太多的控件组合在一起。 - Aristos
@CapitanCavernícola 没有任何混淆器。在调试和发布时都会发生相同的情况。我在某些情况下从PageLoad中调用此方法,在其他情况下从Init事件中调用。当出现此错误时,我在每次调用(不仅是第一次),在每个页面上的每次调用中都会得到它。 - Aristos
这是一个自定义控件还是用户控件?看起来你两个都有。你读过这个吗:将.ascx用户控件转换为可再分发的自定义控件 - Alex Filipovici
@AlexFilipovici 是以 .ascs 结尾的 "用户控件"。 - Aristos
5个回答

11

当启用批处理并且在目录级别存在某种形式的循环引用时,就会发生这种情况。

请参见answer以了解我在此上下文中所说的“循环引用”的确切含义,因为其意义非常微妙。

如果您设法打破循环(例如将用户控件移动到其他位置),则不会遇到此问题。

更新1

理论上,我认为只有循环引用才能引起这种情况,但有时它们很难检测。

我将给您提供一种替代方案,我认为它会起作用,并且非常容易尝试(尽管有点hack)。在给您带来问题的用户控件中,在指令中添加以下属性:

<%@ Control Language="C#" [...] CompilerOptions="/define:dummy1" %>

如果您在其他控件中看到这个问题,您可以添加相同的但是加上dummy2、dummy3等,这样就不会批量处理此用户控件,因为它与其他控件有不同的编译需求。技术上讲,您可以将任何一条C#命令行添加为CompilerOptions,但是一个虚假的/define是最简单和无害的。但与全局关闭批处理不同,性能影响将是最小的,因为只有非常少量的页面不会被批处理。顺便说一句,毋庸置疑,您所看到的是ASP.NET中的一个错误,并且该错误可能已经存在10多年了!也许在某个时候应该解决它 :)

我检查了文件,并没有发现任何循环引用。项目结构很庞大,但正确地放置在一棵好的树中,没有任何文件循环引用。然而,即使我把它移到一个单独的目录中仍然是一样的。问题在于编译器将其与其他控件一起添加到dll中,当我尝试手动加载时,它会简单地失败,因为它创建了一个新的dll。其中另外一个方法是创建此控件的第二个克隆,并将其放置在两个位置,具有不同的名称,但它也失败了。 - Aristos
请注意,我在谈论目录级别的循环引用,而不是文件级别的循环引用。我指出的答案有更多关于此的细节。 - David Ebbo
是的,我已经看到了,但我还不太确定那个答案,我会再次检查并重新考虑。 - Aristos
非常抱歉,我刚刚意识到您是 asp.net 团队的一员。再次感谢您的回答。我已经将文件移动到不同的目录中,但它们又与批处理混合在一起了...但没有循环引用,它们只是捆绑编译,当我尝试单独加载它时会抛出错误。 - Aristos
1
哇,谢谢!我完全不知道我即将获得如此可怕的力量! :) - David Ebbo
显示剩余3条评论

2
为了追踪问题的原因,我认为了解控件是如何创建的很重要。请参考这篇文章:将.ascx用户控件转换为可重新分发的自定义控件步骤1:编写用户控件 为了编写用户控件,最好从一个只包含ascx文件的空应用程序开始。尽管制作用户控件使用了“标准”技术,但是有一些限制需要您注意,以便成功地将其转换为独立的自定义控件。主要限制是用户控件必须是自包含的。也就是说,不能依赖于应用程序全局的东西,如 App_Code 或 global.asax 等。原因是,由于目标是将 UserControl 转换为独立的 DLL,如果它依赖于不属于该 DLL 的代码,那么它在其他应用程序中就会出现问题。唯一的例外是 UserControl 可以依赖于位于 bin 目录(或 GAC 中)的程序集。只需确保在其他应用程序中使用自定义控件时始终可用其他程序集即可。 步骤3:使用发布命令预编译站点 (...) 选择 "使用固定名称和单个页面程序集"。这将确保您的用户控件编译成一个单独的程序集,并以ascx文件为基础命名。如果您不勾选此选项,您的用户控件可能会与其他页面和用户控件(如果有的话)一起编译,并且该程序集将接收一个随机名称,这将更难处理。
我认为你很可能已经把用户控件编译并注册为 GAC 中的一个独立的程序集,并将其包含在您的 Web 应用程序 DLL 中。
注意:也许这应该是一个评论,但是我想包括上述链接中的引用。希望对您有帮助。

1
这是一个好主意/解决方法,将其分离并预编译为项目外的自定义控件。 - Aristos
1
@AlexFilipovici,另一个答案通过最小的更改解决了问题,感谢您的回答。 - Aristos

1

请问您能否写出您在web.config文件中放置了哪些选项? - Aristos
在我的情况下,我使用的是ASP.NET WSP(ASPX)。 我有一个法语版的VS,所以我希望翻译能够匹配真实的菜单项:p对于构建,您可以在“Web站点”菜单中进行操作,您将找到“构建选项”(与在解决方案资源管理器面板中右键单击网站时的“属性页面”相同),然后进入“MSBuild选项”,您将在那里找到2个复选框。对于发布,它在参数步骤中,勾选预编译并进入“配置”; 然后取消选中“允许预编译网站可更新”并选择“不合并。为每个页面和控件创建单独的程序集”。 - DestyNova

0

最近在编译修改后的 asp.net MVC 4 版本并将新 DLL 导入项目时,我遇到了类似的问题。

不知何故,我在 web.config 中(包括 views 文件夹中的 web.config)引用了旧版的 DLL。

在我的情况下,错误是因为两个 DLL 是不同版本引起的。4.0.0 和 4.1.0。 也许您应该考虑这一点。 可能需要指定编译文件的版本(我猜测是 DLLs)

希望这能帮助您解决问题。

其他提示:我猜您有某种版本控制系统?如果是,请将所有更改还原到此问题发生之前,并仔细查看代码以及哪些模型/控件发生了变化以及如何变化。 如果您没有使用版本控制系统... 您无法撤销更改,因此您应该开始使用版本控制系统。


谢谢您的回答。在这里,编译是动态的,我没有做任何事情。我发现的问题是编译器将一个DLL添加到一起,而不是多个控件。现在,当我尝试动态加载它时,会再生成一个仅包含控件的DLL,并因此抛出错误! - Aristos
你正在动态编译的同一个东西可能有不同的版本吗?如果是,这可能是问题的原因。 - Nikola Sivkov
我有一个版本控制,但更改太多了!我的意思是太多了。然而,我不确定问题是否与代码有关 - 可能与项目的增长有关,甚至可能与服务器上的 MS 更新有关。 - Aristos
1
这里我调查的问题不是版本,而是一个DLL文件包含了批量编译中的多个用户控件,而第二个DLL文件只包含一个控件。这导致它们无法一起工作,因此问题不在于版本。 - Aristos

0

我注意到有时设计师会创建第二个CodeBehind设计文件,例如你可能会有:

OneProduct_MediumImage.ascx
OneProduct_MediumImage.ascx.cs
OneProduct_MediumImage.ascx.designer.cs
OneProduct_MediumImage.ascx.designer1.cs

如果在解决方案资源管理器中没有设置"显示所有文件"选项,你可能无法注意到,但对于 Web 项目,编译器会编译文件夹中的所有文件,而不仅仅是项目包含的文件。
其次,如果您的项目是一个"网站项目",那么就没有名称空间,这可能会导致许多奇怪的错误。请参考这个 SO 问题: .net 中的名称空间问题 最后,我通过在控件文件上设置 ClassName 属性来解决看似随机的 UserControl 错误。例如:
<%@ Control Language="cs" 
    AutoEventWireup="false" 
    CodeBehind="OneProduct_MediumImage.ascx.cs" 
    Inherits="ASP.Modules_OneProduct_MedioumImage"
    ClassName="OneProduct_MediumImageControl" %>

谢谢您的回答,我确实使用了ClassName,我会检查您所写的其余部分。(我是一位经验丰富的用户,我知道并看到了我的磁盘上的所有文件,包括隐藏的、系统的、每个人的,我不会错过任何东西,而且我从不使用资源管理器进行文件管理,而是使用其他程序。) - Aristos
有时候我只需要将 ClassName 改成随机的东西(只需添加一个随机字母或数字),而不改变其他任何内容,然后突然之间控件就能正常工作了。然后下一次构建它又会出问题,我再改一个字母,就又好了。从来没有弄清楚为什么会这样。 - w5l

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接