我注意到有关同一HttpApplication
实例引发的事件以及在初始化期间订阅了相同的事件处理程序的HttpModules
的一些奇怪行为,并且这在之间IIS 7中的 IntegratedMode 和 ClassicMode (至少版本为8)。
当您的模块在HttpApplication
方法中为给定的Init()
实例订阅事件处理程序时,似乎在 IntegratedMode 中运行时,与订阅事件相关的C#规则不再适用,至少在引发事件时是这样。
通常在您进行此类订阅时
httpApplication.EndRequest -= SomeMethod;
httpApplication.EndRequest += SomeMethod;
您已经知道SomeMethod
只订阅了一次,因此当httpApplication's EndRequest
被提出时,您的方法只会被调用一次。
当然如果SomeMethod
是一个实例方法并且你有多个实例,那么每个实例都会调用它的方法,这是正常的。但是如果你有一个静态方法,那么无论有多少个不同的实例订阅相同的静态SomeMethod
,它只应该被调用一次。
当您有多个HttpModule
个实例为相同的HttpApplication
实例的同一事件订阅相同的静态方法时,情况似乎并非如此,而在 IntegratedMode
如果您反编译HttpApplication
代码,那么您会看到每个事件订阅实际上都转换为IIS中的某些通知(至少在 IntegratedMode中运行时 )。这一切都很好,但我觉得他们假设在HttpApplication
的{{1}}方法期间附加到Init()
实例事件的每个事件处理程序应该是一个特定HttpModule
的实例方法,至少可以说是奇怪的吗?
下面你会发现一个尽可能小的复制样本,它清楚地反映了这个问题。我不是在寻找其他方法来创建示例(绕过问题,重构代码,......),它只是用最少量的代码和设置重现问题。
所以我的问题是:
这是设计上的奇怪行为,还是他们忽略的东西/错误?或者我对订阅做出错误的假设?
重现的步骤
创建一个名为 IssueDemo 的空Web应用程序项目,并在其中包含以下文件
添加一个空的Index.aspx页面(我正在使用一个webform页面,但MVC存在同样的问题......)
添加包含以下内容的Modules.cs文件
HttpModule
调整web.config文件以反映以下内容(如果您没有为项目命名,请注意程序集引用 IssueDemo )
namespace IssueDemo
{
public abstract class ModuleBase : System.Web.IHttpModule
{
public void Init(System.Web.HttpApplication context)
{
System.IO.File.AppendAllText(
System.AppDomain.CurrentDomain.BaseDirectory + "trace.log",
string.Format("Init() called on {0} (#{1}) for HttpApplication (#{2}){3}",
this.GetType(),
this.GetHashCode(),
context.GetHashCode(),
System.Environment.NewLine));
context.EndRequest -= LogSomething;
context.EndRequest += LogSomething;
}
public void Dispose() { }
private static void LogSomething(object sender, System.EventArgs e)
{
System.Web.HttpApplication httpApplication = (System.Web.HttpApplication)sender;
System.IO.File.AppendAllText(
System.AppDomain.CurrentDomain.BaseDirectory + "trace.log",
string.Format("LogSomething() called on ModuleBase triggered by event raise of HttpApplication (#{0}) for Request (#{1}): {2}{3}",
httpApplication.GetHashCode(),
httpApplication.Request.GetHashCode(),
httpApplication.Request.RawUrl,
System.Environment.NewLine));
httpApplication.Response.Write("Written by ModuleBase's LogSomething()<br/>");
}
}
public class MyHttpModule : ModuleBase { }
public class MyOtherHttpModule : ModuleBase { }
}
在ClassicMode中运行它(您可以使用内置的VS Development Server或在IIS中为您的应用程序选择 Classic .Net AppPool )。您将在浏览器中看到以下消息:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<httpModules>
<add name="MyHttpModule" type="IssueDemo.MyHttpModule, IssueDemo"/>
<add name="MyOtherHttpModule" type="IssueDemo.MyOtherHttpModule, IssueDemo"/>
</httpModules>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules>
<add name="MyHttpModule" type="IssueDemo.MyHttpModule, IssueDemo" />
<add name="MyOtherHttpModule" type="IssueDemo.MyOtherHttpModule, IssueDemo" />
</modules>
</system.webServer>
</configuration>
并且 trace.log 文件将显示以下内容(我删除了favicon.ico的条目,而实例 ID 将有所不同):
Written by ModuleBase's LogSomething()
这基本上是我在将相同的静态方法订阅到同一个Init() called on IssueDemo.MyHttpModule (#9654443) for HttpApplication (#11543392)
Init() called on IssueDemo.MyOtherHttpModule (#66322936) for HttpApplication (#11543392)
LogSomething() called on ModuleBase triggered by event raise of HttpApplication (#11543392) for Request (#19612087): /index.aspx
实例时所期望的(#11543392)。
在IntgratedMode中运行它(您不能使用内置的VS开发服务器,但可以使用IISExpress或普通的IIS与 DefaultAppPool )。您现在将在浏览器中看到以下消息:
HttpApplication
和 trace.log 文件将显示以下内容(我删除了favicon.ico的条目和其他httpApplication实例的创建,而实例 ids 将有所不同):
Written by ModuleBase's LogSomething()
Written by ModuleBase's LogSomething()
这不是我在将相同的静态方法订阅到同一个Init() called on IssueDemo.MyHttpModule (#39086322) for HttpApplication (#36181605)
Init() called on IssueDemo.MyOtherHttpModule (#28068188) for HttpApplication (#36181605)
LogSomething() called on ModuleBase triggered by event raise of HttpApplication (#36181605) for Request (#63238509): /index.aspx
LogSomething() called on ModuleBase triggered by event raise of HttpApplication (#36181605) for Request (#63238509): /index.aspx
实例时所期望的(#36181605)。您看到同一请求的重复执行(#63238509),并且由于只附加了事件处理程序,我可以做的唯一结论是事件被引发两次。顺便说一下,如果你添加一些派生类型并在web.config中注册它们,你会发现重复会增加(但仅限于IntegratedMode)。
如果有人能回答这个问题,那就太好了。与此同时,我已经通过检查我们的代码是否已在特定请求中执行来解决此问题。
答案 0 :(得分:4)
几天后我在Microsoft Connect上问了同样的问题,今天他们提供了以下答案:这种行为是设计上的,处理模块在经典模式和集成模式之间注册的方式不同强>
下面你会找到他们答案的详细说明:
在经典模式下,IIS有效地将整个托管ASP.NET应用程序(System.Web运行时和已注册的任何自定义模块)视为一个单独的HTTP模块。 IIS只是通知ASP.NET它应该执行一些工作,并且ASP.NET运行时将遍历其模块列表并逐个完成所有操作。由于ASP.NET完全负责事件管理,我们只使用一个EventHandlerList(每个HttpApplication)来协调所有事情。
但是,在集成模式下,IIS了解管道中运行的每个单独模块。 IIS协调哪些模块将接收哪些通知。一旦模块的Init方法运行完成,其注册在模块的生命周期内被视为“烘焙”。这意味着模块事件注册是隔离的:每个模块都有自己独立的事件处理程序注册对象(因为IIS内部保持它们隔离)。如果模块A使用某个委托调用add_EndRequest,而模块B使用相同的委托调用remove_EndRequest,则事件处理程序存储实际上由内存中的两个不同的对象支持,因此模块A和B不能相互影响注册