Sei sulla pagina 1di 26

ASP.

NET Cookies Overview


Cookies are associated with a Web site, not with a specific page, so the browser and server will
exchange cookie information no matter what page the user requests from your site. As the user
visits different sites, each site might send a cookie to the user's browser; the browser stores all the
cookies separately.

Cookie Limitations
Most browsers support cookies of up to 4096 bytes. THerefore, cookies are best used to store small
amounts of data, or even better,only an identifier such as a user ID. This user ID can then be used
to identify the user and read user information from a database or other data store. In the case of
Forms Authentication, the Forms cookie can store its own expiration time, as well as custom
UserData (roles, preferences, etc.) This can eliminate the need to use Session to store small
amounts of user-specific data. Forms auth cookies are normally encrypted. Cookie data can be
compressed to allow storage of entire classes in .Net.

Browsers impose limitations on how many cookies your site can store on the user's computer. Most
browsers allow only 20 cookies per site; if you try to store more, the oldest cookies are discarded.
Some browsers also put an absolute limit, usually 300, on the number of cookies they will accept
from all sites combined.

A cookie limitation that you might encounter is that users can set their browser to refuse cookies. If
you define a P3P privacy policy and place it in the root of your Web site, more browsers will accept
cookies from your site. Instead of creating and uploading privacy policies to your sites, you can also
serve a “compact policy,” i.e. a “p3p” HTTP header, e.g.: “IDC DSP COR ADM DEVi TAIi PSA PSD IVAi
IVDi CONi HIS OUR IND CNT”. A policy generator can produce one instead of an XML file. In
ASP.NET it’s a one-liner that you can put into your page base class or master page:

HttpContext.Current.Response.AddHeader ("p3p","CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi
IVDi CONi HIS OUR IND CNT\"");

If you do so programmatically, just make sure to add this line early in the page life cycle. If your
code redirects or throws an exception too early, the “p3p” header will be missing. As an alternative,
you can get IIS to send this header at all times, but in this case the header will appear on
everything: images, stylesheets, JavaScript files, etc. Those files don’t really need it.

You can review the official P3P Policy specification here: http://www.w3.org/P3P/usep3p.html

Although cookies can be very useful in your application, you should try to avoid having the
application depend on being able to store cookies. Do not use cookies to support critical features. If
your application must rely on cookies, you can test to see whether the browser will accept cookies.
ASP.NET Session relies on cookies. With ASP.NET, we have the ability to configure the Session as
"cookieless" where the SessionId is "munged" onto the URL. You can also set this to "auto" and the
runtime will determine which mode to use.

Writing Cookies
The browser manages cookies on a user system. Cookies are sent to the browser via the
HttpResponse object that exposes a collection called Cookies. You can access the HttpResponse
object as the Response property of your Page class. Any cookies that you want to send to the
browser must be added to this collection. When creating a cookie, you specify a Name and Value.
Each cookie must have a unique name so that it can be identified later when reading it from the
browser. Because cookies are stored by name, naming two cookies the same will cause one to be
overwritten.
You can also set a cookie's date and time expiration. Expired cookies are deleted by the browser
when a user visits the site that wrote the cookies. The expiration of a cookie should be set for as
long as your application considers the cookie value to be valid. For a cookie to effectively never
expire, you can set the expiration date to be 50 years from now.

Note: Users can clear the cookies on their computer at any time. Utilities such as CCleaner ("crap
cleaner") allow you to set the cookies you want to keep, and delete all the rest.

If you do not set the cookie's expiration, the cookie is created but it is not stored on the user's hard
disk. Instead, the cookie is maintained in memory as part of the user's session information. When
the user closes the browser, the cookie is discarded. A non-persistent cookie like this is useful for
information that needs to be stored for only a short time or that for security reasons should not be
written to disk on the client computer. For example, non-persistent cookies are useful if the user is
working on a public computer, where you do not want to write the cookie to disk.

ASP.NET 2.0 "HttpOnly" Cookies and fix:


Internet Explorer 6 SP1 and higher supports an extra "HttpOnly" cookie attribute that prevents
client-side script from accessing the cookie via the document.cookie property. Cookies still round
trip.

In ASP.NET 1.1, you can add this to the Global.asax and catch all the cookies on the way out. You
could choose to do this to specific cookies if you like.

protected void Application_EndRequest(Object sender, EventArgs e)


{
foreach(string cookie in Response.Cookies)
{
const string HTTPONLY = ";HttpOnly";
string path = Response.Cookies[cookie].Path;
if (path.EndsWith(HTTPONLY) == false)
{
//force HttpOnly to be added to the cookie
Response.Cookies[cookie].Path += HTTPONLY;
}
}
}

ASP.NET 2.0 can do all this for you via a Web.config setting.

GOTCHA: If you do this in your ASP.NET 1.1 app and then run your 1.1 app under 2.0 without
changes, be aware that ASP.NET 2.0 will append ANOTHER HttpOnly after every cookie giving you
the value TWICE. You'll then need to turn if off in web.config as your code would be handling it.

<httpCookies httpOnlyCookies="false" requireSSL="false" domain="" />

You can add cookies to the Cookies collection in a number of ways. The following example shows
two methods to write cookies:

Response.Cookies["userName"].Value = "patrick";
Response.Cookies["userName"].Expires = DateTime.Now.AddDays(1);
// the above creates the cookie, or overwrites it if the cookie already exists.

HttpCookie aCookie = new HttpCookie("lastVisit");


aCookie.Value = DateTime.Now.ToString();
aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);
The example adds two cookies to the Cookies collection, one named userName and the other named
lastVisit. For the first cookie, the values of the Cookies collection are set directly. You can add values
to the collection this way because Cookies derives from a specialized collection of type
NameObjectCollectionBase.

For the second cookie, the code creates an instance of an object of type HttpCookie, sets its
properties, and then adds it to the Cookies collection via the Add method. When you instantiate an
HttpCookie object, you must pass the cookie name as part of the constructor.

Both examples accomplish the same task, writing a cookie to the browser. In both methods, the
expiration value must be of type DateTime. However, the lastVisited value is also a date-time value.
Because all cookie values are stored as strings, the date-time value has to be converted to a String
.

Cookies with More Than One Value


You can store one value in a cookie, such as user name or last visit. You can also store multiple
name-value pairs in a single cookie. The name-value pairs are referred to as subkeys. For example,
instead of creating two separate cookies named userName and lastVisit, you can create a single
cookie named userInfo that has the subkeys userName and lastVisit.

You might use subkeys for several reasons. First, it is convenient to put related or similar
information into a single cookie. In addition, because all the information is in a single cookie, cookie
attributes such as expiration apply to all the information.

A cookie with subkeys also helps you limit the size of cookie files. As noted earlier in the "Cookie
Limitations" section, cookies are usually limited to 4096 bytes and you can't store more than 20
cookies per site. By using a single cookie with subkeys, you use fewer of those 20 cookies that your
site is allotted. In addition, a single cookie takes up about 50 characters for overhead (expiration
information, and so on), plus the length of the value that you store in it, all of which counts toward
the 4096-byte limit. If you store five subkeys instead of five separate cookies, you save the
overhead of the separate cookies and can save around 200 bytes.

To create a cookie with subkeys, you can use a variation of the syntax for writing a single cookie.
The following example shows two ways to write the same cookie, each with two subkeys:

Response.Cookies["userInfo"]["userName"] = "patrick";
Response.Cookies["userInfo"]["lastVisit"] = DateTime.Now.ToString();
Response.Cookies["userInfo"].Expires = DateTime.Now.AddDays(1);

HttpCookie aCookie = new HttpCookie("userInfo");


aCookie.Values["userName"] = "patrick";
aCookie.Values["lastVisit"] = DateTime.Now.ToString();
aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);

Cookie Scope
By default, all cookies for a site are stored together on the client, and all cookies are sent to the
server with any request to that site. In other words, every page in a site gets all of the cookies for
that site. However, you can set the scope of cookies in two ways:

Limit the scope of cookies to a folder on the server, which allows you to limit cookies to an
application on the site.
Set scope to a domain, which allows you to specify which subdomains in a domain can access a
cookie.

Limiting Cookies to a Folder or Application


To limit cookies to a folder on the server, set the cookie's Path property, as in the following example:

HttpCookie appCookie = new HttpCookie("AppCookie");


appCookie.Value = "written " + DateTime.Now.ToString();
appCookie.Expires = DateTime.Now.AddDays(1);
appCookie.Path = "/Application1";
Response.Cookies.Add(appCookie);

The path can either be a physical path under the site root or a virtual root. The effect will be that
the cookie is available only to pages in the Application1 folder or virtual root. For example, if your
site is called www.mysite.com, the cookie created in the previous example will be available to pages
with the path http://www.mysite.com/Application1/ and to any pages beneath that folder. However,
the cookie will not be available to pages in other applications such as
http://www.mysite.com/Application2/ or just http://www.mysite.com/.

Note
In some browsers, the path is case sensitive. You cannot control how users type URLs into their
browsers, but if your application depends on cookies tied to a specific path, be sure that the URLs in
any hyperlinks you create match the case of the Path property value.
Limiting Cookie Domain Scope

By default, cookies are associated with a specific domain. For example, if your site is
www.mysite.com, the cookies you write are sent to the server when users request any page from
that site. (This might not include cookies with a specific path value.) If your site has subdomains—
for example, mysite.com, sales.mysite.com, and support.mysite.com—then you can associate
cookies with a specific subdomain. To do so, set the cookie's Domain property, as in this example:

Response.Cookies["domain"].Value = DateTime.Now.ToString();
Response.Cookies["domain"].Expires = DateTime.Now.AddDays(1);
Response.Cookies["domain"].Domain = "support.mysite.com";

When the domain is set in this way, the cookie will be available only to pages in the specified
subdomain. You can also use the Domain property to create a cookie that can be shared among
multiple subdomains, as shown in the following example:

Response.Cookies["domain"].Value = DateTime.Now.ToString();
Response.Cookies["domain"].Expires = DateTime.Now.AddDays(1);
Response.Cookies["domain"].Domain = "mysite.com";
The cookie will then be available to the primary domain as well as to sales.mysite.com and
support.mysite.com domains.

Reading Cookies
When a browser makes a request to the server, it sends the cookies for that server along with the
request. In your ASP.NET applications, you can read the cookies using the HttpRequest object,
which is available as the Request property of your Page class. The structure of the HttpRequest
object is essentially the same as that of the HttpResponse object, so you can read cookies out of the
HttpRequest object much the same way you wrote cookies into the HttpResponse object. The
following code example shows two ways to get the value of a cookie named username and display
its value in a Label control
if(Request.Cookies["userName"] != null)
Label1.Text = Server.HtmlEncode(Request.Cookies["userName"].Value);

if(Request.Cookies["userName"] != null)
{
HttpCookie aCookie = Request.Cookies["userName"];
Label1.Text = Server.HtmlEncode(aCookie.Value);
}

Before trying to get the value of a cookie, you should make sure that the cookie exists; if the cookie
does not exist, you will get a NullReferenceException exception. Notice also that the HtmlEncode
method was called to encode the contents of a cookie before displaying it in the page. This makes
certain that a malicious user has not added executable script into the cookie.

Note
Because different browsers store cookies differently, different browsers on the same computer won't
necessarily be able to read each other's cookies. For example, if you use Internet Explorer to test a
page one time, but then later use a different browser to test again, the second browser won't find
the cookies saved by Internet Explorer.

Reading the value of a subkey in a cookie is likewise similar to setting it. The following code
example shows one way to get the value of a subkey
if(Request.Cookies["userInfo"] != null)
{
Label1.Text =
Server.HtmlEncode(Request.Cookies["userInfo"]["userName"]);

Label2.Text =
Server.HtmlEncode(Request.Cookies["userInfo"]["lastVisit"]);
}

In the preceding example, the code reads the value of the subkey lastVisit, which was set earlier to
the string representation of a DateTime value. Cookies store values as strings, so if you want to use
the lastVisit value as a date, you have to convert it to the appropriate type, as in this example:

DateTime dt;
dt = DateTime.Parse(Request.Cookies["userInfo"]["lastVisit"]);

The subkeys in a cookie are typed as a collection of type NameValueCollection. Therefore, another
way to get an individual subkey is to get the subkeys collection and then extract the subkey value
by name, as shown in the following example:

if(Request.Cookies["userInfo"] != null)
{
System.Collections.Specialized.NameValueCollection
UserInfoCookieCollection;
UserInfoCookieCollection = Request.Cookies["userInfo"].Values;
Label1.Text =
Server.HtmlEncode(UserInfoCookieCollection["userName"]);
Label2.Text =
Server.HtmlEncode(UserInfoCookieCollection["lastVisit"]);
}

Changing a Cookie's Expiration Date


The browser is responsible for managing cookies, and the cookie's expiration time and date help the
browser manage its store of cookies. Therefore, although you can read the name and value of a
cookie, you cannot read the cookie's expiration date and time. When the browser sends cookie
information to the server, the browser does not include the expiration information. (The cookie's
Expires property always returns a date-time value of zero.) If you are concerned about the
expiration date of a cookie, you must reset it.

Note
You can read the Expires property of a cookie that you have set in the HttpResponse object, before
the cookie has been sent to the browser. However, you cannot get the expiration back in the
HttpRequest object.
Reading Cookie Collections
You might occasionally need to read through all the cookies available to the page. To read the
names and values of all the cookies available to the page, you can loop through the Cookies
collection using code such as the following.

System.Text.StringBuilder output = new System.Text.StringBuilder();


HttpCookie aCookie;
for(int i=0; i<Request.Cookies.Count; i++)
{
aCookie = Request.Cookies[i];
output.Append("Cookie name = " + Server.HtmlEncode(aCookie.Name)
+ "<br />");
output.Append("Cookie value = " + Server.HtmlEncode(aCookie.Value)
+ "<br /><br />");
}
Label1.Text = output.ToString();

ASP.NET_SessionId Cookie

ASP.NET_SessionId is a cookie that ASP.NET uses to store a unique identifier for your session. The
session cookie is not persisted on your hard disk.

A limitation of the preceding example is that if the cookie has subkeys, the display shows the
subkeys as a single name/value string. You can read a cookie's HasKeys property to determine
whether the cookie has subkeys. If so, you can read the subkey collection to get individual subkey
names and values. You can read subkey values from the Values collection directly by index value.
The corresponding subkey names are available in the AllKeys member of the Values collection,
which returns an array of strings. You can also use the Keys member of the Values collection.
However, the AllKeys property is cached the first time it is accessed. In contrast, the Keys property
builds an array each time it is accessed. For this reason, the AllKeys property is much faster on
subsequent accesses within the context of the same page request.

The following example shows a modification of the preceding example. It uses the HasKeys property
to test for subkeys, and if subkeys are detected, the example gets subkeys from the Values
collection:

for(int i=0; i<Request.Cookies.Count; i++)


{
aCookie = Request.Cookies[i];
output.Append("Name = " + aCookie.Name + "<br />");
if(aCookie.HasKeys)
{
for(int j=0; j<aCookie.Values.Count; j++)
{
subkeyName = Server.HtmlEncode(aCookie.Values.AllKeys[j]);
subkeyValue = Server.HtmlEncode(aCookie.Values[j]);
output.Append("Subkey name = " + subkeyName + "<br />");
output.Append("Subkey value = " + subkeyValue +
"<br /><br />");
}
}
else
{
output.Append("Value = " + Server.HtmlEncode(aCookie.Value) +
"<br /><br />");
}
}
Label1.Text = output.ToString();

Alternatively, you can extract the subkeys as a NameValueCollection object as shown in the
following example:

System.Text.StringBuilder output = new System.Text.StringBuilder();


HttpCookie aCookie;
string subkeyName;
string subkeyValue;
for (int i = 0; i < Request.Cookies.Count; i++)
{
aCookie = Request.Cookies[i];
output.Append("Name = " + aCookie.Name + "<br />");
if (aCookie.HasKeys)
{
System.Collections.Specialized.NameValueCollection CookieValues =
aCookie.Values;
string[] CookieValueNames = CookieValues.AllKeys;
for (int j = 0; j < CookieValues.Count; j++)
{
subkeyName = Server.HtmlEncode(CookieValueNames[j]);
subkeyValue = Server.HtmlEncode(CookieValues[j]);
output.Append("Subkey name = " + subkeyName + "<br />");
output.Append("Subkey value = " + subkeyValue +
"<br /><br />");
}
}
else
{
output.Append("Value = " + Server.HtmlEncode(aCookie.Value) +
"<br /><br />");
}
}
Label1.Text = output.ToString();

Modifying and Deleting Cookies


You cannot directly modify a cookie. Instead, changing a cookie consists of creating a new cookie
with new values and then sending the cookie to the browser to overwrite the old version on the
client. The following code example shows how you can change the value of a cookie that stores a
count of the user's visits to the site:

int counter;
if (Request.Cookies["counter"] == null)
counter = 0;
else
{
counter = int.Parse(Request.Cookies["counter"].Value);
}
counter++;

Response.Cookies["counter"].Value = counter.ToString();
Response.Cookies["counter"].Expires = DateTime.Now.AddDays(1);

Deleting Cookies
Deleting a cookie—physically removing it from the user's hard disk—is a variation on modifying it.
You cannot directly remove a cookie because the cookie is on the user's computer. However, you can
have the browser delete the cookie for you. The technique is to create a new cookie with the same
name as the cookie to be deleted, but to set the cookie's expiration to a date earlier than today.
When the browser checks the cookie's expiration, the browser will discard the now-outdated cookie.
The following code example shows one way to delete all the cookies available to the application:

HttpCookie aCookie;
string cookieName;
int limit = Request.Cookies.Count;
for (int i=0; i<limit; i++)
{
cookieName = Request.Cookies[i].Name;
aCookie = new HttpCookie(cookieName);
aCookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(aCookie);
}

Modifying or Deleting Subkeys


Modifying an individual subkey is the same as creating it, as shown in the following example:

VB.NET:
Response.Cookies("userInfo")("lastVisit") = DateTime.Now.ToString()
Response.Cookies("userInfo").Expires = DateTime.Now.AddDays(1)

C#
Response.Cookies["userInfo"]["lastVisit"] = DateTime.Now.ToString();
Response.Cookies["userInfo"].Expires = DateTime.Now.AddDays(1);

To delete an individual subkey, you manipulate the cookie's Values collection, which holds the
subkeys. You first recreate the cookie by getting it from the Cookies object. You can then call the
Remove method of the Values collection, passing to the Remove method the name of the subkey to
delete. You then add the cookie to the Cookies collection so it will be sent in its modified form back
to the browser. The following code example shows how to delete a subkey. In the sample, the name
of the subkey to remove is specified in a variable.

string subkeyName;
subkeyName = "userName";
HttpCookie aCookie = Request.Cookies["userInfo"];
aCookie.Values.Remove(subkeyName);
aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);

Cookies and Security


The security issues with cookies are similar to those of getting data from the client. In your
application, cookies are another form of user input and are therefore subject to examining and
spoofing. A user can at a minimum see the data that you store in a cookie, since the cookie is
available on the user's own computer. The user can also change the cookie before the browser
sends it to you.

You should never store sensitive data in a cookie, such as user names, passwords, credit card
numbers, and so on. Do not put anything in a cookie that should not be in the hands of a user or of
someone who might somehow steal the cookie. Store instead, the ID of this data, and look it up
when needed.
Similarly, be suspicious of information you get out of a cookie. Do not assume that the data is the
same as when you wrote it out; use the same safeguards in working with cookie values that you
would with data that a user has typed into a Web page. The examples earlier in this topic showed
HTML- encoding the contents of a cookie before displaying the value in a page, as you would before
displaying any information you get from users.

Cookies are sent between browser and server as plain text, and anyone who can intercept your Web
traffic can read the cookie. You can set a cookie property that causes the cookie to be transmitted
only if the connection uses the Secure Sockets Layer (SSL). SSL does not protect the cookie from
being read or manipulated while it is on the user's computer, but it does prevent the cookie from
being read while in transit because the cookie is encrypted. Fiddler is a nice add-on to examine
HTTP traffic.

Determining Whether a Browser Accepts Cookies


Users can set their browser to refuse cookies. No error is raised if a cookie cannot be written. The
browser likewise does not send any information to the server about its current cookie settings.

Note
The Cookies property does not indicate whether cookies are enabled. It indicates only whether the
current browser inherently supports cookies.

One way to determine whether cookies are accepted is by trying to write a cookie and then trying to
read it back again. If you cannot read the cookie you wrote, you assume that cookies are turned off
in the browser.

The following code example shows how you might test whether cookies are accepted. The sample
consists of two pages. The first page writes out a cookie, and then redirects the browser to the
second page. The second page tries to read the cookie. It in turn redirects the browser back to the
first page, adding to the URL a query string variable with the results of the test.

The code for the first page looks like this:

protected void Page_Load(object sender, EventArgs e)


{
if (!Page.IsPostBack)
{
if (Request.QueryString["AcceptsCookies"] == null)
{
Response.Cookies["TestCookie"].Value = "ok";
Response.Cookies["TestCookie"].Expires =
DateTime.Now.AddMinutes(1);
Response.Redirect("TestForCookies.aspx?redirect=" +
Server.UrlEncode(Request.Url.ToString()));
}
else
{
Label1.Text = "Accept cookies = " +
Server.UrlEncode(
Request.QueryString["AcceptsCookies"]);
}
}
}

The page first tests to see if this is a postback, and if not, the page looks for the query string
variable name AcceptsCookies that contains the test results. If there is no query string variable, the
test has not been completed, so the code writes out a cookie named TestCookie. After writing out
the cookie, the sample calls Redirect to transfer to the test page TestForCookies.aspx. Appended to
the URL of the test page is a query string variable named redirect containing the URL of the current
page; this will allow you to redirect back to this page after performing the test.

The test page can consist entirely of code; it does not need to contain controls. The following code
example illustrates the test page.

protected void Page_Load(object sender, EventArgs e)


{
string redirect = Request.QueryString["redirect"];
string acceptsCookies;
if(Request.Cookies["TestCookie"] ==null)
acceptsCookies = "no";
else
{
acceptsCookies = "yes";
// Delete test cookie.
Response.Cookies["TestCookie"].Expires =
DateTime.Now.AddDays(-1);
}
Response.Redirect(redirect + "?AcceptsCookies=" + acceptsCookies,
true);
}

After reading the redirect query string variable, the code tries to read the cookie. For housekeeping
purposes, if the cookie exists, it is immediately deleted. When the test is finished, the code
constructs a new URL from the URL passed to it in the redirect query string variable. The new URL
also includes a query string variable containing test results. The final step is to use the new URL to
redirect the browser to the original page.

An improvement in the example would be to keep the cookie test results in a persistent store such
as a database so that the test does not have to be repeated each time the user views the original
page. (Storing the test results in session state by default requires cookies.)

Cookies and Session State


When a user navigates to your site, the server establishes a unique session for that user that lasts
for the duration of the user's visit. For each session, ASP.NET maintains session state information
where applications can store user-specific information. For more information, see Session State
Overview topic.

ASP.NET must track a session ID for each user so that it can map the user to session state
information on the server. By default, ASP.NET uses a non-persistent cookie to store the session
state. However, if a user has disabled cookies on the browser, session state information cannot be
stored in a cookie.

ASP.NET offers an alternative in the form of cookieless sessions. You can configure your application
to store session IDs not in a cookie, but in the URLs of pages in your site. If your application relies
on session state, you might consider configuring it to use cookieless sessions. However, under some
limited circumstances, if the user shares the URL with someone else—perhaps to send the URL to a
colleague while the user's session is still active—then both users can end up sharing the same
session, with unpredictable results. For more information on configuring your application to use
cookieless sessions, see the ASP.NET State Management Overview topic.
Every time you set the Value of a cookie, remember also to set the Expires date. If you fail to do
this you will quickly find yourself losing Cookies owing to them having expired immediately when
updating them on the client machine or when the browser closes.

When a cookie expires, the client no longer sends it to the server, so you need to make sure that
the Expires property of the cookie is always in the future. If you just set a cookie's value then it will
create a cookie with Expires set to DateTime.MinValue (01-Jan-0001 00:00).

You can set a cookie's Expires property using any DateTime value (a positive relief after ASP). For
example, if you want a Cookie to expire after the user has not been to that part of your site for a
week, you would set Expires = DateTime.Now.AddDays(7).

If you want the cookie to be permanent then the temptation is to use DateTime.MaxValue. However,
there is a simple gotcha here.

DateTime.MaxValue is precisely 31-Dec-9999 25:59:59.9999999. But Netscape (for example) , even


at version 7, doesn't cope with that value and expires the cookie immediately.

A commonly accepted "permanent" cookie expiry date is DateTime.Now.AddYears(30), ie. 30 years


into the future.
Disposing of Stale Cookies

If you want to delete the cookie on the client machine, do not use the obvious
Response.Cookies.Remove("MyCookie") which simply tells the cookie not to overwrite the client's
cookie (see below for a more detailed explanation), set the cookie's Expires property to any time
prior to the current time. This will tell the client to overwrite the current cookie with an expired
cookie and the client will never send it back to the server.

Again, the temptation is to use DateTime.MinValue (01-Jan-0001 00:00:00) to delete a cookie;


again, this would be a mistake. This time, Netscape 7 will work as expected but Internet Explorer 6
considers this to be an exceptional case. If Internet Explorer receives a cookie with what it
considers to be a "blank" expiry date, it will retain the cookie until the browser is closed and then
expire it.

This could 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
other browsers.

The safest way to delete the cookie by using an Expiry date of DateTime.Now.AddYears(-30).

Incoming Cookies
When a page is received, it has a CookieCollection inside the Request, listing all the cookies in this
namespace on the client machine. Any cookies that do not exist in the Request will be null if you try
to access them (so be careful of looking for the Value property unless you are sure it exists).

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 Response.Cookies collection; any others are left alone.

In what seems like a bizarre twist, the incoming (Request) cookie carries an Expires date of
DateTime.MinValue, regardless of the date attached to the cookie on the client system.
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 null so it must have a value. The best arbitrary value would be
DateTime.MinValue.

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 Disappearing Cookie


If you try to access a cookie that doesn't exist in the Response.Cookies collection, it will be created
with an empty string in the Value and an Expires date of 01-Jan-0001 00:00. Strangely, it also
creates a matching cookie in the Request.Cookies collection if one doesn't already exist.

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 / Firefox).

A demonstration:

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.

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.

That is exactly what it does. However...

If you now place a breakpoint on the first line of the Page_Load event handler and add a debugger
watch for Response.Cookies["TestCookie"].Value then strange things start happening.

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.

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.

Now 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, this code isn't going to work as the developer clearly wants it to. The very act of
checking the cookie for null is going to create the cookie. The second condition can never be true
and currentCookieValue will be given an empty string wherever the cookie hasn't been explicitly
created as a result of the first condition.

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 Better Than Cure


The possible solutions here are many, 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;
}
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 Request.Cookies collection to the Response.Cookies collection; all
processing is then done with respect to the response cookie. So the above example becomes:

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 creates 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 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 Response.

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 null value but use a default (maybe empty) string; you will find you can now debug safely.

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.

Three simple rules:

• Never forget to set the Expires date wherever you set the Value.
• Wherever you access the Response.Cookies collection, make sure you are not creating a
blank and/or expired cookie by accident.

Avoid all use of DateTime.Now, DateTime.MinValue and DateTime.MaxValue.

Using Cookies in ASP.NET

A cookie is stored on the client's machine by their web browser software. To set a cookie, we include
information in an HttpResponse that instructs the browser to save a cookie on the client's system. Here's the
basic code for writing a Cookie in ASP.NET

Using System.Web;

Response.Cookies["BackgroundColor"].Value = "Red";
And to read the cookie back :

Response.Write

(Request.Cookies["BackgroundColor"].Value);

Note that for security reasons you can only read a cookie that was set within the same domain name.

Sometimes you may need a collection of stored items, such as user address details. In this case you could
read in a cookie collection like this:
HttpCookieCollection cookies = Request.Cookies;

for(int n=0;n<cookies.Count;n++)

HttpCookie cookie = cookies[n];

Response.Write("<hr/>Name: <b>" + cookie.Name + "</b><br />");

Response.Write("Expiry: " + cookie.Expires + "<br />");

Response.Write("Address1: " + cookie.Address1+ "<br />");

Response.Write("Address2: " + cookie.Address2+ "<br />");

Response.Write("City: " + cookie.City+ "<br />");

Response.Write("Zip: " + cookie.Zip+ "<br />");

Finally, you can see details of cookies during development, by turning on tracing in ASP.NET. Within the
@Page directive at the start of the page simply add Trace="true" :

<%@ Page trace="true" language="c#" Codebehind="page.aspx.cs"


Inherits="MyPage" %>

Introduction
What exactly is a cookie anyway? According to Websters Online, a cookie is any one of
the following:

1. a small flat or slightly raised cake


2. a : an attractive woman <a buxom French cookie who haunts the... colony's
one night spot -- Newsweek> b : PERSON, GUY <a tough cookie>
3. cookie : a small file or part of a file stored on a World Wide Web user's
computer, created and subsequently read by a Web site server, and containing
personal information (as a user identification code, customized preferences,
or a record of pages visited)

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 CookieCollection as a property of an
HttpRequest object (usually Request.Cookies) and returns a CookieCollection
of updates as a property of an HttpResponse object (usually Response.Cookies).

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 Expiration
The 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 Value of a cookie, remember also to set the Expires date. If you
fail to do this you will quickly find yourself losing Cookies owing to them having expired
immediately when updating them on the client machine or when the browser closes.

When a cookie expires, the client no longer sends it to the server, so you need to make
sure that the Expires property of the cookie is always in the future. If you just set a
cookie's value then it will create a cookie with Expires set to DateTime.MinValue
(01-Jan-0001 00:00).

You can set a cookie's Expires property using any DateTime value (a positive relief
after ASP). For example, if you want a Cookie to expire after the user has not been to that
part of your site for a week, you would set Expires = DateTime.Now.AddDays(7).

If you want the cookie to be permanent then the temptation is to use


DateTime.MaxValue, as I did in the lat version of this article. However, there is a
simple gotcha here.

DateTime.MaxValue is precisely 31-Dec-9999 25:59:59.9999999. But Netscape, even


at version 7, doesn't cope with that value and expires the cookie immediately. Amazingly,
and somewhat annoyingly, investigation showed that Netscape 7 will cope with 10-Nov-
9999 21:47:44 but will not handle a second higher (I'll be honest, I didn't test it to any
fraction of a second, I really wasn't interested).

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
DateTime.Now.AddYears(30), ie. 30 years into the future. If someone hasn't visited
your site for that long, do you really care what the state was last time they were there?

Disposing of Stale Cookies


If you want to delete the cookie on the client machine, do not use the obvious
Response.Cookies.Remove("MyCookie") which simply tells the cookie not to
overwrite the client's cookie (see below for a more detailed explanation), set the cookie's
Expires property to any time prior to the current time. This will tell the client to
overwrite the current cookie with an expired cookie and the client will never send it back
to the server.

Again, the temptation is to use DateTime.MinValue (01-Jan-0001 00:00:00) to delete a


cookie; again, this would be a mistake. This time, Netscape 7 will work as expected but
Internet Explorer 6 considers this to be an exceptional case. If Internet Explorer receives
a cookie with what it considers to be a "blank" expiry date, it will retain the cookie until
the browser is closed and then expire it.

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 DateTime.Now to
immediately expire a cookie but there are some dangers in that.

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
DateTime.Now.AddYears(-30).

Incoming Cookies
When a page is received, it has a CookieCollection inside the Request, listing all
the cookies in this namespace on the client machine. Any cookies that do not exist in the
Request will be null if you try to access them (so be careful of looking for the Value
property unless you are sure it exists).

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 Response.Cookies
collection; any others are left alone.

In what seems like a bizarre twist of fate, the incoming (Request) cookie carries an
Expires date of DateTime.MinValue, regardless of the date attached to the cookie on
the client system.

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 null so it must
have a value. The best arbitrary value would be DateTime.MinValue.

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 Cookie


If you try to access a cookie that doesn't exist in the Response.Cookies collection, it
will be created with an empty string in the Value and an Expires date of 01-Jan-0001
00:00. Strangely, it also creates a matching cookie in the Request.Cookies collection
if one doesn't already exist.

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.

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 Response.Cookies["TestCookie"].Value
then strange things start happening.

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 problems
Now 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 null is going to create the cookie. The second condition can never be true
and currentCookieValue will be given an empty string wherever the cookie hasn't
been explicitly created as a result of the first condition.

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 Cure


The 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
Request.Cookies collection to the Response.Cookies collection; all
processing is then done with respect to the response cookie. So the
above example becomes
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 Response.

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 null value but use a default (maybe empty) string; you will find
you can now debug safely. This is the dowloadable example available at the top of this
article.

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.

Potrebbero piacerti anche