Monday, October 1, 2007

Image element with an empty string for the src="" attribute, results in making a call for Default.aspx

Ok, I dont know what you want to call this. A bug in IIS ? Or should we just say unexpected behaviour. Unexpected behaviour obviously, atleast for me since I were not prepared for and expecting this. It was especially very hard to track down and find out why everytime a page is requested, it would also execute a call to "default.aspx" on a seperate request, executing code in default.aspx for nothing. so i'm going to blog my findings here just in case someone else experiences this.

Basically when you have images on your pages, they are not going to be processed by the aspnet_isapi dll since image file extentions are not mapped to it by default.

First a small simple intro :
when the browser meets an <img element, it will try to make a request to the server. The request is going to be for the file in the src="" mce_src="" attribute of your <img element. However since image extentions are not mapped to the Aspnet_isapi.dll in IIS, it will completely bypass going through your application life cycle, since its the Aspnet_isapi.dll in turn which communicates the request to the worker process (aspnet_wp.exe).

Ok, now imagine if for whatever reason you have an image in your page, but the src resolved to an empty string -->> src="" seems a bit unlikely you might think, but what if. What do you think is going to happen ?

I am working on an opensource application and this is exactly what was happening. Basically there were image spacers being used. Image spacers are usually set to less than 10pixels in height and width so if the image were missing, it wont display that nice square box with an x in it, which made it almost impossible to notice.

I was doing some work in Application_AcquireRequestState event, to which i had subscribed to in a custom httpmodule. Everytime i would run this method in debug mode and i'd see it execute an extra time! So i look at the request to why and who is making this extra request and soon enough i see its a request for "default.aspx". But the page i ran is not default.aspx. This was basically driving me nuts. Why is default.aspx running, who is calling this, who made this request and the complexity of the application made it pretty hard to find out.

The best part was, even though default.aspx was being requested, it would render the page i had initially requested and render it correctly. I was freaking out! I just couldnt find a logical explaination for this and it took a while of eliminating pieces of code one by one and isolating every bit to eventually end up to the the image spacer line. I dont rem now if it were just a mere coincidence that i happened to suspect the image spacer element with the dynamic src, just luck maybe.

I dont remember.

But that was it. Basically someone had introduced a piece of code like this :
<img height="85" width="1" src="<%# Globals.GetSkinPath() + "/images/spacer.gif"%>">

This piece of code was supposed to pass a dynamic path for the skin path, which was
returning an empty string. The reason being that Page.DataBind was never called.

Changing that piece of code to simply the following resolved :
<img height="85" width="1" src="<%= Globals.GetSkinPath() + "/images/spacer.gif"%>">

note the abscence of the databinder symbol# ; code like this was all over the
application, which meant it would keep calling and re-executing default.aspx
everytime it found this image spacer that resulted with a src to an empty string.
Pretty expensive also because default.aspx was a pretty heavy page and almost difficult, if not impossible to notice. A couple of these spacers on each page and a call for default.aspx for each of these spacers and your application performance can be very very slow :-)

So lesson to learn, if you find extra requests being made and the requested file is for default.aspx, then you know this request is being made for a file on your webserver with an empty string for the path, which will result in looking for the default.aspx file in your current page folder. So what it will do is look for default.aspx.

so, why did src with an empty string resolves to Default.aspx. This is because IIS by default has "Enable default content page" set and the default page is as you maybe have guessed "Default.aspx" for an asp.net application. How beautiful, i spend a full evening debugging this :-)


Here is a simple test you can all try to test this behaviour :

This is page test.aspx :
-------------------------------------
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
string DynamicSrc = "emoticons.gif";
protected void Page_Load(object sender, EventArgs e)
{
// what if DynamicSrc resolved to string.Emtpy
DynamicSrc = string.Empty;
}
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>

</head>
<body>
<form id="form1" runat="server">
<div>
<img src="<%= DynamicSrc %>" style="height:5px;width:5px" />

</div>
</form>
</body>
</html>


code for the httpmodule, so we can test and verify unexpected behaviour :
--------------------------------------------------------------------------
using System;

using System.Web;
using System.Web.UI;

namespace Test
{
/// <summary>
/// Summary description for TesterHttpModule
/// </summary>

public class SimpleHttpModule : IHttpModule
{
public SimpleHttpModule()
{
//
// TODO: Add constructor logic here
//
}

/// <summary>
/// Initializes the HttpModule and performs the wireup of all application
/// events.
/// </summary>
/// <param name="application">Application the module is being run for</param>
public void Init(HttpApplication application)
{
// Wire-up application events
//
application.BeginRequest += 
                   new EventHandler(this.Application_BeginRequest);
application.AcquireRequestState += 
            new EventHandler(Application_AquireRequestState);
}

private void Application_BeginRequest(Object source, EventArgs e)
{
// do nothing. Just to see this event executing in debug mode
}
private void Application_AquireRequestState(object source, EventArgs e)
{
// Is this request made by the page
Page page1 = HttpContext.Current.Handler as Page;
string requestedUrl = HttpContext.Current.Request.Url.AbsolutePath;
System.Diagnostics.Debug.WriteLine("Request made for page: " + requestedUrl);
if (page1 != null)
{
// do nothing
}
}
}
}

2 comments:

  1. Good find and thanks for sharing. I ran into this exact behavior in my app about 6 months ago. Took me a while to figure it out.

    ReplyDelete
  2. This also is a problem if your default.aspx does something like redirect you to a login page. If that login page automatically clears session state (just for saftey) then once you log in to a page, it'll send that other request which ends up logging you out.



    This is an IE bug that I have seen in IE8 and 7. FireFox does NOT have this problem; it's smarter. If src="" then there obviously isn't going to be any image to get!

    ReplyDelete