Caching is an important part of Web application development. Use caching fully and correctly and your application will yield optimal performance and reliability. Ignore caching and your application will pale in comparison with those of your competitors . If you are providing an online service, like shipping, your customers are likely to notice the effects of the lack of caching and choose another provider next time. (Personally, I prefer vendors that have responsive online services, and I avoid those that don't.) Reliability and responsiveness is a facet of customer service, and we have learned that service wins and keeps customers.
There are several facets to caching in ASP.NET. When we talk about caching, we might be referring to the HttpApplicationState , HttpSessionState , or Cache classes. Session state might be managed with either an in-process state server or one of two out-of-process state servers: SQL Server and aspnet_state.exe . We might be referring to cookies and the view state. We might also be referring to page or partial page caching. We'll explore some of these facets in the following subsections.
Using the HttpApplicationState Class
Application state is used to share information across multiple requests and sessions. This cache is accessible via an HttpContext or Page object. The property is named Application and is defined as an HttpApplicationState object, which inherits from NameObjectCollectionBase . With all NameObjectCollectionBase classes, you index the collection with a unique name and stuff an object (any data) into that index location. You can store simple objects (like integers or arrays of strings) as well as complex objects (like collections and DataSets ). What you put in the application cache depends on what you want to share across the application.
For example, on the Software Conceptions Web site (http://www.softconcepts.com) we list all the books I have authored or coauthored. This information does not vary depending on the session. Various offerings are shown on different pages through the site. To make the data easy to manage I can store images, hyperlinks , and descriptions in a database; load the data into a collection of books; and store it in the application session. The list of books can be more correctly retrieved from the Application session cache.
There are several pieces to this solution. The first piece is the strongly typed collection of Book objects, named Books . The second piece demonstrates how to put the Books collection in the application cache. Assuming we paid for an expensive database readinstead of the in-memory typed collectionwe would get a performance improvement, reading the database only one time for all users. The third piece is a user control that describes how one book should be displayed. The fourth piece is a bit trickier; I am actually using a user control inside of a DataList . The result is that the DataList manages repeating the user control for us. (I will go over this code carefully .) Finally, the user control containing the DataList is added to the default.aspx page.
Implementing the Books Collection
You already know how to implement a strongly typed collection (from Chapter 14 and earlier in this chapter). The Books class is a strongly typed collection of Book objects, containing information you might find useful to display any product in a Web page. Listing 15.17 contains the implementation of the Books and Book classes. Of course, in a production application you could just as easily read this information from a database or XML file. Everything else we will do in this section works equally well, whether you use an XML file, a DataSet object, or a strongly typed collection.
Listing 15.17 The Books Collection and the Book Class
Public Class Books Inherits System.Collections.ReadOnlyCollectionBase Public Shared Function MostRecentThree() As Books MostRecentThree = New Books() MostRecentThree.Add( _ New Book("Visual Basic .NET Power Coding", _ "Paul Kimmel", New DateTime(2003, 1, 1), _ "Addison-Wesley", _ "http://www.aw.com/catalog/" + _ "academic/product/1,4096,0672324075,00.html", _ "")) MostRecentThree.Add( _ New Book(".NET Mobile Application Development", _ "Paul Kimmel", New DateTime(2003, 1, 1), _ "Wiley", _ "http://www.wiley.com/cda/product/0,,0764548506,00.html", _ "~/Books/Images/Wireless.jpg")) MostRecentThree.Add( _ New Book("Advanced C# Programming", _ "Paul Kimmel", New DateTime(2002, 8, 15), _ "McGraw-Hill/Osborne", _ "http://shop.osborne.com/" + _ "cgi-bin/osborne/0072224177.html", _ "~/Books/Images/csharp_dev_guide.jpg")) End Function Default Public ReadOnly Property Item( _ ByVal Index As Integer) As Book Get Return CType(InnerList(Index), Book) End Get End Property Public Function Add(ByVal Item As Book) As Integer Return InnerList.Add(Item) End Function End Class Public Class Book Private FTitle As String Private FAuthor As String Private FPublicationDate As DateTime Private FPublisher As String Private FUrl As String Private FBookCoverLink As String Public Sub New(ByVal Title As String, _ ByVal Author As String, _ ByVal PublicationDate As DateTime, _ ByVal Publisher As String, _ ByVal Url As String, _ ByVal BookCoverLink As String) Me.FTitle = Title Me.FAuthor = Author Me.FPublicationDate = PublicationDate Me.FPublisher = Publisher Me.FUrl = Url Me.FBookCoverLink = BookCoverLink End Sub Public ReadOnly Property Title() As String Get Return FTitle End Get End Property Public ReadOnly Property Author() As String Get Return FAuthor End Get End Property Public ReadOnly Property PublicationDate() As DateTime Get Return FPublicationDate End Get End Property Public ReadOnly Property Publisher() As String Get Return FPublisher End Get End Property Public ReadOnly Property Url() As String Get Return FUrl End Get End Property Public ReadOnly Property BookCoverLink() As String Get Return FBookCoverLink End Get End Property End Class
I presented all the code in Listing 15.17 for completeness. This is the kind of code you might find in a business object layer. The only code likely to confuse you is the MostRecentThree method.
MostRecentThree is a shared method. Shared methods that create instances of classes are referred to as factory methods . This factory method initializes an instance of the containing class, Books . Clearly we could initialize the class from a data repository. In the listing I am using hard-coded values. I don't need to remind you not to do that in a production application. The part that might seem odd is the old usage of the function name as the return value MostRecentThree . I prefer not to use temporary variables . However, here I do need a Books variable. In Visual Basic .NET, like VB6, we have a convenient temporary variable, the function name. This is a technique I employ sometimes. As you know, if you do declare a temporary variable, you can return it by assigning it to the function namethe old wayor use the Return keyword followed by the temporary variable.
Adding the Books Object to the Application Cache
I haven't found any notable limitations when it comes to adding data to the application cache. However, I must mention that I don't usually put a lot of data in the application cache. The application cacherepresented by the Application propertycan be used to store data shared between sessions and contexts in your application. That is, the data is effectively global. I generally put read-only data in the application cache.
Listing 15.18, the Global.asax file, demonstrates how I use the application cache to store the MostRecentThree books. As a result the code reads the Books collection from the cache for all users at once, rather than recreating the object for each user. The impact is minimal in this specific instance because the Books objects all reside in memory. The performance would improve markedly if we were reading a lot of data from a database.
Listing 15.18 Storing the Books Collection in the Application Cache
1: Imports System.Web 2: Imports System.Web.SessionState 3: 4: Public Class Global 5: Inherits System.Web.HttpApplication 6: 7: [ Component Designer generated code ] 8: Sub Application_Start(ByVal sender As Object, _ 9: ByVal e As EventArgs) 10: Application("MostRecentThree") = Books.MostRecentThree 11: End Sub 12: 13: [+]Sub Session_Start(ByVal sender As Object, _ 14: ByVal e As EventArgs) 15: Sub Application_BeginRequest(ByVal sender As Object, _ 16: ByVal e As EventArgs) 17: Context.Items("ColorInfo") = New ColorInfo(LinkID) 18: End Sub 19: 20: Private ReadOnly Property LinkID() 21: Get 22: If (Request.QueryString("LinkID") Is Nothing = False) Then 23: Return Convert.ToInt64(Request.QueryString("LinkID")) 24: Else 25: Return 1 26: End If 27: End Get 28: End Property 29: 30: [+]Sub Application_AuthenticateRequest(ByVal sender As Object, _ 31: ByVal e As EventArgs) 32: 33: [+]Sub Application_Error(ByVal sender As Object, _ 34: ByVal e As EventArgs) 35: [+]Sub Session_End(ByVal sender As Object, ByVal e As EventArgs) 36: [+]Sub Application_End(ByVal sender As Object, _ 37: ByVal e As EventArgs) 38: End Class
The [+] marker represents code outlining. I collapsed the empty methods we are not discussing. Note that Application_BeginRequest and the LinkID property were modified earlier in the chapter (see Listing 15.16 and its discussion), and I left them in place rather than collapsing them to illustrate how even the Global.asax file will take on mass as a project develops.
Lines 8 through 11 show Application_Start ; the new code (compared with Listing 15.15) appears set in bold. When the application begins I stuff the Books collection returned by the factory method Books.MostRecentThree into the application cache. Now all users will have equal access and the performance hit will be divided by the number of users. A reasonable person might shout, "Hey, that's way too easy!" That reasonable person would be right. This is precisely how frameworks help us. The framework should do the work. Underneath the covers is a lot of code, but Microsoft wrote that for us.
This is my opportunity to pitch you. You want your code to converge and synthesize to the point where fundamental operations in your application are very simple. If you discover methods with dozens of lines, you are likely looking at hack code. Sometimes, especially while prototyping, it is okay to hack a bit. However, when the solution is understood , it is time to take out your patterns and refactoring books and synthesize the hacked code to a tidy class that is easy to use.
Defining the BookControl
The BookControl is a user control in the solution that describes the layout of a single Book listing. The visual presentation is basically a table with three data cells and some spacer cells (Figure 15.15) that displays a graphic of the cover of the book and some details about the book. We already explored how to use an HTML table to constrain layout. The interesting piece of information comes in the next section. For now, I will show you the HTML for the BookControl (Listing 15.19) and the code-behind file (Listing 15.20).
Listing 15.19 The BookControl.ascx File as HTML
<%@ Control Language="vb" AutoEventWireup="false" Codebehind="BookControl.ascx.vb" Inherits="softconcepts_vb.BookControl" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %> <TABLE id="Table1" cellSpacing="0" cellPadding="0" width="262" border="0" style="WIDTH: 262px; HEIGHT: 180px"> <TR> <TD rowSpan="2" vAlign="top" align="left" width="120"> <asp:Image id="Image1" runat="server" Height="140px" Width="120px"></asp:Image></TD> <TD width="5" rowSpan="2"></TD> <TD vAlign="top" align="left"> <asp:HyperLink id="HyperLink1" runat="server" CssClass="BookContent">HyperLink</asp:HyperLink></TD> </TR> <TR> <TD vAlign="top" align="left"> <asp:Label id="Label1" runat="server" CssClass="BookContent">Label</asp:Label></TD> </TR> <TR> <TD colSpan="3" height="10"></TD> </TR> </TABLE>
Figure 15.15. The BookControl design, based on a table.
(I will never get over how ugly HTML is.) You can determine from the @ directive that this is a control associated with the code behind the BookControl.ascx.vb file. You can also pick up tidbits about the layout, the controls in the table, and the class. I used the BookContent class from the softconcepts.css style sheet. The table itself contains an ASP.NET image, hyperlink, and label. We will bind data to these in the code-behind file (Listing 15.20).
Listing 15.20 The Code-Behind File for BookControl.ascx.vb
Public MustInherit Class BookControl Inherits System.Web.UI.UserControl Protected WithEvents Image1 As System.Web.UI.WebControls.Image Protected WithEvents HyperLink1 As _ System.Web.UI.WebControls.HyperLink Protected WithEvents Label1 As System.Web.UI.WebControls.Label Private FData As Book [ Web Form Designer generated code ] Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load 'Put user code to initialize the page here End Sub Public Property Data() As Object Get Return FData End Get Set(ByVal Value As Object) SetData(Value) End Set End Property Private Sub SetData(ByVal value As Object) FData = CType(value, Book) Image1.ImageUrl = FData.BookCoverLink Image1.AlternateText = FData.Title Label1.Text = FData.Title HyperLink1.Text = FData.Title HyperLink1.NavigateUrl = FData.Url HyperLink1.Target = "_blank" End Sub End Class
Taken alone the code is very straightforward. Consuming code needs to pass an instance of a Book object to the Data property. All the necessary attributes of the controls are set from this one object. So far so good. The next subsection demonstrates how to bind controls to methods via properties, providing exceptional flexibility and control.
Defining the BookControlList
Assigning one instance of an object to a control or a user control is not too difficult, but what if you want to repeat a complex object? Assigning an object to a DataGrid or DataList control is not difficult either, but what if you don't want the basic row and column presentation of a DataGrid or DataList ? You could certainly guess at the number of times a user control is repeated and manually add that number of user controls to a page. Or you could go one step further; you could dynamically load a user control based on the number of elements, which would be a clever solution. Yet the best solution is to let a control like DataList figure all of that out for you.
You can take an object as complex as a user control. Plop it into the ItemTemplate of a DataList control and automatically create and populate as many user controls as there are rows in the data source. Perhaps best of all, the data source can still be any suitable data source, like a typed collection, an XML file, or a DataSet object. This is precisely what we did with the BookControl .
To display a list of Books objects I created a new user control called BookListControl . BookListControl is initialized with a collection of books. The visual presentation is an HTML table with a DataList control. The ItemTemplate for the DataList is the BookControl we created in the preceding section. This part is easy to create. Getting the data to each dynamically created BookControl is the trick. To create the BookListControl follow these steps.
Listing 15.21 contains the code-behind file for BookListControl . Listing 15.22 contains the HTML for BookListControl and the key to repeating user controls automatically.
Listing 15.21 The Code-Behind File for BookListControl.ascx
Public MustInherit Class BookListControl Inherits System.Web.UI.UserControl Protected WithEvents DataList1 As _ System.Web.UI.WebControls.DataList [ Web Form Designer generated code ] Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load DataList1.DataSource = _ CType(Application("MostRecentThree"), Books) DataList1.DataBind() End Sub End Class
This code is very straightforward. The MostRecentThree Books object is retrieved, typecast to its original type, and assigned to the DataList1. DataSource property. To display the data we call DataList1.DataBind . Each Book object in Books gets to the user controls that are dynamically created in the HTML.
Here is Listing 15.22 with that promised key.
Listing 15.22 The HTML Implementation of BookListControl
<%@ Register TagPrefix="uc1" TagName="BookControl" Src="BookControl.ascx" %> <%@ Control Language="vb" AutoEventWireup="false" Codebehind="BookListControl.ascx.vb" Inherits="softconcepts_vb.BookListControl" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %> <TABLE id="Table1" cellSpacing="0" cellPadding="0" width="300" border="0"> <TR> <TD> <asp:DataList id="DataList1" runat="server"> <ItemTemplate> <uc1:BookControl id=BookControl1 runat="server" Data="<%# Container.DataItem %>"> </uc1:BookControl> </ItemTemplate> </asp:DataList></TD> </TR> </TABLE>
By now you are familiar with all of this code except perhaps the attribute set in bold. Each item in the ItemTemplate is a BookControl object. BookControl has a public property Data (see Listing 15.20). For each object on the data source, controls in the ItemTemplate are dynamically created. Each of these controls is a BookControl object. Each object, represented by Container.DataItem , is assigned to the BookControl.Data property. The private SetData method propagates all the data to the individual controls on the template user control. As a result there is an instance of BookControl for each object in the collection, and that BookControl 's Data property is set in the script block shown in the listing.
Viewing the Finished Result
Finally we are ready to use the control in a page. Fortunately for us and every subsequent consumer, the BookListControl is ready to go as is. All we have to do is place it on a page and it renders correctly. Figure 15.17 shows the BookListControl rendered with a modicum of styles and layout information applied. Ideally if you read the most recent books from a database, all you would have to do is update the database to update the Web site.
Figure 15.17. The BookListControl at runtime.
Perhaps the best benefit of using user controls is that we can nest, layer, composite, and repeat them to an almost unlimited degree of complexity. There is no reason why you can't use nested controls with several layers of related controls and even DataLists of DataLists for very complex layouts. The code, on the other hand, is relatively easy to write.
Using the HttpSessionState Class
The HttpSessionState class is a key and value repository accessible from an HttpContext object, Web page, or user control. The session cache works just like the application cache: provide a key as an index and read or assign a value to the Session property. The difference is in how and why you use one over the other. Use the application cache when you want to share something at the application level, among all users. Use the session cache when you want to store data pertinent to one session. For example, you might use the session cache to track a user's shopping cart. As is true with the application cache, you can stick just about anything in the session cache.
There are three ways to store session state in ASP.NET. The simplest and default means of storing Session objects is the built-in in-process session server. A second way to store session state is to use the out-of-process aspnet_state.exe state server. A third way is to configure and use SQL Server. Each state-managing modality has advantages and disadvantages.
The subsections that follow discuss the various session state servers you can use and provide examples, demonstrating how to store information in the session. (Each example could be used with any of the session servers; I simply offer three distinct examples.)
Configuring In-Process State Servers
Each session is associated with a session ID. By default the session ID is stored in a cookie on the client computer. If the client doesn't support cookies, the session ID can be embedded in the URL. For example, cookieless sessions are needed when using the session cache for wireless devices like cell phones. Wireless devices don't support cookies.
For each kind of session server, a unique session ID exists. This is true whether the device supports cookies or not. The default session server is the in-process session server built into the aspnet_wp.exe worker process application. The in-process session server is fast, easy to use, and configured by default. Listing 15.23 supposes that we want to pass information entered on one page to the next page in the sequence. This represents tracking a process that might span several pages, like a customer order. The code can be found in SessionDemo.sln .
Listing 15.23 Adding Simple Text to the Session Property
Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Session("Text") = TextBox1.Text Server.Transfer("Next.aspx") End Sub
The code relies on a TextBox control and a Button control. When the button is clicked, the text is read from the text box and stored in the session cache through the Session property. The text will be unique to each session. Then, the efficient Server.Transfer method is called, transferring immediately to the page named Next.aspx . We can read the property from the session with code similar to the fragment shown in Listing 15.24.
Listing 15.24 Reading the Session Value from Another Location in the Application
Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Response.Write("You entered " + Session("Text")) End Sub
The in-process session cache is configured by default in the Web.config file. Listing 15.25 shows the excerpt from the Web.config file, followed by a brief summary.
Listing 15.25 Managing Session State Servers
<sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;user id=sa;password=" cookieless="false" timeout="20" />
The <sessionState> tag's mode attribute can be one of four values: Off , InProc , StateServer , or SQLServer . Off indicates that session state is disabled. InProc indicates that the in-process session state server should be used. (This is the default.) StateServer indicates that the aspnet_state.exe server application will be used, and SQLServer indicates that SQL Server will be used to manage state.
Caching the stateConnectionString indicates the IP address and port of the machine running the aspnet_state.exe server. To store state information on a separate machine, use that machine's IP address. By default the localhost address 127.0.0.1 and port 42424 are used. The sqlConnectionString attribute is used if the mode is SQLServer . By default cookies are used to store the session ID, and the session cache data expires in 20 minutes. If you change the cookieless attribute to true and run the sample program, you will see the session ID embedded in the URL. When we talk about the other two state servers, we will be modifying values in the Web.config file's <sessionState> tag to indicate that we want to use one of the other servers. Figure 15.18 shows the session ID for my session embedded in the URL (highlighted) in the Address bar.
Figure 15.18. Cookieless session state management permits you to use session state with devices that don't support cookies or have cookies disabled.
Configuring the Out-of-Process State Server
The out-of-process state server is a bit slower but it will tolerate both IIS and aspnet_wp (worker process) restarts. The aspnet_state.exe server is suitable if slower performance is acceptable to ensure that state information is maintained even if IIS or the worker process has to be restarted. You can configure the out-of-process state server to run by changing the mode attribute to StateServer . You need to change the stateConnectionString attribute only if you want to store state on a computer other than the one the Web application is running on.
You will also need to start the aspnet_state.exe state server. You can quickly start the state server by opening the Visual Studio .NET Command Prompt and typing net start aspnet_state.exe , or you can modify the services settings and reconfigure the aspnet_state.exe service to start automatically (Figure 15.19) when your computer boots up. (If you plan to use the out-of-process state server, you will want the service to start automatically.) Listing 15.26 demonstrates how you can place an entire DataSet into the session cache.
Listing 15.26 Storing a DataSet in the Session Cache
Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Const ConnectionString As String = _ "Provider=Microsoft.Jet.OLEDB.4.0;" + _ "Data Source=C:\Program Files\Microsoft " + _ "Visual Studio\VB98\NWIND.MDB;Persist Security Info=False" Dim Adapter As OleDbDataAdapter = _ New OleDbDataAdapter("SELECT * FROM ORDERS", _ ConnectionString) Dim DataSet As DataSet = New DataSet() Adapter.Fill(DataSet) Session("DataSet") = DataSet End Sub
Figure 15.19. Configure the aspnet_state.exe state server to run automatically from the ASP.NET State Service Properties dialog.
Listing 15.26 creates an OleDbDataAdapter object and uses it to fill a DataSet . The last statement inserts the entire DataSet into the session cache. This example was tested with the ASP.NET state server. The data needs to be serializable in order to put the data in the session. Simple data and classes that use the Serializable attribute or implement the ISerializable interface can be serialized. This covers a lot of ground in .NET. I haven't tested every imaginable kind of data, yet I have not had any specific problems putting data in the session cache.
Configuring SQL Server for Session Management
SQL Server offers the greatest scalability for storing session information. In addition to sustaining session information between IIS and aspnet_wp.exe worker process restarts, SQL Server is intended to be used in multiprocessor or multicomputer Web farms. The mechanics of using the session cache are the same whether you are using the in-process session cache or SQL Server. However, you will first have to configure the SQL Server instance as a repository for the session cache and modify the Web.config file.
To configure SQL Server as the session state repository you need to run the C:\winnt\Microsoft .NET Framework\ version \InstallSqlState. sql script. (This is the default directory, where version represents the version of the .NET Framework you are using.) You can run this query with Microsoft's SQL Server Query Analyzer (which works fine) or with isqlw. exe (which also works fine). The InstallSqlState.sql script will add a new database, ASPState, to your SQL Server instance. ASPState contains stored procedures for managing session state. Actual session data will be written as temporary entries in the tempdb database in the ASPStateTempApplications and ASPStateTempSession tables.
Finally, you will need to change the mode attribute to SQLServer and ensure that the sqlConnectionString attribute is correct. If you are testing SQL Server state management on your workstation, the default value shown in Listing 15.25 will work.
After you have configured the SQL Server ASPState table you can rerun SessionDemo.sln (the code in Listing 15.26) to test the SQL Server session state configuration. You will notice a marked slowdown in page response, and you should see rows in the two tempdb tables mentioned a moment ago.
Following the instructions presented in these subsections is a very straightforward process to configure any of the three session state servers.
Using the Cache Class
One instance of the Cache class is created for each application domain. The Cache class is accessible through an HttpContext instance or Page property and exists to facilitate application performance. Unlike the session cache, the Cache property is not associated with a particular session, but it is useful for storing objects that require large amounts of server memory. Data is placed in the Cache class just like data is placed in the session or application caches and exists for the lifetime of the application.
The Cache class allows you to set expiration policies and create external dependencies, expiring or refreshing Cache data based on some external occurrence, like changes to a data file. For more information on using the Cache property and Cache Dependency objects, read the help documentation for the Cache and CacheDependency classes and refer to the example in the Using Dynamic Interfaces with XML section later in this chapter.
Cookies are represented as instances of HttpCookieCollections that contain instances of HttpCookies . These classes represent small bits of textual information stored on each client machine. Suitable for cookie usage are things like the user name of a specific user relative to a Web site. Thus, for example, if you store a user's name on the machine, your application can look for that specific cookie to reidentify the user the next time he or she returns to your Web site.
Cookies are stored as text on a PC, so anyone who has access to your PC can search for cookie files, but only the originating host can read a cookie it stored on your PC. Unfortunately, due to security concerns and worries about applications sniffing for data in cookies, some users disable cookies. As a result, heavy reliance on cookies or storing sensitive information in a cookie is not a recommended practice.
A practical example of cookie use is how .NET uses a cookie to store the session ID for each session by default. However, even .NET does not rely on the universal availability of cookies and allows session IDs to be passed embedded in the URL when cookieless session state management is in use, as it will be for some mobile devices like wireless phones. In the fragment example below, I am creating an instance of HttpCookie keyed by the string "Data" and storing the value of the current date and time as a string in the cookie.
Response.Cookies.Add( _ New HttpCookie("Data", DateTime.Now.ToString()))
At a later time in the program's execution I can retrieve this cookie from the Response.Cookies collection by indexing the Response.Cookies collection with the key "Data" . The example represents a suitable amount of data to store as a cookie, in part because there is a practical limit of 4K that can be stored as a cookie. Due to this size limitation and the fact that some users disable or delete cookie files from their machines, as mentioned above, I don't recommend relying heavily on cookies in your applications.
Enabling and Using the View State
Web Forms controls have a property named EnableViewState . If you rely on the default value of True , the states of Web controls are stored in encrypted text fields along with a page when the page is submitted to the server. The view state is used to reestablish the values of these controls when the page returns from the server.
As a benefit the view state information prevents you from needing to cache and reestablish control values for a page, but encrypting, decrypting , storing, and retrieving view state information takes CPU cycles. As a result, you should leave view state enabled ( EnableViewState=True ) only if you are actually using the view state. If you are assigning the value of a control from an object each time the page is rendered, disable view state for a performance boost. Listing 15.27 provides an excerpt from the rendered HTML for the WebForm1.aspx page, showing in bold the encrypted view state for the controls on this page.
Listing 15.27 An Excerpt from the HTML for a Page with an Encrypted View State
</HEAD> <body MS_POSITIONING="GridLayout"> <form name="Form1" method="post" action="WebForm1.aspx" id="Form1"> <input type="hidden" name="__VIEWSTATE" value="dDwtMTkzOTEyMTQxNzs7Pgsi7Iwyvsw49Yof3poXc4Ka9N+a" />
You can enable or disable the view state for the page in the Properties window or in the HTML view by setting the enableViewState attribute to True to enable it and False to disable it. You can set the view state of individual controls by modifying their EnableViewState properties or manage all the controls and the page view state through the page's EnableViewState property.
Caching a Web Page
If you have a page that contains static content, such as an error page or a page that is not bound to any dynamic data, you can cache the page. Cached pages are rendered once and placed in a cache. Subsequent requests to the page are served by the cached page rather than running the code and recreating a new page for each request. This can boost your application's performance too.
You can indicate that a page is to be cached with the @ OutputCache directive in the HTML view, referred to as declarative caching , or you can cache a page programmatically in the code-behind file, referred to as imperative caching . With declarative caching you get caching statically as described by the @ OutputCache directive. With imperative caching you can change the cache policy at runtime based on some dynamic criteria.
To use declarative page caching add the @ OutputCache directive to the HTML at the top of the text where you will also find the @ Page directive. At a minimum you will need to provide values for the Duration and VaryByParam attributes. Duration indicates how long the page will be cached and is expressed in seconds. VaryByParam must be None at a minimum, or you can supply a parameter name. In this context parameter refers to a query parameter passed in an HTTP GET or POST . Here is an example of the basic OutputCache directive that will cache a page for 10 seconds.
<%@ outputCache Duration="10" VaryByParam="None" %>
You can experiment with CachingDemo.sln . For example, if you change the VaryByParam attribute to cust_id , then when the value of cust_id changed in the GET or POST a new version of the page would be rendered. Users requesting any version of the page with the same parameter value would get the same rendered page unless the cache had expired . You can also choose to set the VaryByHeader and VaryByCustom attributes. VaryByHeader will cache multiple versions of a page based on the HTTP Header information associated with a request. VaryByCustom will cache multiple versions of a page based on the browser type or a custom string value that you must provide.
Caching a Partial Web Page
You can apply the same strategy for caching partial Web pages that you use for caching Web pages. You can use the @ OutputCache directive to cache a user control. This is referred to as partial page caching . You can use partial page caching if you have a user control with data that doesn't change very often. The only limitation is that if you employ partial page caching, you cannot programmatically refer to the user control in the code-behind file because any particular rendering of the control may be a cached version.
If data for a user control is the same for all users unless some condition changes and everyone gets the reflected change, use partial page caching to enhance the performance of your application.