我正在创建一个http模块,在调试时我发现了一些起初(至少)看起来像怪异行为的东西。
当我在httpmodule的init方法中设置断点时,我可以看到http模块初始化方法被多次调用,即使我只是启动了网站进行调试并发出了一个请求(有时会被点击只有1次,其他时间多达10次)。
我知道我应该期待HttpApplication的几个实例正在运行,并且每个http模块都会被创建,但是当我请求单个页面时,它应该由一个http应用程序对象处理,因此只会触发事件关联一次,但它仍然会为每个请求多次触发事件,这是没有意义的 - 除了它必须在httpApplication中多次添加 - 这意味着它是相同的httpmodule init方法,每次调用而不是每次碰到我的断点时都会创建一个新的http应用程序(参见我底部的代码示例等)。
这里可能出现什么问题?是因为我正在调试并在http模块中设置断点?
它已经注意到,如果我启动网站进行调试并快速跳过httpmodule中的断点,它只会触发一次init方法,而事件处理程序也是如此。如果我让它在断点处挂起几秒钟,则会多次调用init方法(看起来这取决于我在跨越断点之前等待多长时间)。也许这可能是一些内置功能,以确保httpmodule初始化,http应用程序可以提供请求,但它似乎也可能产生灾难性后果。
这似乎是合乎逻辑的,因为它可能正在尝试完成请求,因为我设置了断点,它认为出了问题,并尝试再次调用init方法?它可以处理请求吗?
但这是发生了什么,一切都很好(我只是在猜测),还是一个真正的问题?
我特别关注的是,如果有什么东西让它挂在“生产/现场”服务器上几秒钟,那么很多事件处理程序都会通过init添加,因此每个页面请求都会突然触发几个事件处理程序次。
此行为可能会导致任何网站崩溃。
我查看了用于formauthentication和rolemanagermodule等的httpmodules的“原始”.net代码,但我的代码与这些模块使用的代码不同。
我的代码看起来像这样。
public void Init(HttpApplication app)
{
if (CommunityAuthenticationIntegration.IsEnabled)
{
FormsAuthenticationModule formsAuthModule = (FormsAuthenticationModule) app.Modules["FormsAuthentication"];
formsAuthModule.Authenticate += new FormsAuthenticationEventHandler(this.OnAuthenticate);
}
}
这是一个如何在.NET框架的RoleManagerModule中完成它的例子
public void Init(HttpApplication app)
{
if (Roles.Enabled)
{
app.PostAuthenticateRequest += new EventHandler(this.OnEnter);
app.EndRequest += new EventHandler(this.OnLeave);
}
}
有谁知道发生了什么事?
(我希望有人可以告诉我为什么会这样,并向我保证一切都很好):))
更新:
我试图缩小问题的范围,到目前为止我发现被调用的Init方法总是在我的http模块的一个新对象上(与之前的想法相符)。
我似乎对于第一个请求(启动站点时)所有正在创建的HttpApplication对象及其模块都试图为第一个请求提供服务,因此全部命中了正在添加的事件处理程序。 我无法弄清楚为什么会这样。
如果我请求另一个页面,所有创建的HttpApplication(及其无模块)将再次尝试提供请求,导致它多次命中eventhandler。
但似乎如果我然后跳回到第一页(或另一个),只有一个HttpApplication将开始处理请求,一切都按预期 - 只要我不让它在休息时挂起点。
如果我让它在断点处挂起,它就会开始创建新的HttpApplication对象,并开始添加HttpApplications(超过1个)来提供/处理请求(已经在HttpApplication服务的过程中,该HttpApplication当前已停止断点)。
我猜或希望它可能是一些智能的“幕后”方式,有助于分发和处理负载和/或错误。但我不知道。 我希望有些人可以向我保证它完全没问题,它应该是怎样的?
答案 0 :(得分:42)
多次调用Init()方法是正常的。当一个应用程序启动时,ASP.NET Worker进程将实例化它认为需要的HttpApplication对象,然后它们将它们集中在一起(例如,将它们重新用于新请求,类似于数据库连接池)。
现在,对于每个HttpApplication对象,它还将实例化已注册的每个IHttpModule的一个副本,并多次调用Init方法。因此,如果创建了5个HttpApplication对象,将创建5个IHttpModule副本,并且您的Init方法调用5次。有意义吗?
现在为什么要实例化5个HttpApplications对象呢?好吧,也许您的ASPX页面链接到您的浏览器将尝试下载的其他资源,css,javascript,WebResource.aspx,也许是某个地方的iframe。或者,ASP.NET Worker Process可能会“启动”超过1个HttpApplication对象,这实际上是在IIS(或内置于Web服务器中的VS)下运行的ASP.NET进程的内部细节/优化。
如果您希望保证只运行一次的代码(并且不想在Global.asax中使用Application_StartUp事件),您可以在IHttpModule中尝试以下操作:
private static bool HasAppStarted = false;
private readonly static object _syncObject = new object();
public void Init(HttpApplication context)
{
if (!HasAppStarted)
{
lock (_syncObject)
{
if (!HasAppStarted)
{
// Run application StartUp code here
HasAppStarted = true;
}
}
}
}
我做过类似的事情似乎也有效,但是如果我错过了什么,我会欢迎批评我的工作。
答案 1 :(得分:8)
检查HttpContext.Current.Request以查看触发模块初始化的请求。可以是浏览器发送多个请求。
如果您已连接到IIS,请检查IIS日志,以了解您在断点处停留时是否收到任何请求。
答案 2 :(得分:2)
以下是关于您应该使用什么,何时以及如何工作的一些解释。 When to use Application_Start vs Init in Global.asax?
编辑:更多阅读
INFO: Application Instances, Application Events, and Application State in ASP.NET
答案 3 :(得分:1)
上面的例子为所有请求锁定IHttpModule,然后,它会冻结整个应用程序。 如果您的IHttpModule调用请求需要多次调用HttpApplication方法CompleteRequest并在EndRequest事件中配置IHttpModule的HttpApplication实例,以便删除HttpApplication的实例,如下所示:
public class TestModule :IHttpModule
{
#region IHttpModule Members
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += new EventHandler(context_EndRequest);
}
void context_EndRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
app.CompleteRequest();
app.Dispose();
}
void context_BeginRequest(object sender, EventArgs e)
{
//your code here
}
#endregion
}
如果您每次都需要IHttpModule请求而无需在回发时重新请求,请使用上面的代码。