
Beginning ASP.NET 2.0 With CSharp (2006) [eng]
.pdf
Chapter 14
The values can be False to turn off session state, True to enable it, and ReadOnly to make it read-only. The ReadOnly property can also be applied to the Page directive on individual pages as follows:
<%@ Page enableSessionState=”ReadOnly” %>
It should be noted that for low-user sites, turning off session state probably won’t make much difference, but it could have an impact on a high-performance site with a large number of active users. You should also make sure that developers know that session state is turned off for a site, otherwise they may try to code for it when it isn’t available.
View State
The way ASP.NET server controls retain their state is a great boon for the programmer and makes construction of web sites far easier than it used to be. The retention of state is called the view state and is stored within a hidden control on the page, so no extra programming is needed. This does, however, come with a downside, and that downside is that the size of the page increases, which in turn leads to a slower-loading page. The advantages of view state outweigh the disadvantages, but it’s useful to understand how it works. Take a look at the Wrox United home page, which contains many controls. There are some links at the top, a menu and the login and shopping cart on the left, and then the main content, which consists of the news. All of these are handled by controls, and only the central content is specific to the individual page — the rest is supplied by the Master page, and therefore appears on every page.
This is important from the performance perspective because the view state for the menu is 2780 bytes. That might not seem like a lot, but it’s sent on every page request and returned on every postback. Plus that’s just the menu control; others have view state too. Essentially any control that participates in postback will probably have view state and thus the more controls, the more view state.
Like session state, view state can also be disabled, both at the page and control level, using the EnableViewState attribute. To enable this for all controls on a page (or the Master page) you modify the Page directive as follows:
<%@ Page EnableViewState=”False” %>
For controls, the attribute is the same. For example, the menu control in site.master could have view state disabled like so:
<asp:Menu id=”MyMenu” runat=”server”
EnableViewState=”False” />
The menu continues to work as before, but now it has no view state so the page has more than 2000 bytes less to transmit every time. This technique can be applied to other controls, especially those such as grids or lists. One case is the Players page (players.aspx), which uses a DataList to show the player details, and has no interaction. Turning off view state for this control reduces the page size by another 2700 bytes.
In fact, many of the controls don’t actually need view state, and those that interact with the page might be able to have view state disabled. This is different from previous versions of ASP.NET where disabling view state stopped some controls from working. View state can be disabled for the entire application in a similar way to session state, by modifying the pages element in the Web.config file:
538

Performance
<pages enableViewState=”false” />
If this was done, you could then turn on view state for those controls that need it.
Later in the chapter, you’ll look at how you can find out how large controls are and how much view state they store. But first, the next section looks at how you can use code to improve performance.
Pages and Code
Many techniques can be used in code to help with performance, from a simple use of framework classes to a properly architected application. Some techniques will only come from experience, some will only be relevant to certain applications, and some are things you should generally know.
One thing you should know is the .NET Framework class library. You don’t have to know it inside out, nor remember all of the classes, but it’s a good idea to familiarize yourself with the classes and namespaces. It’s definitely worthwhile spending some time just browsing through the reference documentation, getting a feeling for what’s possible. For example, many string handling routines are built right into the String object, and there are data type conversion classes, collections for storing multiple copies of objects, and so on.
Data Binding and Postback
If you aren’t using data source controls for your data binding, one thing you should make sure you do is to only bind when you need to. Data binding involves fetching the data from the database (and as previously mentioned, data access is expensive) and binding each row and column from the data to the rows and columns in a control. There’s no point doing all of that data access and binding unless it’s absolutely necessary. For example, consider the following code:
protected void Page_Load(object Sender, EventArgs e)
{
GridView1.DataSource = SomeCollection; GridView1.DataBind();
}
This simply binds a GridView control to a collection of data (the data is irrelevant for this discussion), but remember that if the page has buttons that post back, then the Page_Load event is run every time, so the data is bound every time. If the grid isn’t going to change, and it retains its state, why rebind it? Using the IsPostBack property can avoid this:
protected void Page_Load(object Sender, EventArgs e)
{
if (!Page.IsPostBack)
{
GridView1.DataSource = SomeCollection; GridView1.DataBind();
}
}
539

Chapter 14
Now the grid is only bound the first time the page is loaded. This may seem like an obvious point, but you’d be surprised how easy it is to forget. Remember that database access is relatively expensive — it consumes both time and resources.
If you are updating data and you do need to rebind the grid after a postback, you can bind in the button event as well, after the data has been changed. What you don’t want to do is bind the grid in when the page loads, update the data, and then rebind the grid again.
Object References
One standard performance optimization is the use of references to objects or items in collections. For example, imagine you are processing some user input, perhaps a name, entered into the text box with an ID of NameTextBox. You have to work with the entered name in several places in some code, so you do this:
ValidateName(NameTextBox.Text);
UpdateDatabase(NameTextBox.Text);
DisplayName(NameTextBox.Text);
You can see that the Text property of the name is referenced three times. What happens if you realize the user could have entered leading and trailing spaces, but that your routines need these stripped off? You might be tempted to just trim the spaces each time, like so:
ValidateName(NameTextBox.Text.Trim());
UpdateDatabase(NameTextBox.Text.Trim());
DisplayName(NameTextBox.Text.Trim());
A much better solution is to only reference the name once, storing the value in a local variable as follows:
string Name = NameTextBox.Text.Trim();
ValidateName(Name);
UpdateDatabase(Name);
DisplayName(Name);
Not only is this easier to read, but it’s also more efficient — you’re performing the Trim once, and only referencing the Text property once.
In general, this technique should be used wherever you reference objects properties, or call methods, several times. There’s probably very little performance improvement for referencing a Text property a few times, but it does sometimes aid readability, and it would certainly improve performance in a highvolume site. Consider the search box on Amazon — how many times a day does that get used?
StringBuilder Versus String Concatenation
Joining strings is a common occurrence and the technique most people use is concatenation, like so:
string FirstName = “Bill”; string LastName = “Barker”;
string FullName = FirstName + “ “ + LastName;
540

Performance
Here the plus sign (+) is used to join strings. The FullName is created from the FirstName, a space, and then the LastName. With just these three strings, trying to improve performance probably isn’t worthwhile, but when more strings are added, there is a significant overhead. The reason is that strings are immutable, which means that after they are created, they can never be changed. For example, consider creating a mail message to send to new members of the Wrox United fan club:
string Message = “Dear “ + MemberName +
“. Welcome to the Wrox United Fan Club. As a member you’ll” +
“receive regular newsletters to keep you up-to-date with the” +
“activities of the club, special discounts in the club store,” +
“advance notice of special events, and many more exciting freebies.”;
EmailMessage.Body = Message;
This is fine as it stands because most of this is literal strings — that is, text within quotation marks. When literal strings are concatenated, the compiler will join them so there is no overhead. But what if the strings are user-supplied? For example, you might have the following code, where the strings are taken from a Web Form:
string Address = AddressLine1.Text + AddressLine2 + AddressLine3 +
AddressLine4.Text + AddressLine5.Text;
This also is fine, and despite what you might read elsewhere, this is still very efficient because the compiler knows how many strings there are and can optimize the code for you. We’ve mentioned these two scenarios because certain books and articles explicitly state that this code is very inefficient — ignore them, because the code is fine. Where you might have a performance problem is when the number of strings is large (more than 10) or unknown. For example, imagine a string being built from some rows in a database (returned as a SqlDataReader, rdr), where you might have the following code:
string str;
while (rdr.Read())
str += rdr.GetString(0);
return str;
This loops while the reader has some data and appends the data from column 0 to the string variable str. As it stands, this is very inefficient because the compiler doesn’t know how many strings there will be, so it cannot perform any optimization. Although this looks innocuous, you have to remember that strings are immutable, so after they are created, they are never changed. The preceding code, therefore, doesn’t really append one string to another — it actually creates a new string made up from the two being appended. This new string is then assigned to str, and this happens each time through the loop. To improve performance, the StringBuilder class can be used, which is in the System.Text namespace:
using System.Text;
StringBuilder sb = new StringBuilder(); while (rdr.Read())
sb.Append(rdr.GetString(0));
return sb.ToString();
541

Chapter 14
When the StringBuilder object is created, it allocates a buffer of memory, and as strings are appended to it using the Append class, the buffer gradually fills up. When full, another chunk of memory is allocated, ready for more strings, so memory allocation happens infrequently and in chunks. This is far more efficient than appending strings to each other where the memory allocation happens for each string.
In general, you should use a StringBuilder object where you are appending a large number of strings, or where the number of strings is unknown.
Picking the Right Collection
Numerous collections exist and at first glance some seem just as good as others, but you need to carefully consider the use of the collection to ensure optimal performance. The following list describes the collections and what their best uses are:
The ArrayList dynamically resizes as items are added and is best used to store custom object types when the data changes frequently — for example, when you are performing frequent inserts and deletes.
The HashTable is a collection of pairs of data (a key and a value) and is best suited for data that doesn’t change frequently. It is especially useful for data that is queried often.
The ListDictionary should only be used to store small amounts of data, generally fewer than 10 items.
The NamevalueCollection is a sorted collection of keys and values and is best for data that is already sorted. It is efficient for data that frequently changes or needs to be queried often.
The Queue provides first-in, first-out storage, so it should be used when sequentially ordered data is required.
The SortedList stores keys and values in a sorted order. This makes adding data slow, because existing items need to be rearranged to ensure the new item is in the correct order. A SortedList is best for data that doesn’t change often.
The Stack provides first-in, first-out storage, so it should be used when sequentially ordered data is required.
The StringCollection is a strongly typed ArrayList, so it is useful for storing strings in an arbitrary order, and for strings that change frequently.
The StringDictionary is a strongly typed HashTable, so it is useful when strings need to be stored and those strings don’t change often.
Using the correct collection not only improves performance but also reduces the potential for errors. For example, if you need to store strings, use a StringCollection or a StringDictionary, because they only store strings — trying to store another data type results in a compiler error, so your code is protected against storage of incorrect types. A generic list will always be faster than its non-generic equivalent.
Now it’s time to turn away from the coding aspects of performance and look toward a feature that ASP.NET provides for us. Although coding and configuration is still required, it’s not the code itself that will improve performance — rather, it is ASP.NET itself.
542

Performance
Caching
Caching is the term given to keeping a copy of something for later use, and provides a way of avoiding resource-intensive operations. For example, we’ve already talked about databases being expensive in terms of performance, but there’s no real way to avoid them with data-driven sites. So what you can do is minimize database access by fetching data and storing it elsewhere (but not another slow resource such as a file). Another type of caching can just involve operations that don’t need to be done multiple times — storing the result of that operation saves having to redo it when required.
Caching can be used in many ways, but luckily ASP.NET has made some of these easier.
Page Caching
You already know that ASP.NET pages are compiled, but you probably haven’t thought much about it. After all, it works, so what’s the big deal? Well, knowing how it works means you can understand how page caching works, and means you can use it effectively. The following set of diagrams show how the compilation works and how it affects caching. Figure 14-4 starts with the first request of a page, which is compiled and stored on disk in intermediate language — this isn’t a fully compiled executable, but is a shortened source code format. The .NET runtime then compiles the intermediate code, which executes, and the result of that execution is the HTML that is returned to the browser.
Disk
ASP.NET page |
Compiler |
|
.NET CLR
Runtime
Intermediate Code
Figure 14-4
The second time the page is requested (see Figure 14-5), assuming the source hasn’t been changed, the intermediate code is still available, so the compilation stage is bypassed.
543

Chapter 14
Disk
ASP.NET page |
Compiler |
|
.NET CLR
Runtime
Intermediate Code
Figure 14-5
Already we have an improvement, just by the intermediate code being cached. What can also happen is that the page output can be cached, if designed as part of the page. When the intermediate code is compiled, the HTML is not only returned to the browser, but it is also stored on disk, as shown in Figure 14-6.
Disk
ASP.NET page |
Compiler |
|
Disk
.NET CLR
Runtime
Intermediate Code
Figure 14-6
544

Performance
Now you have a great situation, because subsequent requests for the page simply get the HTML from the cache. All areas of compilation are avoided, as illustrated in Figure 14-7.
Disk
ASP.NET page |
Compiler |
|
Disk
.NET CLR
Runtime
Intermediate Code
Figure 14-7
Now take a look at how you can implement this type of caching.
Output Caching
Output caching is the simplest of the page caching techniques shown in the previous section, whereby the entire page is cached. Give this a go in the next Try It Out to see how easy it is.
Try It Out |
Output Caching |
1.In VWD, open History.aspx.
2.As the first line in the content, above the existing h1 element, add the following:
<div class=”boxFloatRight”><%=DateTime.Now%></div>
3.Save the file and run the application, navigating to the History page under the About menu. Notice the time to the right of the title, and press F5 to refresh the page. Press F5 several times, noting that the time is updated.
4.Close the browser. In VWD, edit the History.aspx page again, adding the following line, near the top of the page, underneath the @Page directive:
<%@ OutputCache Duration=”30” VaryByParam=”none” %>
545

Chapter 14
5.Save the file and re-run the application. Try pressing F5 several times and notice that now the time isn’t updated. Wait at least 30 seconds and refresh the page again — notice that the time has changed.
How It Works
Output caching works as shown in Figures 14-4 through 14-7. The OutputCache directive tells ASP.NET that after the output of the page has been created, it is to be stored in the cache. The Duration is the time in seconds that the page is to remain in the cache — any requests within those 30 seconds are served from the cache. After the 30 seconds are up, the cached page expires and is removed from the cache. The next request will re-execute the page, whereupon it is placed into the cache again. (The VaryByParam attribute is discussed in a just a bit.)
The div displaying the current time shows the time the page was executed. So the first time you request the page, the current time is shown, because this is when the HTML is generated. But because the HTML is cached, subsequent views of the page receive the cached HTML, which of course has the time fixed.
It’s only when the page is removed from the cache that it’s re-executed and the time updated.
The advantage of this system is that pages that are used frequently will be often cached, whereas pages that aren’t used often get discarded from the cache.
The VaryByParam attribute dictates whether any external influences affect how the page is cached. Setting the value to none means that nothing affects the caching — only one copy will be cached. However, what about pages that allow selections and fetch data based on those selections? One example is the Fixtures page, which can either show past fixtures or future fixtures, depending on a value passed in as part of the querystring. If VaryByParam=”none” is used, only one copy of the page would be cached, so the first Fixtures page requested would be cached. Say two people view the fixtures one after another, and that the first view is for future fixtures, which would be cached. If the second person requests past fixtures, he or she would be returned the cached page, which was for future fixtures. In fact, with this model, the past fixtures wouldn’t be viewable until the first page was evicted from the cache.
To get around this, VaryByParam can be set to the name of the querystring variable, which means that a copy of the page would be cached for each different value of the variable. This could be implemented in the Fixtures page simply by adding the following cache directive:
<%@ OutputCache Duration=”30” VaryByParam=”type” %>
The two menu entries for fixtures have the following as their URLs:
Fixtures.aspx?type=future
Fixtures.aspx?type=past
Now when two people request the different fixture pages, two copies are stored in the cache, one for each type.
Fragment Caching
Fragment caching enables portions of a page to be cached while the rest of the page remains dynamic. This is ideal for those pages that contain a mixture of static and dynamic content, and is achieved by
546

Performance
having the content that is to be cached contained within a user control. The user control then uses the OutputCache directive to dictate how it should be cached, in the same way that pages use it:
<%@ Control OutputCache Duration=”30” VaryByParam=”none” %>
When the user control is placed on the page, only the content for the user control will be cached, allowing the rest of the page to remain dynamic.
This is useful for pages where the data is very dynamic and changing often and you don’t want the page cached, but there is some content (perhaps from a database) that doesn’t change often and can be cached.
Post Cache Substitution
Post cache substitution is the opposite of fragment caching, where the page is cached but a portion of the page is dynamic. This is achieved with the Substitution control, which works differently from other forms of caching. You still use the OutputCache directive on the page to determine the caching, but the Substitution control is simply a placeholder into which the content is to be placed, and this content has to be created manually. The Substitution control has a property called MethodName that indicates a function that will return a string of data to be substituted into the cached page. For example, the page would be as follows:
<%@ Page CodeFile=”PCS.aspx.cs” Inherits=”PCS” %> <%@ OutputCache Duration=”30” VaryByParam=”none” %> <html>
<form runat=”server”>
<div class=”boxFloatRight”><%=DateTime.Now%></div>
<asp:Substitution id=”sub1” runat=”server” MethodName=”Substitute” /> </form>
</html>
The code-behind would be as follows:
partial class PCS : System.Web.UI.Page
{
static string Substitute(HttpContext myContext)
{
return “Date fetched on “ + DateTime.Now.ToString();
}
}
When this page is run, it will be cached, so the div with the date and time will remain the same. But each time the page is requested, the Substitute method will be called, so the data that it returns will not be cached. Here’s how it works.
The first time the page is requested by a user, the normal caching regime is applied — the page will be placed into the output cache. However, because there is a Substitution control on the page, the output cache keeps a note of the MethodName (for each Substitution control if there is more than one on the page). When the page is next requested, instead of being returned directly from the cache, the method detailed in MethodName is called, which changes the contents of the Substitution control. At this point, it is integrated with the copy of the page in the output cache and returned to the user. In this case, you get the benefit of output caching but with the flexibility of dynamic content.
547