figure 2.5
output of listing 2.1.5 when viewed through a browser.
if you've worked with classic asp, you are likely familiar with the concept of session-level variables. these variables are defined on a per-user basis and last for the duration of the user's visit to the site. these variables are synonymous with global variables in that their values can be accessed across multiple asp pages. session-level variables, which are discussed in greater detail in chapter 14, "managing state," are a simple way to maintain state on a per-user basis. because we want the user's navigation history stack to persist as the user bounces around our site, we will store the stack class instance in a session-level variable.
to implement a navigation history stack as a session-level variable, we must make sure that we have created such a variable before trying to reference it. keep in mind that when a visitor first comes to our site and visits that first page, the session-level variable will not be instantiated. therefore, on each page, before we refer to the navigation history stack, it is essential that we check to ensure that our session-variable, session["history"], has been assigned to an instance of the stack class.
--------------------------------------------------------------------------------
note
to access a session variable using c#, the braces are used around the session variable name. for example, to retrieve the value of the history session variable with c# we'd use:
session["history"]
with vb.net, however, parentheses are used in place of the brackets:
session("history")
--------------------------------------------------------------------------------
line 6 in listing 2.1.5 checks session["history"] to determine whether it references a stack object instance. if session["history"] has not been assigned an object instance, it will equal null (or nothing, in vb). if session["history"] is null, we need to set it to a newly created instance of the stack class (line 9).
however, if session["history"] is not null, we know that the user has already visited at least one other page on our site. therefore, we can display the contents of the session["history"] stack. this is accomplished in lines 12 through 17 with the use of an enumerator. we'll discuss iteration through collections via enumerators in the next section, "similarities among the collection types." with c#, as opposed to vb, explicit casting must be done when working with the session object. for example, on line 13, before we can call the getenumerator() method (a method of the stack class), we must cast the session["history"] variable to a stack:
// c# code must use an explicit cast
ienumerator enumhistory = ((stack) session["history"]).getenumerator();
'vb code, however, does not require an explicit cast
dim enumhistory as ienumerator = session("history").getenumerator()
with vb, however, such a cast is not necessary. casting issues with the session object are discussed in more detail in chapter 14.
after either creating a new session-level stack instance or displaying the stack's contents, we're ready to add the current url to the navigation history stack. this could be accomplished with the following simple line of code:
((stack) session["history"]).push(request.url.pathandquery);
however, if the user refreshed the current page, it would, again, get added to the navigation history stack. it would be nice not to have the same page repeatedly appear in the navigation history stack. therefore, on line 23, we use the peek method to see if the top-most element in the stack is not equal to the current url. if the top-most element of the stack is not equal to the current url, we push the current url onto the top of the stack, otherwise we do nothing.
before we use the peek method, we first determine whether the stack is empty. recall from the previous section, "working with the queue class," using the peek method on an empty queue will raise an invalidoperationexception exception. this is the same case with the stack class; therefore, on line 21, we first check to ensure that at least one element is in the stack before using the peek method.
two useful utility asp.net pages have been created to provide some extra functionality for our navigation history stack. the fist page, clearstackhistory.csharp.aspx, erases the contents of the history stack and is presented in listing 2.1.6. the second page, back.csharp.aspx, serves like a back button in the user's browser, taking him to the previously visited page. the code for back.csharp.aspx is given in listing 2.1.7. we'll examine these two code listings momentarily.
listing 2.1.5 also contains a link to another asp.net page, listing2.1.5.b.aspx. this page is identical to listing2.1.5.aspx. in your web site, you would need to, at a minimum, include the code in listing 2.1.5 in each asp.net page to correctly keep the navigation history up-to-date.
listing 2.1.6 clearstackhistory.csharp.aspx erases the contents of the navigation history stack
1: <script language="c#" runat="server">
2:
3: void page_load(object sender, eventargs e)
4: {
5: // see if we have a stack created or not:
6: if (session["history"] == null)
7: {
8: // there's no stack, so we don't need to do anything!
9: } else {
10: // we need to clear the stack
11: ((stack) session["history"]).clear();
12: }
13: }
14:
15: </script>
16:
17: <html>
18: <body>
19: your navigation history has been cleared!
20: </body>
21: </html>
listing 2.1.6 contains the code for clearstackhistory.csharp.aspx. this code only has a single task—clear the contents of the navigation history stack—and therefore is fairly straightforward. the asp.net page starts by checking to determine if session["history"] refers to a stack object instance (line 6). if it does, the clear method is used to erase all the stack's elements (line 11).
the code for the second utility page, back.csharp.aspx, can be seen in listing 2.1.7.
listing 2.1.7 back.csharp.aspx sends the user to the previous page in his navigation history stack
1: <script language="c#" runat="server">
2: void page_load(object sender, eventargs e)
3: {
4: // see if we have a stack created or not:
5: if (session["history"] == null ||
6: ((stack) session["history"]).count < 2)
7: {
8: // there's no stack, so we can't go back!
9: response.write("egad, i can't go back!");
10: } else {
11: // we need to go back to the prev. page
12: ((stack) session["history"]).pop();
13: response.redirect(((stack) session["history"]).pop().tostring());
14: }
15: }
16: </script>
as with clearstackhistory.csharp.aspx, back.csharp.aspx starts by checking to determine if session["history"] is null. if that is the case, a warning message is displayed because we can't possibly step back through our navigation history stack if it doesn't exist!
take a moment to briefly look over listing 2.1.5 again. note that on each page we visit, we add the current url to the stack. therefore, if we want to go back to the previous page, we can't just pluck off the top element from the stack (because that contains the current url). rather, we must pluck off the top-most item, dispose of it, and then visit the next item on the top of the stack. for that reason, our stack must have at least two elements to be able to traverse back to the previous page. on line 6, we check to make sure that the navigation history stack contains at least two elements.
given that we have a properly defined navigation history stack—that is, session["history"] is not null and there are at least two elements in the stack—we will reach lines 12 and 13, which do the actual work of sending the user back to the previous page. line 12 simply disposes of the top-most stack element; line 13 uses the redirect method of the response object to send the user to the next element at the top of the stack.
that wraps up our examination of the navigation history stack example. the code samples spanned three listings: listing 2.1.5, listing 2.1.6, and listing 2.1.7. if you decide to use this code on your web site, there are a couple of things to keep in mind:
first, because our implementation of the navigation history stack is a code snippet in an asp.net page, the code in listing 2.1.5 would need to appear in every web page on your site. this, of course, is a ridiculous requirement; it would make sense to encapsulate the code and functionality in a user control to allow for easy code reuse. (for more information on user controls, refer to chapter 5, "creating and using user controls.")
second, remember that in back.csharp.aspx we are popping off the top two urls. because pop removes these elements from the stack altogether, the navigation history stack cannot contain any sort of forward link.
similarities among the collection types
because each collection has the same basic functionality—to serve as a variable-sized storage medium for objects—it is not surprising that the collection types have much in common with one another. all have methods to add and remove elements from the collection. the count property, which returns the total number of elements in the collection, is common among all collection types.
each collection also has a means to iterate through each element. this can be accomplished in vb using a for each ... next loop or, in c#, a foreach loop, as follows:
'with vb, use a for each ... next loop
dim qtasks as queue = new queue()
' ... populate the queue ...
dim s as string
for each s in qtasks
's represents the current element in qtasks
response.write(s + "<br>")
next
// in c#, a foreach construct can be used to iterate
// through each element
queue qtasks = new queue();
// ... populate the queue ...
foreach (string s in qtasks)
{
// s represents the current element in qtasks
response.write(s + "<br>");
}
although each collection can be iterated via a for each ... next or foreach loop, each collection can also have its elements iterated with an enumerator. enumerators are small classes that provide a simple functionality: to serve as a (read-only) cursor to allow the developer to step through the elements of a collection.
the .net framework provides a number of specific enumerators for specific collection types. for example,the idictionaryelement enumerator is useful for iterating through a hashtable. the ilist enumerator is handy for stepping through the elements of an arraylist. all these specialized enumerators are derived from a base enumerator interface, ienumerator. because of this fact, all the collection types can be iterated via the ienumerator enumerator as well.
because an enumerator's most basic purpose is to serve as a cursor for a collection, the ienumerator class contains only a single property that returns the element in the collection to which the enumerator is currently pointing. (more specialized enumerators, such as idictionaryelement, contain multiple properties.) ienumerator contains just two methods: movenext, which advances the enumerator to the next element in the collection, and reset, which returns the enumerator to its starting position—the position immediately before the first element in the collection.
listing 2.1.8 contains a simple asp.net page that illustrates iteration through both an arraylist and hashtable with the ienumerator enumerator. the output is shown in figure 2.6.
listing 2.1.8 to step through each element of a collection, an enumerator can be used
1: <script language="vb" runat="server">
2:
3: sub page_load(sender as object, e as eventargs)
4: ' create some collections
5: dim ateam1 as new arraylist(), _
6: ateam2 as new arraylist(), _
7: ateam3 as new arraylist()
8:
9: dim htprojects as new hashtable()
10:
11: ' assign memebers to the various teams
12: ateam1.add("scott")
13: ateam1.add("rob")
14: ateam1.add("chris")
15:
16: ateam2.add("doug")
17: ateam2.add("don")
18:
19: ateam3.add("billy")
20: ateam3.add("mark")
21: ateam3.add("charles")
22: ateam3.add("steve")
23:
24:
25: ' add each team to the htprojects hashtable
26: htprojects.add("prototyping", ateam1)
27: htprojects.add("coding", ateam2)
28: htprojects.add("testing", ateam3)
29:
30: ' now, list each project
31: dim enumprojects as ienumerator = htprojects.getenumerator()
32: do while enumprojects.movenext()
33: lblprojectlisting.text &= enumprojects.current.key & "<br>"
34: loop
35:
36: ' now list each team
37: dim enumteam as ienumerator
38: enumprojects.reset()
39: do while enumprojects.movenext()
40: lbldetailedlisting.text &= "<b>" & enumprojects.current.key
& ":</b><ul>"
41:
42: enumteam = enumprojects.current.value.getenumerator()
43: do while enumteam.movenext()
44: lbldetailedlisting.text &= enumteam.current & "<br>"
45: loop
46:
47: lbldetailedlisting.text &= "</ul><p>"
48: loop
49: end sub
50:
51: </script>
52:
53: <html>
54: <body>
55:
56: <font size=+1><b><u>project listing:</u></b></font><br>
57: <asp:label runat="server" id="lblprojectlisting" />
58: <p>
59:
60: <font size=+1><b><u>detailed project listing</u>:</b></font><br>
61: <asp:label runat="server" id="lbldetailedlisting" />
62:
63: </body>
64: </html>