A Matter of Context
Susan Warren
Microsoft Corporation
January 14, 2002
One of the most common problems with writing Web applications is letting your code know the context in which it's being executed. Let's look at a simple example—personalizing a page—that illustrates this problem:
Please sign in.
vs.
Welcome Susan!
Seems simple enough, but even this tiny bit of Web UI requires a couple of bits of information that will vary each time the page is requested. I'll need to know:
- Is the user signed in?
- What is the user's display name?
More generally, what is the unique context each time the page is requested? And how can I write my code so that it takes this information into account?
In fact, due to the stateless nature of HTTP, there are many different pieces of context a Web application might need to track. When a user interacts with a Web application, the browser sends a series of independent HTTP requests to the Web server. The application itself has to do the work of knitting these requests into a pleasing experience for the user and knowing the context of the request is critical.
ASP introduced several intrinsic objects like Request and Application to help track the context for an HTTP request. ASP.NET takes the next step and bundles these objects, plus several additional context-related objects into an extremely handy intrinsic object called Context.
Context is an object of type System.Web.HttpContext. It is exposed as a property of the ASP.NET Page class. It's also available from user controls and your business objects (more on that later). Here's a partial list of the objects rolled up by HttpContext:
If you're an ASP developer, some of the objects above will look quite familiar. There are a few enhancements, but for the most part, they work exactly the same in ASP.NET as in ASP.
Context Basics
Some of the objects in Context are also promoted as top-level objects on Page. For example, Page.Context.Response and Page.Response reference the same object so the following code is equivalent:
[Visual Basic® Web Form]
[C# Web Form]
You can also use the Context object from your business objects. HttpContext.Current is a static property that conveniently returns the context for the current request. This is useful in all kinds of ways, but here's a simple example of retrieving an item from the cache in your business class:
[Visual Basic]
[C#]
Context in Action
The Context object provides The Answer to several common ASP.NET "How Do I ...?" questions. Perhaps the best way to communicate just how valuable this gem can be is to show it in action. Here are a few of the best Context tricks I know.
How Do I Emit an ASP.NET Trace Statement From My Business Class?
Answer: Easy! Use HttpContext.Current to get the Context object, then call Context.Trace.Write().
[Visual Basic]
Imports System Imports System.Web Namespace Context ' Demonstrates emitting an ASP.NET trace statement from a ' business class. Public Class TraceEmit Public Sub SomeMethod() ' get the request context Dim _context As HttpContext = HttpContext.Current ' use context to write the trace statement _context.Trace.Write("in TraceEmit.SomeMethod") End Sub End Class End Namespace
[C#]
using System; using System.Web; namespace Context { // Demonstrates emitting an ASP.NET trace statement from a // business class. public class TraceEmit { public void SomeMethod() { // get the request context HttpContext _context = HttpContext.Current; // use context to write the trace statement _context.Trace.Write("in TraceEmit.SomeMethod"); } } }
How Can I Access a Session State Value From My Business Class?
Answer: Easy! Use HttpContext.Current to get the Context object, then access Context.Session.
[Visual Basic]
Imports System Imports System.Web Namespace Context ' Demonstrates accessing the ASP.NET Session intrinsic ' from a business class. Public Class UseSession Public Sub SomeMethod() ' get the request context Dim _context As HttpContext = HttpContext.Current ' access the Session intrinsic Dim _value As Object = _context.Session("TheValue") End Sub End Class End Namespace
[C#]
using System; using System.Web; namespace Context { // Demonstrates accessing the ASP.NET Session intrinsic // from a business class. public class UseSession { public void SomeMethod() { // get the request context HttpContext _context = HttpContext.Current; // access the Session intrinsic object _value = _context.Session["TheValue"]; } } }
How Can I Add a Standard Header and Footer to Every Page in My Application?
Answer: Handle the application's BeginRequest and EndRequest events, and use Context.Response.Write to emit the HTML for the header and footer.
Technically, you can handle the application events like BeginRequest in either an HttpModule or by using Global.asax. HttpModules are a bit harder to write, and aren't typically used for functionality that is used by a single application, as in this example. So, we'll use the application-scoped Global.asax file instead.
As with an ASP page, several of the ASP.NET context intrinsics are promoted to be properties of the HttpApplication class, from which the class representing Global.asax inherits. We won't need to use HttpContext.Current to get a reference to the Context object; it's already available in Global.asax.
In this example, I'm putting the
<html> and
<body> tags, plus a horizontal rule into the header section, and another horizontal rule plus the end tags for these into the footer section. The footer also contains a copyright message. The result looks like the figure below:
Figure 1. Example of standard header and footer as rendered in the browser
This is a trivial example, but you can easily extend this to include your standard header and navigation, or simply output the <!-- #include ---> statements for these. One caveat—if you want the header or footer to include interactive content, you should consider using ASP.NET user controls instead.
[SomePage.aspx source—sample content]
[Visual Basic Global.asax]
<%@ Application Sub Application_BeginRequest(sender As Object, e As EventArgs) ' emit page header Context.Response.Write("<html>" + ControlChars.Lf + _ "<body bgcolor=#efefef>" + ControlChars.Lf + "<hr>" + _ ControlChars.Lf) End Sub Sub Application_EndRequest(sender As Object, e As EventArgs) ' emit page footer Context.Response.Write("<hr>" + ControlChars.Lf + _ "Copyright 2002 Microsoft Corporation" + _ ControlChars.Lf + "</body>" + ControlChars.Lf + "</html>") End Sub </script>
[C# Global.asax]
<%@ Application void Application_BeginRequest(Object sender, EventArgs e) { // emit page header Context.Response.Write("<html>\n<body bgcolor=#efefef>\n<hr>\n"); } void Application_EndRequest(Object sender, EventArgs e) { // emit page footer Context.Response.Write("<hr>\nCopyright 2002 Microsoft Corporation\n"); Context.Response.Write("</body>\n</html>"); } </script>
How Can I Show A Welcome Message When The User Is Authenticated?
The Answer: Test the User context object to see if the user is authenticated. If so, get the user's name from the User object too. This is, of course, the example from the beginning of the article.
[Visual Basic]
<script language="VB" runat="server"> Sub Page_Load(sender As Object, e As EventArgs) { If User.Identity.IsAuthenticated Then welcome. </asp:HyperLink>
[C#]
<script language="C#" runat="server"> void Page_Load(object sender, EventArgs e) { if (User.Identity.IsAuthenticated) { welcome. </asp:HyperLink>
And Now for Something Really Wonderful: Context.Items
I hope the examples above show how much easier it is to write your Web application with a little context information at hand. Wouldn't it be great to be able to access some context that is unique to your application in the same way?
That's the purpose of the Context.Items collection. It holds your application's request-specific values in a way that is available to every part of your code that participates in the processing of a request. For example, the same piece of information can be used in Global.asax, in your ASPX page, in the user controls within the page, and by the business logic the page calls.
Consider the IBuySpy Portal sample application. It uses a single main page—DesktopDefault.aspx—to display portal content. Which content is displayed depends on which tab is selected, as well as the roles of the user, if authenticated.
Figure 2. IbuySpy home page
The querystring includes the TabIndex and TabId parameters for the tab being requested. This information is used throughout the processing of the request to filter which data is displayed to the user.
To use a querystring value, you need to first make sure it's a valid value and, if not, do a little error handling. It's not a lot of code, but do you really want to duplicate it in every page and component that uses the value? Of course not! In the Portal sample it is even more involved since there is other information that can be preloaded once we know the TabId.
The Portal uses the querystring values as parameters to construct a new "PortalSettings" object and add it to Context.Items in the BeginRequest event in Global.asax. Since the begin request is executed at the beginning of each request, this makes the tab-related values available to all of the pages and components in the application. When the request is complete, the object is automatically discarded—very tidy!
[Visual Basic Global.asax]
Sub Application_BeginRequest(sender As [Object], e As EventArgs) Dim tabIndex As Integer = 0 Dim tabId As Integer = 0 ' Get TabIndex from querystring If Not (Request.Params("tabindex") Is Nothing) Then tabIndex = Int32.Parse(Request.Params("tabindex")) End If ' Get TabID from querystring If Not (Request.Params("tabid") Is Nothing) Then tabId = Int32.Parse(Request.Params("tabid")) End If Context.Items.Add("PortalSettings", _ New PortalSettings(tabIndex, tabId)) End Sub
[C# Global.asax]
void Application_BeginRequest(Object sender, EventArgs e) { int tabIndex = 0; int tabId = 0; // Get TabIndex from querystring if (Request.Params["tabindex"] != null) { tabIndex = Int32.Parse(Request.Params["tabindex"]); } // Get TabID from querystring if (Request.Params["tabid"] != null) { tabId = Int32.Parse(Request.Params["tabid"]); } Context.Items.Add("PortalSettings", new PortalSettings(tabIndex, tabId)); }
The DesktopPortalBanner.ascx user control pulls the PortalSetting's object from Context to access the Portal's name and security settings. In fact, this one module is a great all-around example of Context in action. To illustrate the point, I've simplified the code a little, and marked all of the places either HTTP or application-specific Context is accessed in bold.
[C# DesktopPortalBanner.ascx]
<%@ Import public int tabIndex; public bool ShowTabs = true; protected String|<" + "/span" + ">"; // if authentication mode is Cookie, provide a logoff link if (Context.User.Identity.AuthenticationType == "Forms") {|</span>\n" + "<a href=" + Request.ApplicationPath + "/Admin/Logoff.aspx class=SiteLink> Logoff" + "</a>"; } } // Dynamically render portal tab strip if (ShowTabs == true) { tabIndex = portalSettings.ActiveTab.TabIndex; // Build list of tabs to be shown to user ArrayList authorizedTabs = new ArrayList(); int addedTabs = 0; for (int i=0; i < portalSettings.DesktopTabs.Count; i++) { TabStripDetails tab = (TabStripDetails)portalSettings.DesktopTabs[i]; if (PortalSecurity.IsInRoles(tab.AuthorizedRoles)) { authorizedTabs.Add(tab); } if (addedTabs == tabIndex) { tabs.SelectedIndex = addedTabs; } addedTabs++; } // Populate Tab List at Top of the Page with authorized // tabs tabs.DataSource = authorizedTabs; tabs.DataBind(); } } </script> <table width="100%" cellspacing="0" class="HeadBg" border="0"> <tr valign="top"> <td colspan="3" align="right"> <asp:label <a href="<%= Request.ApplicationPath %>">Portal Home</a> <span class="Accent"> |</span> <a href="<%= Request.ApplicationPath %>/Docs/Docs.htm"> Portal Documentation</a> <%= LogoffLink %> </td> </tr> <tr> <td width="10" rowspan="2"> </td> <td height="40"> <asp:label </td> <td align="center" rowspan="2"> </td> </tr> <tr> <td> <asp:datalist <ItemTemplate> <a href='<%= Request.ApplicationPath %> /DesktopDefault.aspx?tabindex=<%# Container.ItemIndex %>&tabid= <%# ((TabStripDetails) Container.DataItem).TabId %>'> <%# ((TabStripDetails) Container.DataItem).TabName %> </a> </ItemTemplate> <SelectedItemTemplate> <span class="SelectedTab"> <%# ((TabStripDetails) Container.DataItem).TabName %> </span> </SelectedItemTemplate> </asp:datalist> </td> </tr> </table>
You can browse and run the complete source for the IBuySpy portal online in both Visual Basic and C# at, or download it and run it yourself.
Summary
Context is another one of those "good things get even better in ASP.NET" features. It extends the already great context support of ASP to add both hooks into the new runtime features of ASP.NET. Plus it adds Context.Items as a new state mechanism for very short-lived values. But the ultimate benefit to you as a developer is more compact, easier to maintain code, and that's a context we can all get behind.