HttpApplication
我注意到同一实例引发的事件以及在初始化期间HttpModules
订阅同一事件处理程序的事件有一些奇怪的行为,这在 IIS 7 中的IntegratedMode和ClassicMode之间(至少到版本 8)。
似乎当您的模块在其方法中为给定HttpApplication
实例订阅事件处理程序时,在IntegratedMode中运行时,与订阅事件相关的 C# 规则不再适用,至少在引发事件时是如此。Init()
通常当您进行这样的订阅时
httpApplication.EndRequest -= SomeMethod;
httpApplication.EndRequest += SomeMethod;
保证您SomeMethod
只订阅一次,因此当httpApplication's EndRequest
引发时,您的方法只会被调用一次。
当然如果SomeMethod
是一个实例方法并且你有多个实例,那么每个实例都会调用它的方法,这是正常的。但是如果你有一个静态方法,那么无论有多少不同的实例订阅同一个静态方法,SomeMethod
它都应该只被调用一次。
HttpModule
当您有多个实例订阅同一静态方法到同一实例的同一事件时,情况似乎并非如此HttpApplication
,同时在IntegratedMode下运行
如果您反编译HttpApplication
代码,那么您会看到每个事件订阅实际上都转换为 IIS 中的某些通知(至少在IntegratedMode中运行时)。这一切都很好,但我觉得他们假设在方法HttpApplication
期间附加到实例事件的每个事件处理程序都应该是特定的实例方法,这至少可以说是奇怪的吗?Init()
HttpModule
HttpModule
您将在下面找到一个尽可能小的复制样本,以清楚地反映这个问题。我不是在寻找其他方法来创建示例(绕过问题、重构代码……),它只是用最少的代码和设置重现了问题。
所以我的问题是:
这种奇怪的行为是设计使然还是他们忽略了/错误?还是我对订阅做出了错误的假设?
重现步骤
创建一个名为IssueDemo的空 Web 应用程序项目,并在其中包含以下文件
添加一个空的 Index.aspx 页面(我使用的是网络表单页面,但 MVC 也存在同样的问题......)
添加包含以下内容的 Modules.cs 文件
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 { } }
调整 web.config 文件以反映以下内容(如果您没有将项目命名为IssueDemo ,请注意程序集引用)
<?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>
在 ClassicMode 下运行它(您可以使用内置的 VS Development Server 或为您的应用程序选择 IIS 中的Classic .Net AppPool)。您将在浏览器中看到以下消息:
Written by ModuleBase's LogSomething()
trace.log文件将显示以下内容(我删除了 favicon.ico 的条目,并且实例ID会有所不同):
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
这基本上是我在为同一个
HttpApplication
实例订阅相同的静态方法时所期望的(#11543392)。在 IntgratedMode 下运行它(你不能使用内置的 VS 开发服务器,但你可以使用 IISExpress 或带有DefaultAppPool的普通 IIS )。您现在将在浏览器中看到以下消息:
Written by ModuleBase's LogSomething() Written by ModuleBase's LogSomething()
并且trace.log文件将显示以下内容(我删除了 favicon.ico 的条目和其他 httpApplication 实例的创建,并且实例ID会有所不同):
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
当为同一个实例订阅同一个静态方法时,这不是我所期望的
HttpApplication
(#36181605)。您会看到同一请求的重复执行 (#63238509),并且由于仅附加了事件处理程序,因此我可以得出的唯一结论是该事件被引发了两次。顺便说一句,如果您添加更多派生类型并在 web.config 中注册它们,您会看到重复项会增加(但仅在 IntegratedMode 中)。
如果有人能回答这个问题,那就太好了。与此同时,我已经通过检查我们的代码是否已经在特定请求期间执行来解决这个问题。