Category Archives: 6001

IIS/ASP.NET Cookieless Support Not Working As Expected

In one of the environments I work, cookies cannot be used because the pages run inside web browser controls running on a client application and cookies end up being shared by all browsers.

Fortunately, ASP.NET allows us to persist some cookies as part of the URL.

To persist the session state identifier cookie in the URL we just need to add the following configuration:

<configuration>
  <system.web>
    <sessionState cookieless="UseUri" />
  </system.web>
</configuration>



and you’ll get URLs like this:



http://localhost/Cookieless/(S(jcmwek3ja0lvdpbwoacpjirv))/default.aspx




The way IIS and ASP.NET do this is by IIS removing the section between parenthesis after the virtual directory path and adding the AspFilterSessionId HTTP header to the request. Than, ASP.NET picks it up and extracts the cookie.




I wrote this simple page to demonstrate this working:



<%@ Page Language="C#" AutoEventWireup="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <table>
            <tr>
                <td>Raw URL</td>
                <td><%= Request.RawUrl %></td>
            </tr>
            <tr>
                <td>Cookiless Cookies<br />AspFilterSessionId Request HTTP Header</td>
                <td><%= Request.Headers["AspFilterSessionId"] %></td>
            </tr>
            <tr>
                <td>Session ID</td>
                <td><%= Session.SessionID %></td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>



For the above URL, we’ll get a page like this:




Raw URL /Cookieless/default.aspx
Cookiless Cookies
AspFilterSessionId Request HTTP Header
S(jcmwek3ja0lvdpbwoacpjirv)
Session ID jcmwek3ja0lvdpbwoacpjirv




IIS strips these cookies even for serving static content like cascading stylesheets.




You can test this by creating a default theme. You can do this by adding a Default folder under the App_Themes folder and adding a Styles.css file to it:



body
{
    background-color: Yellow;
}
table, tr, td
{
    border: thin solid black;
}



and setting the theme as default using the following configuration:



<configuration> <system.web> <sessionState cookieless="UseUri" /> <pages theme="Default"/> </system.web> </configuration>




And you’ll get a "pretier" page:




Raw URL /Cookieless/default.aspx
Cookiless Cookies
AspFilterSessionId Request HTTP Header
S(jcmwek3ja0lvdpbwoacpjirv)
Session ID jcmwek3ja0lvdpbwoacpjirv




If you have special needs for your session state identifiers, you can implement your own session identifier manager.




But if you want to use cookieless cookies, you only have one way to do it: extend the SessionIDManager class:



public class SessionIdManager : System.Web.SessionState.SessionIDManager
{
    public override string CreateSessionID(System.Web.HttpContext context)
    {
        string id = System.Guid.NewGuid().ToString("B");

        return id;
    }

    public override bool Validate(string id)
    {
        try
        {
            new System.Guid(id);
            return true;
        }
        catch
        {
            return false;
        }
    }
}



and configure the session state module to use it:



<configuration>
  <system.web>
    <sessionState cookieless="UseUri" sessionIDManagerType="SessionIdManager" />
    <pages theme="Default"/>
  </system.web>
</configuration>



And we’ll end up with this nice page:



http://localhost/Cookieless/(S(%7b0861e55a-e29b-4b6f-825b-1e1d4c57f095%7d))/default.aspx




Raw URL /Cookieless/(S({0861e55a-e29b-4b6f-825b-1e1d4c57f095}))/default.aspx
Cookiless Cookies
AspFilterSessionId Request HTTP Header
 
Session ID {0861e55a-e29b-4b6f-825b-1e1d4c57f095}




OOPS! What happened here?




Looks like IIS was unable to transfer the cookies to the appropriate HTTP header but ASP.NET was able to find the requested resource. On the other hand, IIS couldn’t find the http://localhost/Cookieless/(S(%7b0861e55a-e29b-4b6f-825b-1e1d4c57f095%7d))App_Themes/Default/Styles.css.




This happens in these environments:



Operating System IIS ASP.NET
Windows XP Pro SP3 5.1 2.0 SP1, 3.5
Windows Server 2003 R2 6 2.0 SP1, 3.5
Windows Server 2008 7 2.0 SP1, 3.5





Fortunately, in IIS 7 you can have HTTP modules in integrated pipeline mode that are called for every resource requested to IIS.




Your module doesn’t even need to do nothing. It just needs to exist:



public class Module : System.Web.IHttpModule
{
    public void Dispose()
    {
    }

    public void Init(System.Web.HttpApplication context)
    {
    }
}



and be added to the configuration:



<configuration>
  <system.web>
    <sessionState cookieless="UseUri" sessionIDManagerType="SessionIdManager" />
    <pages theme="Default"/>
  </system.web>
  <system.webServer>
    <modules>
      <add name="Module" preCondition="integratedMode" type="Module" />
    </modules>
  </system.webServer>
</configuration>



And our “pretty” page is back:



Raw URL /Cookieless/default.aspx
Cookiless Cookies
AspFilterSessionId Request HTTP Header
S({0861e55a-e29b-4b6f-825b-1e1d4c57f095})
Session ID {0861e55a-e29b-4b6f-825b-1e1d4c57f095}




Is it just me, or there’s something definitely wrong here?




That’s why I opened this bug on Microsoft Connect

Other Ways For Making PathInfo And ASP.NET Themes Work Together

On my last post I wrote about a solution for the problem that arises when we try the use path infos and ASP.NET Themes and Skins together.

Raj Kaimal suggested rewriting all LINK HTML elements URLs to the correct URL as seen from the client. Something like this:

void HttpApplicationPreRequestHandlerExecute(object sender, System.EventArgs e)
{
    var httpApplication = sender as System.Web.HttpApplication;

    var httpContext = httpApplication.Context;

    var page = httpContext.CurrentHandler as System.Web.UI.Page;

    if ((page != null) && !string.IsNullOrEmpty(httpContext.Request.PathInfo))
    {
        page.PreRenderComplete += delegate(object source, System.EventArgs args)
        {
            var p = source as System.Web.UI.Page;

            foreach (System.Web.UI.Control headerControl in p.Header.Controls)
            {
                var link = headerControl as System.Web.UI.HtmlControls.HtmlLink;
                if (link != null)
                {
                    link.Href = p.ResolveUrl(link.Href);
                }
            }
        };
    }
}



With this approach you still have a problem (which mine didn’t solve) with post backs because the rendering of the ACTION of the HTML FORM is also broken.



Israel Aéce suggested the use of the BASE HTML element to re-base relative URLs. Something like this:



void HttpApplicationPreRequestHandlerExecute(object sender, System.EventArgs e)
{
    var httpApplication = sender as System.Web.HttpApplication;

    var httpContext = httpApplication.Context;

    var page = httpContext.CurrentHandler as System.Web.UI.Page;

    if ((page != null) && !string.IsNullOrEmpty(httpContext.Request.PathInfo))
    {
        page.PreRenderComplete += delegate(object source, System.EventArgs args)
        {
            page.Init += delegate(object source, System.EventArgs args)
            {
                var p = source as System.Web.UI.Page;

                var htmlBase = new System.Web.UI.WebControls.WebControl(System.Web.UI.HtmlTextWriterTag.Base);
                htmlBase.Attributes.Add("href", p.Request.Url.GetLeftPart(System.UriPartial.Authority) + p.Request.CurrentExecutionFilePath);
                p.Header.Controls.Add(htmlBase);
            };
        };
    }
}



This seems like the better solution except if your site sits behind several security perimeters and it is not possible to be sure what the domain is as seem from the client side, which was my problem to begin with.



But if you are thinking of calling Server.Execute, Server.TransferRequest or Server.TransferRequest, neither of these two solutions will work.

WCSF 2.0 And IIS7 Integrated Pipeline Mode

While preparing the demos for my session at TechDays Portugal 2008, I’ve noticed that the Web Client Software Factory 2.0 doesn’t work with IIS7 in integrated pipeline mode because it’s trying to access the Request property of the current HTTP Context from the HTTP Application Start “event”, which is not available at this point.


This is an already known issue and you can vote to get it solved.


Meanwhile, there are two ways to work around this:


Changing the Composite Web Application Block


If you are comfortable with having your own build of this block instead of the provided strong named one, you only need to change one statement in the WebConfigModuleInfoStore class (WCSFBlocks-Feb2008\CompositeWeb\Source\CompositeWeb\Services\WebConfigModuleInfoStore.cs, line 105).


Just replace:

configuration =
    WebConfigurationManager.OpenWebConfiguration(context.Request.ApplicationPath + “/” +
                                                 configFilePath.Replace(context.Request.PhysicalApplicationPath, “”));

with:

configuration =
    WebConfigurationManager.OpenWebConfiguration(HttpRuntime.AppDomainAppVirtualPath + “/” +
                                                 configFilePath.Substring(HttpRuntime.AppDomainAppPath.Length));

Changing the application


If you prefer to (or have to) use the provided and strong named version of the Composite Web Application Block, you can always change your application.


Just open the generated global.asax file:

<%@ Application Language=”C#” Inherits=”Microsoft.Practices.CompositeWeb.WebClientApplication” %>

and add:

<script RunAt=”server”>

    private bool initialized;

    protected override void Application_Start(object sender, EventArgs e)
    {
        this.initialized = false;
    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        if (!this.initialized)
        {
            this.initialized = true;

            base.Application_Start(sender, e);
        }
    }

</script>

Are Page Modules Still Useful In IIS7?

With IIS7 a new transfer method is available in the HttpServerUtility class. It’s the TransferRequest method.

What this method is intended to do is behave like the HttpResponse.Redirect method without the penalty of traveling to the client and back.

I said “is intended to behave” because it still doesn’t, like Luís Abreu found out in his first attempt to use this new method. Fortunately, Thomas Marquardt already knows about it and said they will fix it.

This problem that Luís ran into reminds us that there is still too much going on when you call TransferRequest that won’t be if a Page Module is used. So, I guess they are still useful.