birdus
birdus

Reputation: 7504

IHttpModule.BeginRequest firing 2X, Application_BeginRequest firing 1X

I'm running VS 2008 and .NET 3.5 SP1.

I want to implement hit tracking in an HttpModule in my ASP.NET app. Pretty simple, I thought. However, the BeginRequest event of my HttpModule is firing twice for each page hit. The site is very simple right now...no security, just a bit of database work. Should log one row per page hit. Why is this event firing twice?

Moreover, IHttpModule.BeginRequest actually fires a different number of times for the first page hit when running for the first time (from a closed web browser)...3 times when I'm hitting the DB to provide dynamic data for the page, and only 1 time for pages where the DB isn't hit. It fires 2 times for every page hit after the first one, regardless of whether or not I'm touching the DB.

It's interesting to note that Application_BeginRequest (in Global.asax) is always firing only once.

Here's the code:

using System;
using System.Data;
using System.Data.Common;
using System.Net;
using System.Web;
using BluHeron.BusinessLayer;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;

namespace BluHeron.HttpModules
{
    public class SiteUsageModule : IHttpModule
    {
        public void Init(HttpApplication httpApp)
        {
            httpApp.BeginRequest += OnBeginRequest;
        }

        static void OnBeginRequest(object sender, EventArgs a)
        {
            UsageLogger.LogSiteUsage(((HttpApplication)sender).Context.Request);
        }

        public void Dispose()
        { }
    }

    public static class UsageLogger
    {
        public static void LogSiteUsage(HttpRequest r)
        {
            string ipAddress = GetHostAddress(Dns.GetHostAddresses(Dns.GetHostName()));
            string browserVersion = r.Browser.Type;

            string[] urlChunks = r.RawUrl.Split('/');
            string page = urlChunks[urlChunks.GetLength(0)-1];

            SqlDatabase db = new SqlDatabase(Common.GetConnectionString());
            DbCommand cmd = db.GetStoredProcCommand("LogUsage");

            db.AddInParameter(cmd, "IPAddress", SqlDbType.NVarChar, ipAddress);
            db.AddInParameter(cmd, "BrowserVersion", SqlDbType.NVarChar, browserVersion);
            db.AddInParameter(cmd, "PageName", SqlDbType.NVarChar, page);
            db.AddInParameter(cmd, "Notes", SqlDbType.NVarChar, "");

            db.ExecuteNonQuery(cmd);
        }

        private static string GetHostAddress(IPAddress[] addresses)
        {
            foreach (IPAddress ip in addresses)
            {
                if (ip.ToString().Length <= 15)
                {
                    return ip.ToString();
                }
            }

            return "";
        }
    }
}

Upvotes: 6

Views: 5477

Answers (7)

Eilon
Eilon

Reputation: 25704

One possibility is that there are other requests going on that you might not be considering. For example, let's say your ASPX page references some images or CSS files. If those requests go through the ASP.NET pipeline then your module will be called and they'll register as hits.

Also, when you say IHttpModule.BeginRequest, do you mean that in IHttpModule.Init() you are hooking up HttpApplication.BeginRequest? If so then the reason I mention above might still apply.

Upvotes: 0

birdus
birdus

Reputation: 7504

This is interesting. I removed the reference to the CSS file from the master page and I'm getting fewer repeat hits in the HttpModule for certain browsers (as was suggested), but I'm still getting repeats. I have 6 browsers installed, and I'm getting some variation between them.

For reference, this is the URL I'm plugging in to my browsers for this test:

http://localhost/BluHeron

default.aspx is set as the start page and is indeed returned for the aforementioned URL. I'm using HttpRequest.RawUrl for reporting which page the user hit. Specifically, I'm splitting the RawUrl string and just reporting the last item in the array of strings (see code).

  • Every single browser is reporting hitting default.aspx, as expected (RawUrl = /BluHeron/default.aspx).
  • 4 of the 6 browsers are also reporting BluHeron (RawUrl = /BluHeron).
  • 3 of the 6 browsers are also recording a blank in the database (RawUrl = /BluHeron/).

There are a couple ways I can get accurate reporting of how many people are hitting which pages.

  1. Select from the database only rows that actually list one of my pages (ignore /BluHeron and blanks)
  2. Just use Application_BeginRequest in the global.asax file, which seems to consistently get called only once per page hit.
  3. Get this figured out.

So, I've got options for getting good reports even with crappy data in the database. I would prefer to understand what's going on here and not to have junk in the database.

Thanks for looking, everyone!

Upvotes: 1

Michael come lately
Michael come lately

Reputation: 9323

The "Default Document" part of IIS seems to fire a second BeginRequest event.

If you have determined that the Request.Path is the same for the HttpApplication in both event handlers and your URL ends with a slash, try adding a URL Rewrite rule to shortcut the "Default Document" processing.

Upvotes: 1

Nitin Daphale
Nitin Daphale

Reputation: 11

Disable Browser Link in Visual Studio 2013 and up, which causes the second request.

VS Snapshot

This occurs when an Application is run from Visual Studio.

Upvotes: -1

artfulhacker
artfulhacker

Reputation: 4873

We solved this by using

HttpContext.Current.ApplicationInstance.CompleteRequest();

This should prevent the the twice fire you are seeing.

Upvotes: 1

Westley
Westley

Reputation: 61

quite late on this, but ran into the same issue. In our case it was due to the anonymous request first that returns the 401 per the RFC. The second request authenticates.

Upvotes: 1

Hith
Hith

Reputation: 31

This might be too late for the answer but can be useful for someone else. I faced with the same problem. BeginRequest event triggered for twice for each request. I debugged the code and realized that the first trigger for actual resource request but the second is result of "favicon.ico" request. At the beginning of BeginRequest event, a simple check for favicon.ico request eliminates second execution of the method.

public void Application_BeginRequest(object sender, EventArgs e) {
   HttpApplication app = (HttpApplication)sender;
   HttpContext ctx = app.Context;

   if (ctx.Request.Path == "/favicon.ico") { return; }

Upvotes: 3

Related Questions