|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
Simply drop the files into any web application, include the .aspx file in the project and set it as the Start Page. IntroductionWhat exactly is a cookie anyway? According to Websters Online, a cookie is any one of the following:
As tempting as the other definitions may be, what we're looking at here is the third. A cookie is a small packet of information sent as part of the HTTP Response, stored on the client machine and subsequently sent as part of any HTTP Request to the originating web site. In ASP.NET terms, a page receives a Cookies are generally used for various levels of state management: maybe for keeping a user logged on to a site or for keeping track of the last time a site (or area of a site) was last visited. I recently used cookies to keep track of a tournament signup process, where a team captain might sign up as many as 10 players to a team, one at a time. I was pretty sure that at some point a user's browser might fall over, or either the client or server machine might crash, or a user might click on another link in the menu. I didn't want them to have to start over again, so I stored a Session ID in a cookie and on each signup record in the database. This Session ID is easily retrieved the next time the user comes back to the signup page and I could pick up all the data from the database and save the user a lot of time. Cookies are a very powerful tool in web design but in ASP.NET they can also be the cause of many problems, especially for users of ASP (which processes cookies slightly differently). Nothing here is rocket science but it is only simple once you understand what's going on behind the scenes. Cookie ExpirationThe first thing you need to understand about cookies is this: Cookies carry an expiry date. The second thing you need to understand is this: Expiry dates are the cause of most cookie-related bugs. Every time you set the When a cookie expires, the client no longer sends it to the server, so you need to make sure that the You can set a cookie's If you want the cookie to be permanent then the temptation is to use
Thus if, like me, you subscribe to the "it doesn't have to look pretty on Netscape, as long as it's functional on the latest version" school of thought, always use a date prior to that. A commonly accepted "permanent" cookie expiry date is Disposing of Stale CookiesIf you want to delete the cookie on the client machine, do not use the obvious Again, the temptation is to use This could, of course, be useful if the behaviour was consistent across browsers. Unfortunately that is not the case and trying to use the Internet Explorer functionality will cause the page to fail when viewed in Netscape. Another easy trap to fall into: in theory, you should be able to use If the server machine time is not quite syncronised with the client machine time, it's possible that the client will think the time given is somewhere in the (admittedly near) future. This can cause a bug to show up when uploaded to a live server that wasn't obvious when testing locally. Worse, it could create a situation where a web application works fine when you view it but not when another user accesses from his machine. Both situations are notoriously hard to debug. The safest (and most symmetric) way to delete the cookie by using an Expiry date of Incoming CookiesWhen a page is received, it has a For the Response, on the other hand, there are no cookies when your code begins, they are created as and when you need them. When the server sends back the Response, the client machine only adjusts those Cookies that exist in the In what seems like a bizarre twist of fate, the incoming (Request) cookie carries an This is actually quite easily explained - as many web developers know, it's near impossible to get hold of the expiry date of a cookie once it is written to the client machine (try it in JavaScript). It certainly isn't sent as part of the request. But Microsoft will have wanted Response and Request cookies to be of the same class (HttpCookie). As DateTime is a value object, rather than a reference object, it cannot be Understandable as it is, this is another place we can get caught out if we are not careful. If we want to copy a Request cookie directly to the Response (something we will later see is a useful tool) then we need to create a new expiry date, even if we can safely assume the old date will be okay. The Mysterious Case of the Disappearing CookieIf you try to access a cookie that doesn't exist in the
So if you look at a cookie in the Response then you are indirectly overwriting the cookie on the client machine with an empty cookie, due to expire when the browser closes (or expiring immediately in Netscape). A demonstration will help illustrate the point. Consider a single web page consisting of a label that displays a cookie. Three command buttons each redirect the page to itself, the first sets a cookie, the second clears it and the third does nothing (see image). For clarity, there is also a groove-style border around the label and the label is, by default, filled it with dashes ("-") so we can see exactly what is happening. ie. we don't want to confuse a blank string with simply not having populated the label. <asp:label id="myRequestCookie"
style="Z-INDEX: 101; LEFT: 26px; POSITION: absolute; TOP: 22px"
runat="server" Width="220px" BorderStyle="Groove">
-----------------------------------</asp:label>
<asp:button id="btnCookies.Set"
style="Z-INDEX: 102; LEFT: 26px; POSITION: absolute; TOP: 56px"
runat="server" Width="220px" Text="Set Cookie"></asp:button>
<asp:button id="btnClearCookie"
style="Z-INDEX: 103; LEFT: 26px; POSITION: absolute; TOP: 84px"
runat="server" Width="220px" Text="Clear Cookie"></asp:button>
<asp:Button id="btnDoNothing"
style="Z-INDEX: 104; LEFT: 26px; POSITION: absolute; TOP: 112px"
runat="server" Width="220px" Text="Do Nothing"></asp:Button>
private void Page_Load(object sender, System.EventArgs e)
{
// Display the Request cookie on the page
if (Request.Cookies["TestCookie"] == null)
myRequestCookie.Text = "No cookie found";
else
myRequestCookie.Text = Request.Cookies["TestCookie"].Value;
}
private void btnCookies.Set_Click(object sender, System.EventArgs e)
{
// Set up a cookie and redirect to this page to pick it up for display
Response.Cookies["TestCookie"].Value = "Cookie is set";
Response.Cookies["TestCookie"].Expires = DateTime.Now.AddYears(30);
Response.Redirect("Example5.1.aspx");
}
private void btnClearCookie_Click(object sender, System.EventArgs e)
{
// Expire the cookie and redirect to this page to display a message
Response.Cookies["TestCookie"].Expires = DateTime.Now.AddYears(-30);
Response.Redirect("Example5.1.aspx");
}
private void btnDoNothing_Click(object sender, System.EventArgs e)
{
// Do absolutely nothing except redirect to simulate moving to another page
Response.Redirect("Example5.1.aspx");
}
What you would expect this page to do is always display the latest cookie status: set or null. When the DoNothing button is pressed you would expect the status to remain the same. Well, guess what. That is exactly what it does do. However...
If you now place a breakpoint on the first line of the Page_Load event handler and add a debugger watch for When you set the cookie, it appears to be set, when you clear it or do nothing (regardless of current state), you get an empty string (see image). This is because the debugger is creating an empty cookie just by looking to see if one is there; this new blank cookie will hang around for as long as the browser is open and then expire. This blank cookie is what now appears in your label. When you hit the "Set Cookie" button, the first blank response cookie is overriden by a valid (and non-expired) one, so when it returns there is a request cookie which is not automatically overwritten when you break. But when the Response comes back with the label populated correctly, it also has a rogue blank-cookie which immediately expires, so even then the page hasn't worked correctly even though it appears to at first. This can be extremely dangerous if you are debugging one page which sets the Cookie and then leave the watch visible while debugging another page which doesn't. Real-world problemsNow that we know exactly what is happening, we can predict potential problems and easily fix them. The most common predicament is likely to be the case where you want to update a cookie conditionally in one part of the code and then get the "current" value later. Consider the following code carefully: private void Page_Load(object sender, System.EventArgs e)
{
// ...
// Set cookie only under given situation
if (myCondition)
{
Response.Cookies["MyCookie"].Value = myValue;
Response.Cookies["MyCookie"].Expires = DateTime.Now.AddYears(30);
}
// ...
}
private void MyButton_OnClick(object sender, System.EventArgs e)
{
// ...
// If there's an updated cookie then get it, otherwise get the old one
if (Response.Cookies["MyCookie"] == null)
currentCookieValue = Request.Cookies["MyCookie"].Value;
else
currentCookieValue = Response.Cookies["MyCookie"].Value;
// ...
}
As you can guess, because you've just seen the potential problems brutally demonstrated, this code isn't going to work as the developer clearly wants it to. The very act of checking the cookie for This can be inherently difficult to debug because the two pieces of code will probably not be this close together. We've already seen what can happen if you put a Watch on a response cookie so that approach is best avoided and to confuse the developer completely the cookie will expire immediately so it will not be present in the next request. Prevention is Better Than CureThe possible solutions here are legion, the most obvious would be to change the second condition to Remove the rogue cookie where it is created. private void MyButton_OnClick(object sender, System.EventArgs e)
{
// ...
// If there's an updated cookie then get it, otherwise get the old one
if (Response.Cookies["MyCookie"].Value == ""
&& Response.Cookies["MyCookie"].Expires == DateTime.MinValue)
{
currentCookieValue = Request.Cookies["MyCookie"].Value;
Response.Cookies.Remove("MyCookie");
}
else
{
currentCookieValue = Response.Cookies["MyCookie"].Value;
}
// ...
}
Of course, if you have to duplicate this code many times, it is not going to be long before this solution gets unwieldy A much cleaner solution to the same problem is to make sure that every page likely to update a cookie starts with a copy from the private void Page_Load(object sender, System.EventArgs e)
{
// Ensure preservation of cookie
if (Request.Cookies["MyCookie"] != null)
Response.Cookies.Set(Request.Cookies["MyCookie"]);
else
Response.Cookies.Set(new HttpCookie("MyCookie", "DefaultValue"));
// The Request Cookie doesn't include expiry date, so you need to add one
// in either case
Response.Cookies["MyCookie"].Expires = DateTime.Now.AddYears(30);
// ...
// Change cookie value only under given situation
if (myCondition)
Response.Cookies["MyCookie"].Value = myValue;
// ...
}
private void MyButton_OnClick(object sender, System.EventArgs e)
{
// ...
// Response.Cookies will always hold the current value
currentCookieValue = Response.Cookies["MyCookie"].Value;
// ...
}
The one downside of this is that it does create excessive bandwidth usage; you might be sending back a cookie containing the same detail that the client sent. If it is a single small cookie then this is not a serious problem. The odds are high that anything you send back in the cookie collection will be insignificant when compared to the page itself. If you do think that bandwidth will be a problem, or if you just want to be super-efficient, the best thing to do is to remove cookies that will not update the original before sending the protected override void OnPreRender(System.EventArgs e)
{
// Remember that if the request cookie was null, it
// is created by looking at the response cookie
if (Response.Cookies["TestCookie"].Value == Request.Cookies["TestCookie"].Value)
Response.Cookies.Remove("TestCookie");
base.OnPreRender(e);
}
Try applying this technique to the "missing cookie" example, remembering that you should never rely on a Always remember to remove the watch when you have finished debugging that page. If the watch is still active when editing a page that may not even look at that specific cookie, you may end up blanking the cookie. ConclusionAs with most things .NET, the potential problems are both severe and little documented, but the solutions are simple once you have implemented them a couple of times. Three simple rules:
Remember: Take care of your cookies and they'll be a good friend, play rough and they'll bite you. Revision History01-Jan-03
| ||||||||||||||||||||