Tuesday, April 21, 2009

ASP.Net HTTPRequest.Cookies and HTTPResponse.Cookies Analyzed

One thing that confuses many new ASP.net developers (and even well seasoned ones too!) is understanding the difference between Request.Cookies and Response.Cookies. It is very easy to dismiss this as a simple answer. Request.Cookies are the cookies that the browser sent to IIS and the Response.Cookies are what will be sent back to the browser. That is all fine and dandy, right?

Most developers would make an assumption the HttpRequest.Cookies is a static point in time capture of what was sent from the browser. So then you go and add a cookie to the Response.Cookies collection and bam what happens? The Request.Cookies collection has changed! What? It was static right? And that right there is what makes it not so black and white. And the MSDN documentation fails to mention this any where.

So here are some conclusions that I have made using Redgate's Reflector product to look at the System.Web assembly.

HttpRequest.Cookies Get Property
- Looks at private member _cookies and returns it if not nothing
- HttpCookies collection created with Friend (Internal) constructor that takes HttpResponse and a read only flag which a set to Nothing and True, respectively. A function is called to populate _cookies from the HTTP headers and the collection is returned.

HttpResponse.Cookies Get Property
-- Looks at private member _cookies and returns it if not nothing
-- HttpCookies collection created with Friend (Internal) constructor that takes HttpResponse and a read only flag which are set to current Response and False, respectively. Empty collection is returned

HttpRequest.Cookies("NonexistentCookieName") returns nothing because the HttpCookies collection internal member _response is nothing.

HttpResponse.Cookies("NewCookieName") creates a new Cookie because response is not nothing. i.e., this is why any key sent through HttpResponse gets a new cookie object.

So there a two separate HTTPCookie collections. HTTPCookie collection is flagged to know if it is a collection of Request cookies or Response Cookies. Simple and pretty standard so far. Now, here is the twist.

HttpResponse.Cookies("NewCookieName") not only creates a new cookie but also calls Me._response.OnCookieAdd(cookie).

HttpResponse.OnCookieAdd calls Me.Request.AddResponseCookie(cookie).

Wait a second! Adding a cookie to the Response collection also adds the same cookie object to the HttpRequest.Cookies collection? But, HttpRequest is the static read-only version sent from the browser. Right? That is what I thought and many others. And that is why there are so many questions on cookies in the forums. People are not just stupid knuckle heads asking the same dumb question over and over. This doesn't make sense!

And here is another thing....
Let's say you already have a cookie and you want to modify the value.

HttpResponse.Cookies("ExistingCookieName").Value = "NewValue"

Guess what? HttpRequest.Cookies now has two entries for "ExistingCookieName". One with the old value and one with the new value.

For the life of me, I cannot figure out why the Request.Cookies collection is changed and/or what benefit doing so provides.

I could understand that when a brand new cookie is added, other code in the same response call can also find the value in the Request. But why not the same when a cookie is modified, why have two conflicting values in Request.Cookies?

Also note that HttpResponse.AddCookie and HttpResponse.SetCookie are marked as infrastructure in MSDN so they should not be used. But for the record AddCookie calls the Me.OnCookieAdd(cookie) which adds the Cookie to the HttpRequest.Cookies collection, however SetCookie does not.

So in conclusion the Description for HttpRequest.Cookies "Gets a collection of cookies sent by the client." is a lie. It is updated by changes to the HttpResponse.Cookies collection and I have proof by looking at the source code. I will continue my mission to find out why....... There must be some logical answer out there somewhere....

P.S. Here is a method that I wrote that somewhat alleviates the issue.

    Public Sub SetCookieValue(ByVal cookieName As String, ByVal cookieValue As String)
        'Try to get existing cookie if it already exists.
        Dim c As HttpCookie = HttpContext.Current.Request.Cookies(cookieName)
        If c Is Nothing Then
            'Cookie did not already exist so create a new one
            HttpContext.Current.Response.Cookies(cookieName).Value = cookieValue
            'The above call also adds the cookie to the Request.Cookies collection.
        Else
            'Cookie already exists in Request collection, so use it.
            c.Value = cookieValue
            HttpContext.Current.Response.Cookies.Add(c)
            'There will be two entrees in Request.Cookies, but they will point to same object.
        End If
    End Sub
kick it on DotNetKicks.com