Share via


From the August 2002 issue of MSDN Magazine

MSDN Magazine

Code Your Way to ASP.NET Excellence
Jeff Prosise Wicked Code Archive
Download the code for this article:WickedCode0208.exe (43 KB)
O

ne of the selling points of the Microsoft® .NET Framework is that it makes programming easier—not just Web programming, but application programming in general. Few would argue that it doesn't live up to the hype. The .NET Framework Class Library (FCL), which provides the API that managed applications write to, is so rich and diverse that tasks requiring hundreds of lines of code in unmanaged applications are often accomplished with just one or two lines of managed code. If you don't believe it, compare a Windows®-based app that displays a JPEG with one that does the same using the .NET Framework (see https://samples.gotdotnet.com/quickstart/ winforms/doc/WinformsGDIPlus.aspx).
      With such a rich class library to fall back on, you might be misled into believing that the .NET Framework presents fewer opportunities for clever coders to do great things. On the contrary, the sheer size of the .NET Framework means that nuggets of opportunity are everywhere. To illustrate, here are five little gems to help ASP.NET programmers code their way to programming excellence. They're from my personal grab bag of favorite ASP.NET programming tricks. I think you'll find them as useful as I do.

Localize Web Apps by Adding 10 Lines of Code

      Like all managed applications, ASP.NET applications rely on classes in the .NET FCL. And many FCL methods are culture-aware. For example, DateTime's ToShortDateString method uses culture information to format dates. String.Format uses culture information to format currency values and other numeric strings. If you create a CultureInfo object representing a culture and assign it to the calling thread's CurrentCulture property, these methods will adapt their output accordingly.
      A Web app might receive requests from all over the world. ASP.NET apps can retrieve information about a user's preferred culture from the Accept-Language headers that accompany HTTP requests. The information in those headers is exposed through the Request object's UserLanguages property. Suppose a user configures his or her browser to specify French as the language of choice, with U.S. English second. Here's what the Accept-Language header transmitted by the browser might look like:

  fr, en-us;q=0.5
  

 

Request.UserLanguages, which exposes a string array, would represent this information as a pair of strings:

  fr
  
en-us;q=0.5

 

      The following statement creates a CultureInfo object representing the culture specified in the first UserLanguages string. It works equally well with neutral culture strings (strings such as "fr" that specify a language but not a country or region) and specific culture strings (strings such as "en-us" that specify both a language and a country or region):

  CultureInfo ci=CultureInfo.CreateSpecificCulture(UserLanguages[0]);
  

 

      And this statement assigns the CultureInfo object to the current thread, allowing culture-aware methods to adapt their output:

  Thread.CurrentThread.CurrentCulture = ci;
  

 

      Doing this in each and every request is a first step toward internationalizing a Web app written with ASP.NET. In some cases, nothing more is required to make the app culture-aware.
      The sample application in Figure 1 and Figure 2 demonstrates how to apply this knowledge. Date.aspx is a simple Web page that displays the current date. Global.asax contains a handler for the Application_BeginRequest event that fires at the beginning of each and every request. Inside the event handler is code that extracts the user's preferred culture from the request and assigns a CultureInfo object representing that culture to the thread that's currently processing the Web request.
      To see the impact that this Global.asax file has on Date.aspx's output, copy both files to an ASP.NET-enabled Web server. Then fire up your browser and enter Date.aspx's URL. (If the browser is running on the Web server and the files are in the server's wwwroot directory, the correct URL is https://localhost/date.aspx.) If you're a U.S. user, you'll see the page depicted in Figure 3.

Figure 3 U.S. English Localization
Figure 3 U.S. English Localization

      Now go to Tools | Internet Options | General and click on the Languages button to add "French (France) [fr]" to your language preferences and place it at the top of the list. Request Date.aspx again and the page will look quite different (see Figure 4). Include an Application_BeginRequest handler like the one in Figure 2 in every Web app you deploy and you'll gain a measure of culture awareness free of charge.

Figure 4 French Localization
Figure 4 French Localization

Use HTTP Handlers to Generate Images Dynamically

      Including an image in a Web page is as simple as inserting an <img> tag into the page's HTML. The following statement displays the image in the file named Logo.jpg:

  <img src="logo.jpg">
  

 

This is fine and good if Logo.jpg contains a static image that can be generated absent any user input. But what if you want to build a Web page that displays images built at run time from user input—for example, a page that graphs user-supplied data or values obtained from a database? The simple answer is that you create the image on the server and return an <img> tag that references it. The reality is that dynamic image generation is easier said than done—unless, that is, you're an ASP.NET programmer.
      The secret to displaying dynamically generated images in ASP.NET Web pages is the HTTP handler. An HTTP handler is a class that handles requests for a given resource or resource type. When you request an ASPX file, it's an HTTP handler that physically handles the request by loading and executing the page. That handler is mapped to .aspx files in the <httpHandlers> section of Machine.config. Other built-in HTTP handlers process requests for ASMX files and other ASP.NET file types.
      You can extend ASP.NET by writing HTTP handlers of your own. Figure 5 lists the source code for a generic HTTP handler that generates a 128×128-pixel image and returns it in an HTTP response as a JPEG. Each time a resource registered to this handler is requested, ASP.NET calls the handler's ProcessRequest method. ProcessRequest creates a bitmap (an instance of System.Drawing.Bitmap) in memory and draws to it using a System.Drawing.Graphics object. Replace the TODO statement with GDI+ code that draws on the bitmap surface and you have an HTTP handler that generates images on the fly.
      The application in Figure 6 and Figure 7 demonstrates how to put this knowledge to work in a Web app. To see them in action, deploy the files to an ASP.NET Web server. Then call up PieChart.aspx in your browser, enter four numbers, and click the Show Chart button. A pie chart appears depicting the relative proportions of the values that you entered, as shown in Figure 8.

Figure 8 The Generated Pie Chart
Figure 8 The Generated Pie Chart

      How does PieChart.aspx work? It includes an image control that initially displays no image at all because its ImageUrl property is unassigned. Clicking the Show Chart button activates the OnShowChart method on the server. OnShowChart builds a URL complete with a query string containing user input and assigns it to the Image control. Entering the values 100, 200, 300, and 400 into the four TextBoxes produces the following URL:

  piechart.ashx?q1=100&q2=200&q3=300&q4=400&width=256&height=224
  

 

Note the resource identified in the URL: PieChart.ashx. Inside PieChart.ashx is an HTTP handler that generates pie charts from the inputs in the query string. The @ WebHandler directive at the top of the file informs ASP.NET that PieChart.ashx contains an HTTP handler. ASP.NET automatically compiles and executes the handler when the ASHX file is requested. No special registration is required because Machine.config maps ASHX files to a handler that knows how to compile and run other HTTP handlers.
      You can extrapolate from this example to build Web apps that display dynamically generated images of any sort. I recently used it to build a Web-based front end to Microsoft TerraServer. The app passes information to an HTTP handler that fetches satellite images from TerraServer and stitches them together to form the final image. It makes for interesting demos at conferences, and it serves as a pretty good example of how Web Services and HTTP handlers can work together, too.

Use the Application Cache to Write Apps that Scream

      ASP programmers are familiar with application state—the global in-memory repository for data that's accessible to all parts of an application. ASP.NET supports ASP-style application state, but it also offers something better: the ASP.NET application cache. The application cache is a fine place to cache data to reduce performance-inhibiting file and database accesses. It's also smarter than application state. It lets you assign expiration policies to items in the cache and create dependencies between items in the cache and files. It even notifies you when items are removed from the cache, affording you the opportunity to replace those items with ones containing the latest data.
      As a sample demonstrating how the application cache can be used to improve performance, consider the Web page in Figure 9. Each time it's requested, it reads an array of quotes from a text file named Quotes.txt and displays a randomly selected line. Quotes.txt, which isn't listed here but is included in the downloadable sample code for this column (see the link at the top of this article), contains a collection of famous quotations. Copy DumbQuotes.aspx and Quotes.txt to a virtual directory on your Web server. Then call up Quotes.aspx in your browser and refresh it a few times. Each refresh should produce a new quotation, as shown in Figure 10.

Figure 10 Pulling a Quote from the Cache
Figure 10 Pulling a Quote from the Cache

      So what's wrong with DumbQuotes.aspx? Nothing—unless, that is, you value performance. Each time it's requested, DumbQuotes.aspx performs a physical file access, opening Quotes.txt and reading its contents into an ArrayList. File and database accesses are common sources of performance bottlenecks in Web apps. This page would perform much faster if it read Quotes.txt once, cached it in memory, and retrieved quotations directly from the cache instead of from the disk.
      Figure 11 contains an improved version of the code—SmartQuotes.aspx—that reads quotations from the application cache. The accompanying Global.asax file (see Figure 12) initializes the cache when the application starts up by reading Quotes.txt and placing the resulting ArrayList in the application cache. It also configures ASP.NET to call the local method named RefreshQuotes if Quotes.txt is modified. RefreshQuotes responds by rereading the file and adding an updated ArrayList to the cache. DumbQuotes.aspx and SmartQuotes.aspx produce the identical output, but the latter scales much better due to the dramatically reduced number of file accesses.

Combine DataGrids with Client-side Script for Advanced Customizations

      The DataGrid is probably the most versatile Web control in the FCL. It gets the most press and also generates the most questions. One of the most common is "How do I create a DataGrid that pops up a message box asking the user for confirmation before deleting a record?" The answer is to wire the DataGrid's buttons with OnClick attributes that use JavaScript's confirm function to pop up a message box. The hard part is figuring out how to do the wiring. The Web page in Figure 13 shows how it's done.
      ConfirmDelete.aspx uses a DataGrid to display records from the "titles" table of the SQL Server™ Pubs database. The DataGrid includes a ButtonColumn that renders a column of Delete buttons. You can't include an OnClick attribute in the ButtonColumn tag because ButtonColumn doesn't support it. But you can process the ItemCreated events that the DataGrid fires as it renders the rows in the data source and programmatically add OnClick attributes. In Figure 13, the statement

  OnItemCreated="OnAttachScript"
  

 

registers the method named OnAttachScript to be called each time a row is created. OnAttachScript, in turn, retrieves a reference to the button control in the row's leftmost column:

  WebControl button = (WebControl) e.Item.Cells[0].Controls[0];
  

 

Then it adds a DHTML OnClick attribute that executes the following JavaScript statement:

  button.Attributes.Add ("onclick", "return confirm
  
(\"Are you sure?\");");

 

      The JavaScript code pops up a confirmation window and prevents the page from posting back to the server if the user clicks Cancel rather than OK. Because the OnDeleteRecord method that's called when the Delete button is clicked is a server-side event handler, it doesn't get called if the postback is suppressed.

Figure 14 DataGrids and Confirmation Buttons
Figure 14 DataGrids and Confirmation Buttons

      Prove it to yourself by calling up ConfirmDelete.aspx in your browser. Click one of the Delete buttons to pop up a confirmation box (see Figure 14). Then click OK to simulate a record deletion, or Cancel to leave the record intact. OnDeleteRecord doesn't really delete the record. It simply writes the record's "title" field to the Label control at the bottom of the page. No text appears when you click Cancel, proving that OnDeleteRecord wasn't called.

Use View State to Persist Data Between Requests

      One of the greatest difficulties in writing stateful Web applications stems from the fact that HTTP is a stateless protocol. Suppose you wanted to build a page with a DataGrid that supports sorting and paging. Furthermore, suppose that when paging the DataGrid, you want to remember how it was last sorted so you can preserve the sort order. You can't store information regarding the last sort in the page itself (you can try, but it won't work because two requests for the same page are handled by two different page objects), but you can store it in view state.
      View state is an easy-to-use mechanism for preserving data from one HTTP request to the next. Controls use view state to preserve state across invocations; pages can use it, too. The Page class in ASP.NET (System.Web.UI.Page) includes a public property named ViewState. The ViewState property provides ready access to view state. In an ASPX file, the statement

  ViewState["SortExpression"] = "Price";
  

 

writes the string "Price" to view state and keys it with the string "SortExpression." In a subsequent request, the statement

  string exp = (string) ViewState["SortExpression"];
  

 

reads the string back. ASP.NET preserves the string between requests by round-tripping it to the client and back in a hidden <input> control named __VIEWSTATE. That's why __VIEWSTATE appears in the source code window when you execute a View/Source command after retrieving an ASPX file.
      The Web page in Figure 15 demonstrates how view state might be used to support a sortable, pageable DataGrid. In addition to sorting the DataGrid, the OnSort method preserves a record of the last sort by writing it to view state. When paging the DataGrid, the OnNewPage method retrieves the sort information from view state and uses it to sort the DataView that it binds to the DataGrid. As a result, you can page through the DataGrid without losing a sort performed earlier.

Send questions and comments for Jeff to wicked@microsoft.com.

Jeff Prosise makes his living programming Windows and teaching others to do the same. He is also a cofounder of Wintellect, a developer training and consulting firm that specializes in .NET. He is the author of Programming Microsoft .NET (Microsoft Press, 2002). Contact Jeff at wicked@microsoft.com.