36

我正在创建一个 http 模块,在调试时我注意到一些起初(至少)看起来很奇怪的行为。

当我在 httpmodule 的 init 方法中设置断点时,我可以看到http 模块的 init 方法被调用了几次,即使我只是启动了网站进行调试并发出了一个请求(有时它只被命中 1 次, 其他次数多达 10 次)。

我知道我应该期望HttpApplication的多个实例正在运行,并且将为每个实例创建 http 模块,但是当我请求单个页面时,它应该由单个 http 应用程序对象处理,因此只触发一次关联的事件,但它仍然为每个请求触发事件多次,这是没有意义的 - 除了它必须在该httpApplication中添加多次- 这意味着它是每次调用的相同httpmodule init 方法,而不是新的 http 应用程序每次遇到断点时都会创建(请参阅底部的代码示例等)。

这里可能出了什么问题?是不是因为我在调试,在http模块中设置了断点?

它已经注意到,如果我启动网站进行调试并快速越过 httpmodule 中的断点,它似乎只会命中一次init 方法,而eventhandler也是如此。如果我改为让它在断点处挂起几秒钟,则会多次调用init 方法(似乎这取决于我在跨过断点之前等待多长时间)。也许这可能是一些内置功能,以确保 httpmodule 已初始化并且 http 应用程序可以服务 requests ,但它似乎也可能产生灾难性后果。

这似乎是合乎逻辑的,因为它可能正在尝试完成请求,并且由于我设置了断点,它认为出现问题并尝试再次调用 init 方法?所以它可以处理请求吗?

但这是正在发生的事情,一切都很好(我只是在猜测),还是一个真正的问题?

我特别担心的是,如果某些东西使它在“生产/实时”服务器上挂起几秒钟,则会通过init添加许多事件处理程序,因此对页面的每个请求都会突然触发事件处理程序几次。

这种行为可能会迅速导致任何网站宕机。

我已经查看了用于表单身份验证的httpmodulesrolemanagermodule等的“原始”.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(及其模块)将再次尝试服务请求,导致它多次命中事件处理程序。

但似乎如果我然后跳回第一页(或另一页),只有一个HttpApplication将开始处理请求并且一切都按预期进行 - 只要我不让它在断点处挂起.

如果我让它挂在断点上,它会开始创建新的HttpApplication对象并开始添加HttpApplications(超过 1 个)来服务/处理请求(该请求已经在由当前停止在断点)。

我猜想或希望它可能是一些智能的“幕后”方式来帮助分配和处理负载和/或错误。但我不知道。我希望那里的一些人可以向我保证它很好,它应该是怎样的?

4

4 回答 4

50

Init()多次调用该方法是正常的。当应用程序启动时,ASP.NET Worker 进程将实例化HttpApplication它认为需要的尽可能多的对象,然后将它们汇集起来(例如,将它们重用于新请求,类似于数据库连接池)。

现在对于每个HttpApplication对象,它还将实例化每个IHttpModule已注册的副本并多次调用 Init 方法。因此,如果创建了 5 个HttpApplication对象,将创建 5 个您的副本IHttpModule,并且您的 Init 方法被调用 5 次。说得通?

现在为什么要实例化 5 个HttpApplication对象?好吧,也许您的 ASPX 页面具有指向您的浏览器将尝试下载的其他资源的链接,例如 css、javascript、WebResource.aspx,也可能是某处的 iframe。或者,也许 ASP.NET 工作进程“有心情”启动多个HttpApplication对象,这实际上是在 IIS(或内置于网络服务器的 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;
            }
        }
    }
}

我做过类似的事情,而且似乎很有效,但我欢迎对我的工作提出批评,以防我遗漏了什么。

于 2010-03-10T11:45:37.680 回答
8
  1. 检查 HttpContext.Current.Request 以查看模块的 init 被触发的请求。可能是浏览器发送多个请求。

  2. 如果您连接到 IIS,请检查 IIS 日志以了解在您停留在断点时是否收到任何请求。

于 2009-07-17T02:43:15.917 回答
3

这里有一些关于你应该使用什么、何时以及如何工作的解释。 何时在 Global.asax 中使用 Application_Start 与 Init?

编辑:更多阅读

ASP 专栏:HTTP 模块

信息:ASP.NET 中的应用程序实例、应用程序事件和应用程序状态

于 2009-07-17T01:18:08.720 回答
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 请求而无需在回发时重新请求,请使用上面的代码。

于 2010-03-16T08:05:50.153 回答