LANGUAGES: VB.NET | C#
ASP.NET VERSIONS: 1.x
Accentuate the Positive
Remove QueryStrings but Keep Site Dynamic
By Josef Finsel
Q. I have a dynamic Web site that relies heavily on QueryString variables. The problem is that most Web search engines stop searching when they reach a URL that has the same page address with a different QueryString. How can I address this?
A. The easiest way to address this is by hiding the QueryString variables and creating unique URL addresses that incorporate them. To properly explain this, I m going to have to take a few minutes to build a dynamic Web site that uses QueryStrings; I ll use the Northwind database provided with SQL Server as the basis for the Web site.
I often hear complaints that sample code used for articles is a little unrealistic, so let me take a bit of time to build this dynamic site using the same techniques I would use to build any real site, starting with a Data Object class. Create a Web site and add a class named NorthwindDO. (You can download and explore the full source code with both C# and VB.NET examples; see the end of the article for details). This data object makes getting data as easy as calling a function. A quick look at one of the methods in the class will demonstrate how all of them work:
Public Function GetCustomerInformation(ByVal customerID
As String) As DataSet
Dim cn As SqlConnection = New SqlConnection(connectionString)
Dim ds As DataSet = New DataSet("OrderInfo")
Dim da As SqlDataAdapter = New SqlDataAdapter
Dim cmd As SqlCommand = New SqlCommand( _
"select companyname, contactname from customers where
customerid = '" & _
customerID & "'", cn)
da.SelectCommand = cmd
As this method shows, the data object wraps calls to the database within methods. This makes getting the data as easy as calling a method with the appropriate parameters. With the data object built, we can start using it within our Web pages, of which there will be three: CustomerList provides a complete list of customers and uses the CustomerID as a QueryString parameter to CustOrders; CustOrders lists all orders for a customer and has links to OrderInfo with the OrderID as a parameter; and, finally, OrderInfo displays the information for an order.
Let s take a look at the code for CustOrders because it will take a QueryString and create new links with that QueryString. The code for the Page_Load event of CustOrders is shown in Figure 1.
Dim ndo As NorthwindDO = New NorthwindDO
Dim strOutput As StringBuilder = New StringBuilder("Orders for")
Dim custDS As DataSet = _
|Order ID")||Order Date")|
Literal1.Text = strOutput.ToString()
Figure 1: The Page_Load event uses the data object and creates HTML.
As you can see from Figure 1, we use NorthwindDO to get both information about the customer and all of the orders a customer has made. This data is translated into HTML and put into a Literal tag for display. Each order creates a row in a table that generates a link using the OrderID as a QueryString parameter.
That covers the mechanics behind building a dynamic site using QueryStrings. But the question is, how do you do this in a way that doesn t use QueryStrings and makes your site look larger because each dynamically generated page is unique? The trick is to build a site that s broken on purpose.
Handling Broken Pages
As every user of a browser knows, anytime you attempt to open a page that doesn t exist, you ll get the dreaded 404 error; unless, of course, you write code to handle those missing pages. We ll begin the process by creating a new customer list page, CustomerListDir.aspx. The main difference between this list of customers and CustomerList.aspx is that the new one points each customer to CustOrders/CustomerID instead of CustOrders.aspx?CustomerID=CustomerID. Open this in the browser and click on one of the customers and you ll get a 404 error.
Let s add a CustOrders directory and add a Web form to it named Default.aspx. This Web form only needs to contain a Literal tag and we can cut and paste the code from CustOrders.aspx. The only change we need to make is to look at the RawUrl property of the Request object rather than the QueryString property:
Dim s404Page As String = Request.RawUrl
s404Page = s404Page.Substring(s404Page.LastIndexOf("/") + 1)
Dim custDS As DataSet = ndo.GetCustomerInformation(s404Page)
With the code in place, all we need to do now is tell the Web server to serve up this page instead of the 404 error page. To do this, open your Web sites so that you can manage them (right click on My Computer and select Manage). Right click on the CustOrders directory and select properties. Select the tab at the top labeled Custom Errors and find the entry for 404. Double click it to open it and change the Message Type to URL and the URL to the location of the Default.aspx Web form. In my case, because I have this as a separate directory under the main Web for this project, it will be /200508_VB/CustOrders/Default.aspx (see Figure 2). Once this is accomplished, I can open the CustomerListDir.aspx and click on any of the links. This will bring up the directory style address in the browser window but display the data in the Web form the browser is redirected to. Not only will this work with VS 2005, but it will even work with Web forms that use master pages for consistent styling.
Figure 2: Adding a redirect for 404 errors.
A Few More Words about the Sample Code
As I mentioned earlier, I took a little time to make this code look more like a real-world application by separating data elements into a data object class. Encapsulating the data access this way provides a couple of advantages over including the code within the Web forms themselves. First, if I want to use the same data on multiple pages, I only have to code it once. This is handy for things like Customer Information, which could be used on both of the sub pages.
The second advantage that encapsulating the data in a data class provides is that it makes it easier to transport the code. To get the code installed and running on your computer, simply modify the private variable connectionString to include the server, user, and password for your SQL Server. Another advantage lies in the ability to take this class and reuse it in a non-Web application.
I m also using the StringBuilder class to create strings. Although the use of StringBuilder has been a best practice since the introduction of the class, not many folks understand either why or how much of an impact there can be. When you create a string, you create a memory reference, equivalent to writing the string down on a piece of paper. When you add to that string (using & in VB or + in C#), you are copying that memory reference and adding to it, the equivalent of running the piece of paper with writing on it through the copier, writing the new string on it, and throwing away the original piece of paper, which will sit around waiting for the garbage collector to recycle it. As you can tell, this is highly inefficient.
The StringBuilder class, on the other hand, is akin to writing on the page and then writing more on the page, and then writing more on the page until the string is complete and then making the copy and handing it to you. This is not only quicker, it s also far more efficient. To demonstrate this, I ve written two VS 2005 utility programs, TestStringBuilder and MemUsage (again, see end of article for download details). TestStringBuilder takes four parameters indicating the number of times to run the test, how large a string to create, how many bytes to add to the string each time, and whether to create the string using StringBuilder or by concatenating strings. When it s done, it will tell you some basic information about how long it took in milliseconds for each individual concatenation or append. It s easy enough to see the difference between the two when run with the parameters 500 10000 5 0 and 500 10000 5 1. The first will take several seconds; the second returns almost instantly.
The MemUsage utility will run a program and return data about how long it takes the program to run, how much memory the program is using, and how much time the program requires from the processor. When running TestStringBuilder with the parameters above, the concatenation used 5156864 bytes of memory and used a third of a second of processor time; the StringBuilder used 2146304 bytes of memory and used less than two hundredths of a second. Although these numbers may seem contrived, the fact remains that the StringBuilder class is more efficient. And while a few bytes may not seem like a lot, it only takes a few users to turn that into a meaningful amount of memory to be used.
That s it for this month. Remember, this is your column; it runs on the questions you ask, so send your ASP.NET quandaries to [email protected] and I ll help you get the answers you need.
C# and VB.NET code examples are available for download.
Josef Finsel is a software consultant with Strategic Data Solutions (http://www.sds-consulting.com). He has published a number of VB and SQL Server-related articles and is currently working with VS 2005. He s also author of The Handbook for Reluctant Database Administrators (Apress, 2001).