Reduce Bloat and Traffic

Delete Long-lived Authentication Cookies; Preserve Bandwidth by Keeping View State onthe Server

AskthePRO

LANGUAGES: C#

ASP.NET VERSIONS: 1.0 | 1.1

 

Reduce Bloat and Traffic

Delete Long-lived Authentication Cookies; Preserve Bandwidth by Keeping View State on the Server

 

By Jeff Prosise

 

Q. An ASP.NET app on my company's Web server uses forms authentication, and originally allowed ASP.NET to issue persistent authentication cookies that were valid for 50 years. We've since become wiser and have restricted authentication cookie lifetime to 7 days. However, I still worry about those 50-year cookies floating around out there. How can I destroy them without inconveniencing users with 7-day cookies?

 

A. By examining each request as it comes in, checking for forms authentication cookies issued before a specified date (the date when you switched from 50-year cookies to the safer 7-day variety), and deleting those that you find. The Global.asax file in Figure 1 demonstrates one way to go about it.

 

<script language="C#" runat="server">

void Application_BeginRequest(Object sender, EventArgs e)

{

  HttpCookieCollection cookies =

     ((HttpApplication)sender).Request.Cookies;

  int count = cookies.Count;

  for (int i=0; i<count; i++)

  {

    if (String.Compare(cookies[i].Name,

         FormsAuthentication.FormsCookieName, true) == 0)

    {

      HttpCookie cookie = cookies[i];

      FormsAuthenticationTicket ticket =

        FormsAuthentication.Decrypt(cookie.Value);

      if (ticket.IssueDate < new DateTime(2004, 5, 1))

                                          // May 1, 2004

       cookies.Remove(FormsAuthentication.FormsCookieName);

      break;

    }

  }

}

</script>

Figure 1: Use this Global.asax file to invalidate authentication cookies issued before May 1, 2004.

 

Application_BeginRequest is called each time ASP.NET receives a request for a page or other resource in the host application. Significantly, Application_BeginRequest is called very early in the request's lifetime, even before ASP.NET's forms authentication module sees the request. The Application_BeginRequest method in Figure 1 checks for an authentication cookie in the request. If it finds one, the method decrypts the cookie to create a forms authentication ticket. (Authentication cookies are encrypted by default so the data in them cannot be read nor altered.) Then it inspects the ticket's IssueDate property and removes the cookie from the request if the issue date is before May 1, 2004. By the time the forms authentication module processes the request, the authentication cookie has been stripped away, and the user will be asked to log in again, whereupon he or she will be issued a new authentication cookie.

 

If the user happens to switch away from the login page without logging in, the old cookie will remain in his or her browser's cookie cache and will be submitted again, but it won't be honored. The moment the user logs in again, the new cookie will replace the old.

 

Q. How can I prevent a DataGrid from writing large volumes of data into view state without disabling view state altogether?

 

A. DataGrids are prodigious users of view state, which causes headaches for ASP.NET developers. A DataGrid with 40 or 50 rows can easily double the volume of data round-tripped between client and server, effectively decreasing the bandwidth of the connection.

 

One solution is to set the DataGrid's EnableViewState property to False, but that has two adverse side effects. First, the DataGrid has to rebind to the data source on every trip to the server (as opposed to only outside of postbacks). Second, DataGrids that are denied access to view state don't fire events properly.

 

An alternate solution to the problem of DataGrid view state is to move view state to session state, rather than store it in hidden fields. This can be accomplished by overriding System.Web.UI.Page's LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium methods.

 

To demonstrate, the page in Figure 2 uses a DataGrid to display records from the Northwind database's Products table. Do a View Source after pulling the page up in your browser and you'll see the massive amounts of view state written out by the DataGrid. In effect, the DataGrid writes its contents twice to each HTTP response: once in the form of view state so it can repopulate itself within postbacks, and once more in the form of the HTML that it renders.

 

<%@ Import Namespace="System.Data"%>

<%@ Import Namespace="System.Data.SqlClient"%>

<html>

  <body>

    <form runat="server">

      <asp:Button Text="Test" RunAt="server"/>

      <asp:DataGrid ID="MyDataGrid" RunAt="server"/>

    </form>

  </body>

</html>

<script language="C#" runat="server">

void Page_Load(Object sender, EventArgs e)

{

  if (!IsPostBack)

  {

    SqlDataAdapter adapter =

      new SqlDataAdapter("select * from products",

      "server=localhost;database=northwind;uid=sa");

    DataSet ds = new DataSet();

     adapter.Fill(ds);

    MyDataGrid.DataSource = ds;

    MyDataGrid.DataBind();

  }

}

</script>

Figure 2: Used this way, DataGrid controls slow effective connection speeds by generating data that is persisted in hidden fields.

 

Figure 3 shows a modified version of the page. This version uses LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium to store view state in session state, rather than in hidden fields. The DataGrid looks and acts the same as before, but View Source reveals that the DataGrid no longer pollutes HTTP messages with serialized content. Aside from increased memory consumption on the server, you basically get to have your cake and eat it too.

 

<%@ Import Namespace="System.Data"%>

<%@ Import Namespace="System.Data.SqlClient"%>

<html>

  <body>

    <form runat="server">

      <asp:Button Text="Test" RunAt="server"/>

      <asp:DataGrid ID="MyDataGrid" RunAt="server"/>

    </form>

  </body>

</html>

<script language="C#" runat="server">

void Page_Load(Object sender, EventArgs e)

{

  if (!IsPostBack)

  {

    SqlDataAdapter adapter =

      new SqlDataAdapter("select * from products",

      "server=localhost;database=northwind;uid=sa");

    DataSet ds = new DataSet();

    adapter.Fill(ds);

    MyDataGrid.DataSource = ds;

    MyDataGrid.DataBind();

  }

}

protected override object

  LoadPageStateFromPersistenceMedium()

{

  return Session["__ViewState"];

}

protected override void SavePageStateToPersistenceMedium(

  object state)

{

  Session["__ViewState"] = state;

}

</script>

Figure 3: Overriding LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium allows view state to be kept on the server.

 

Q. I used the technique described in your February 2004 column to convert temporary session ID cookies into persistent ones. However, it doesn't work in ASP.NET 1.1. Is there a fix?

 

A. You bet. The problem results from a subtle change apparently made to the .NET Framework's HttpCookieCollection class between version 1.0 and 1.1 - one in which the very act of reading a cookie by name from an HttpCookieCollection adds a cookie of the same name to HTTP response if the cookie doesn't exist when it's read. Figure 4 contains a revised source code listing for the HTTP module presented in the February column. This one, I'm happy to report, works like a champ with ASP.NET 1.1.

 

using System;

using System.Web;

 

public class PersistentSessionModule : IHttpModule

{

  public void Init(HttpApplication application)

  {

    application.EndRequest +=

      new EventHandler(OnEndRequest);

  }

  public void Dispose() {}

 

  void OnEndRequest(Object sender, EventArgs e)

  {

    HttpCookie cookie =

      GetCookieFromResponse("ASP.NET_SessionId");

    if (cookie != null)

      cookie.Expires = DateTime.Now.AddDays(7);

  }

 

  private HttpCookie GetCookieFromResponse(string name)

  {

    HttpCookieCollection cookies =

      HttpContext.Current.Response.Cookies;

    int count = cookies.Count;

    for (int i=0; i<count; i++)

    {

      if (String.Compare(cookies[i].Name, name, true) == 0)

        return cookies[i];

    }

  return null;

  }

}

Figure 4: This HTTP module converts temporary session ID cookies into persistent session ID cookies.

 

The sample code accompanying this article is available for download.

 

Jeff Prosise is the author of several books, including Programming Microsoft .NET (Microsoft Press, 2002). He's also a cofounder of Wintellect (http://www.wintellect.com), a software consulting and education firm that specializes in .NET. Have a question for this column? Submit queries to [email protected].

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish