ASP.NET Performance - Real World .NET, C#, and Silverlight: Indispensible Experiences from 15 MVPs (2012)

Real World .NET, C#, and Silverlight: Indispensible Experiences from 15 MVPs (2012)

Chapter 2

ASP.NET Performance

by Bill Evjen

It is one thing to know how to build an ASP.NET application and get everything working as you want. It is another thing to get it working well. As you build web applications today, you must also ensure that the choices you make in the construction of the application also work well with regard to the overall performance of the application.

As your web application takes on more user load, you have options for how you want to deal with the growth. The choices you have can be made either via the code of your application, or via actual hardware changes.

This chapter covers a lot of different ground, but all the items mentioned here have a direct impact on the overall performance of your ASP.NET applications.

This chapter includes discussions on how to deal with state management and caching. Also touched upon are hardware considerations and configuration for your server. Finally, this chapter covers how to monitor your application's performance, because this will help you fine-tune what is wrong, or what could be improved.

Looking at How ASP.NET Handles Page Requests

Before starting on some of the items you can do to your applications to help with performance, it is first important to understand how ASP.NET handles page requests. ASP.NET compiles your ASP.NET pages (.aspx) as they are referenced (for example, by an end user in the browser).

When an ASP.NET page is referenced in the browser for the first time, the request is passed to the ASP.NET parser that creates the class file in the language of the page. It is passed to the ASP.NET parser based on the file's extension (.aspx) because ASP.NET realizes that this file extension type is meant for its handling and processing. After the class file has been created, the class file is compiled into a dynamic link library (DLL) and then written to the disk of the web server. At this point, the DLL is instantiated and processed, and an output is generated for the initial requester of the ASP.NET page. Figure 2.1 shows the details of this process.

Figure 2.1 How ASP.NET handles the initial request

2.1

On the next request, great things happen. Instead of going through the entire process again for the second and respective requests, the request simply causes an instantiation of the already-created DLL, which sends out a response to the requester. Figure 2.2 shows how this is done.

Figure 2.2 How ASP.NET handles subsequent requests

2.2

Because of the mechanics of this process, if you make changes to your .aspx code-behind pages, you must recompile your application. This can be quite a pain if you have a larger site and do not want your end users to experience the extreme lag that occurs when an .aspx page is referenced for the first time after compilation. Consequently, many developers have created their own tools that automatically hit every single page within their application to remove this first-time lag hit from the end user's browsing experience.

ASP.NET provides a few ways to precompile your entire application with a single command that you can issue through a command line. One type of compilation is referred to as in-place precompilation.

To precompile your entire ASP.NET application, you must use the aspnet_compiler.exe tool that comes with ASP.NET. To get to this tool, you will need to navigate to it using the Command window. From the Command window, navigate to C:\Windows\Microsoft.NET\Framework\v4.0.30319\. When you are there, you can work with the aspnet_compiler tool. You can also get to this tool directly by pulling up the Visual Studio 2010 Command Prompt. Choose Start → All Programs → Microsoft Visual Studio 2010 → Visual Studio Tools → Visual Studio Command Prompt (2010).

After you get the command prompt open, enter the following command:

aspnet_compiler -p "C:\Inetpub\wwwroot\WROX" -v none

You then get a message stating that the precompilation is successful.

Another great thing about this precompilation capability is that you can also use it to find errors on any of the ASP.NET pages in your application. Because it hits each and every page, if one of the pages contains an error that won't be triggered until runtime, you are notified of the error immediately as you employ this precompilation method.

The next section looks at a couple of the more important aspects of the performance of your applications — state management and caching. Getting these items right from the start is vital for your application.

State Management and Caching

Simply put, building ASP.NET applications is more difficult than with other types of applications (such as building a Windows Forms application) primarily because web applications are a stateless type of application.

The Internet is stateless by nature. You are simply making requests and responses (generally using the Hypertext Transfer Protocol, or HTTP). The server receives an HTTP request for a particular page, and sends the caller the requested page (the response). The server that is sending the response does not keep track of who made the request. Every request is equal in the server's eyes.

When the same calling application makes a second request, the server gives it the second piece of information, but still does not house any information about this calling application. The server does not know that this application is the same one that just recently requested the first piece of logic.

This creates a problem if you want your web application to remember information about the calling application. Remembering the calling application and being able to make a distinction between requests allows end users to work through an application in a continuous manner. You may want your application to retain certain information — who the users are, their preferences, or any other pertinent information about them — as they make multiple requests. You do this by using varying techniques that you can apply throughout your web application's code.

One of the common techniques of the past and present is to use the Session object. But by simply using ASP.NET, you have so much more at your disposal. ASP.NET offers a wide variety of features and techniques to apply when working with state management. You might have used many of these techniques with web applications that you developed in the past.

On the other hand, caching, although also focused on storing information, is a means to provide a better experience for your end users by making the application load and perform faster than otherwise. One of the best things you can do for your ASP.NET application is to build an application that has a good caching strategy.

Understanding State in .NET

If you are working with state management in your web application, it is important to understand state as it works within .NET as a whole. The .NET Framework and ASP.NET provide a plethora of options when dealing with state. Table 2.1 describes some of your server-side options.

Table 2.1 Server-Side Options

Session Type

Description

Application

Using the Application object, you can store state that is applicable to all users. You are unable to use this for providing different state options to different users.

Cache

Using the Cache object, you are able to also store state for every user of the application. This object supports the capability to expire the cache, and it also provides the capability to set dependencies on how the cache is expired.

Database-driven Session

This is a means of using the Session object and having all the states stored safely on a SQL server.

Session

Using the Session object, you are capable of storing state on the server on a per-user basis. The Session object allows you to store the state in-process (in the same process as the application), out-of-process (in a different process), or even using the aforementioned database approach.

If it were all about the server-side options, it would not be that long of a story to tell. ASP.NET also includes a good list of client-side state management techniques that make the process of storing state rather easy. Table 2.2 defines your client-side options.

Table 2.2 Client-Side Options

Session Type

Description

ControlState

This provides a means of providing state to controls (for the control developer) that is quite similar to View State.

Cookie

This provides a means of storing state directly in the file system of the client. Cookies can be rejected by clients.

Hidden Field

Using hidden fields, you can store state for a user directly in the code of the page to use on subsequent requests that are posted back to the server.

querystring

Using the querystring capabilities provided, you are able to store state within the actual URL on a per-user basis.

View State

This provides the capability to use encoded state within the page code.

As you can see, there are a number of ways to work with state (not even all of them are listed). It is important to understand that there isn't a right or wrong way to work with state. It really has a lot to do with what you are trying to achieve and work with in your application.

Working with Sessions

Sessions within an ASP.NET application enable users to easily maintain application state. Sessions will remain with the user as he or she works through repeated calls to an application for a defined period. Sessions also provide a great way to improve overall performance, as opposed to re-looking up the state over and over again from a data store of some kind.

However, using sessions comes with a warning. One of the best ways to improve the performance of your ASP.NET applications is to fan-out the hardware on which they run (that is, adding additional web servers). Using sessions incorrectly will result in an application that won't work in this fan-out model. If you are taking this approach, you should look at either using sticky-routing (making sure that all subsequent requests from the user go to the same machine repeatedly), or using a state server of some kind for central storage of sessions. When using the Session object incorrectly (that is, not building them with an anticipation of a fan-out model), the other servers that are dealing with the subsequent requests won't know anything about the stored Session request. This will be discussed shortly.

Sessions are easily created, and it is just as easy to retrieve information from them. Use the following code to create a session for the user or calling application that can be accessed later in the application, or to assign a value to an already established session:

Session["EmployeeID"] = Value1;

This will assign what was being held in the variable Value1 to the EmployeeID Session object. To retrieve this information from the session and then use it in your code, use the following:

Value2 = Session["EmployeeID"];

In ASP.NET, a session would time out on the user after 20 minutes. If the user opened a page within a web application (thereby creating a session), and then walked away for a cup of coffee, when the user came back to the page 40 minutes later, the session would not be there for him or her. You could get around this by going into the server and changing the time allotted to the session timeout property, but this is cumbersome, and requires that you stop the server and then start it again for the changes to take effect. In addition, because sessions are resource-intensive, you would not want to store too many sessions for too long.

ASP.NET allows you to change the session timeout property quite easily. On the application level, it is stored in the web.config file. The machine.config file stores the default timeout setting for the entire server. By changing the setting in the web.config file, you can effectively change the timeout property of sessions within the application.

The great thing about changing this property within this XML application file is that the server does not have to be stopped and started for the changes to take effect. After the web.config file is saved with its changes, the changes take effect immediately. It is important to note though that when you do this, the application domain is indirectly restarted, and all state information is lost, including all the contents of the Session object.

Listing 2.1 shows the part of the web.config file that deals with session state management — the <sessionState> node.

Listing 2.1: Reviewing the <sessionState> Element in the web.config File

<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> node of the web.config file is where session state is managed. The property that you are concerned with now is the timeout property.

The timeout property is set to 20 (the default setting). This setting represents minutes of time. Therefore, if you want the users' sessions to last for one hour, you set the timeout property to 60.

Running Sessions In-Process

Presently, the default setting for sessions in ASP.NET stores the sessions in the in-process mode. Running sessions in-process means that the sessions are stored in the same process as the ASP.NET worker process. Therefore, if Internet Information Services (IIS) is shut down and then brought back up again, all sessions are destroyed and unavailable to users. On mission-critical web applications, this can be a nightmare.

To run the sessions in-process, set the mode property in the <sessionState> node to InProc. Running sessions in-process provides the application with the best possible performance.

Table 2.3 describes all the available session modes.

Table 2.3 Available Session Modes

Mode

Description

InProc

Session state is in-process with the ASP.NET worker process. Running sessions InProc is the default setting.

Off

Session state is not available.

StateServer

Session state is using an out-of-process server to store state.

SQLServer

Session state is using an out-of-process SQL Server to store state.

Running Sessions Out of Process

It is possible to run sessions out of process. Running a session out of process allows IIS to be stopped and then restarted, while maintaining the user's sessions.

Along with the .NET Framework is a Windows service called ASPState. This service enables you to run sessions out of process, but it must be started in order to use it to manage sessions.

To start the ASPState service, open the command prompt (Start → Programs → Accessories → Command Prompt). At the command prompt, type the following command and press Enter:

CD \WINDOWS\Microsoft.NET\Framework\v4.0.30319

This changes the directory of the command prompt. After typing that line in the command prompt, enter the following:

net start aspnet_state

This turns on the session out-of-process capabilities, as shown in Figure 2.3.

Figure 2.3 Turning on the session out-of-process capabilities

2.3

Once the out-of-process mode is enabled, you can change the settings in the <sessionState> node of the web.config file so that all the users' sessions are run in this manner. You do this by setting the mode to StateServer, as shown in Listing 2.2.

Listing 2.2: Setting the Session Object to Use the StateServer Option

<sessionState

mode="StateServer"

stateConnectionString="tcpip=127.0.0.1:42424"

sqlConnectionString="data source=127.0.0.1;user id=sa;password="

cookieless="false"

timeout="20" />

Now the user can turn off IIS and then on again, and his or her sessions will remain intact, although doing this is a little more resource-intensive than running the sessions in-process.

If the mode is set to StateServer, the server looks to the stateConnectionString property to assign the sessions to a specified server and port. In this case, it is set to the local server (which is the default setting). You can easily change this so that the sessions are stored on a completely separate server.

Running sessions out of process provides a great advantage with ASP.NET. This is a great tool when running web applications in a web farm where you are unsure to which server the user will be directed. This gives you the capability to move users from one server to another, and yet maintain their states.

Maintaining Sessions on SQL Server

Another option to run sessions out of process is to employ SQL Server to store the user sessions. Storing sessions in SQL Server also enables users to move from one server to another and maintain their states. It is the same as the StateServer mode, but instead stores the sessions in SQL Server.

If you installed the .NET Framework, you also installed a mini-version of SQL Server on your computer. This SQL Server-lite version enables you to store your sessions to use for state management. However, it is recommended that you use a full-blown version of SQL Server, such as SQL Server 2008. This is a more dependable solution.

The first thing to do to use SQL Server as a repository for your sessions is to create the database within SQL that ASP.NET can use. Included in the version folder of ASP.NET (found at C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319) are two scripts that work with SQL Server session management. The first is the install script, InstallSqlState.sql. This script tells SQL Server which database tables and procedures to create. You can look at the script instructions, which are quite readable, by opening the script in Notepad.

If you ever wish to remove these tables and stored procedures from SQL Server, use the uninstall script, UninstallSqlState.sql.

If you wish to use SQL Server to manage your sessions, run the install script. To do this, open up the command prompt again and navigate to the version folder of ASP.NET that you are running. On the command line, enter the following:

OSQL -S localhost -U sa -P -I InstallSqlState.sql

The OSQL utility enables you to enter Transact-SQL statements, system procedures, and script files. This utility uses Open Database Connectivity (ODBC) to communicate with the server. Running this command creates the tables and procedures needed to run the SQL Server session management option. The notation -S in the command line is specifying the location of the server that is to be used. In this case, you are using localhost, meaning your local server.

The notation -U refers to the SQL server's assigned username to gain access. In this case, it is just the typical sa.

The notation -P is for the SQL server's password, if required. In this case, it is not required. Therefore, you leave it blank.

Following the SQL server's setting specifications, you then specify the script that you wish to install, InstallSqlState.sql. This installs what is necessary to run SQL Server session management.

After you have created the necessary tables and procedures, change the <sessionState> node of the web.config file as shown in Listing 2.3.

Listing 2.3: Using SQL Server as a StateServer

<sessionState

mode="SQLServer"

stateConnectionString="tcpip=127.0.0.1:42424"

cookieless="false"

timeout="20" />

To use SQL Server to manage sessions, the mode of the <sessionState> node must be set to SQLServer. After the mode is set to SQLServer, ASP.NET then looks to the sqlConnectionString property to find the SQL server to connect to when storing state. The value of this property should be set so that the data source is the server where SQL and any needed login information are located.

Deciding on the State of Sessions

The mode you choose for running sessions within your web application makes a considerable difference in the performance, functionality, and reliability of your web application.

Which mode should you choose? Following are the best conditions for each option:

· InProc — The session is run in the same process as the ASP.NET worker process. Therefore, this option should be used when the maintaining of sessions is not mission-critical to the application. This option has the best performance possible out of all the choices.

· StateServer — This Windows Service option runs the sessions out of process and is, therefore, best when used on multiple servers, or when sessions must be maintained if IIS is stopped and then restarted. This option provides better performance than the other out-of-process option, SQLServer.

· SQLServer — This out-of-process option is the most reliable choice because the sessions are stored directly in SQL Server. Even though this is the most reliable choice, this option ranks worst in performance.

To get the best performance from your use of sessions, you are able to declare on each page how you want to deal with the Session object using the EnableSessionState attribute within the Page directive. Following is an example of using this attribute:

<%@ Page Language="C#" EnableSessionState="True" CodeFile="Default.aspx.cs"

Inherits="_Default" %>

The EnableSessionState is set to True by default (as shown in bold). This attribute has the following three options:

· EnableSessionState=”True” — This is the default setting, and means that the ASP.NET page will make use of the Session object. The page will also use read/write access to the Session object for only the session ID that is being used.

· EnableSessionState=”False” — Changing the value to False means that the page will not require access to the Session object. If your page is not using the Session object at all, it is best to set the value of this attribute to False, which will cause ASP.NET to schedule this particular page ahead of other pages that require the use of the Session object. This will help with the overall scalability of your ASP.NET application.

· EnableSessionState=”ReadOnly” — Setting the value to ReadOnly means that your ASP.NET page will require only read access to the Session object. All pages that are only going to be reading from the Session object will benefit from this setting.

If your application is not going to make use of the Session object, the best thing to do with regard to performance is to then turn off session state. You can do this via the web.config or the machine.config file. In the <sessionState> element, you set the mode attribute to Off.

Working with Output Caching

Still focusing on the state management and caching strategies that can be employed on the server, another option at your disposal is caching. Caching is the process of storing frequently used data on the server to fulfill subsequent requests. It is tremendously better to grab objects to use in code from memory rather than repeatedly calling a data store. Caching will improve the overall performance, scalability, and availability of your ASP.NET applications.

Output caching is a way to keep the dynamically generated page content in the server's memory or disk for later retrieval. This type of cache saves post-rendered content so that it will not have to be regenerated again the next time it is requested. After a page is cached, it can be served up again when any subsequent requests are made to the server.

You apply output caching by inserting an OutputCache page directive at the top of an .aspx page, as follows:

<%@ OutputCache Duration="60" VaryByParam="None" %>

The Duration attribute defines the number of seconds a page is stored in the cache. The VaryByParam attribute determines which versions of the page output are actually cached. You can also generate different responses based on whether an HTTP-POST or HTTP-GET response is required by using theVaryByHeader attribute. Other than the VaryByParam attribute for the OutputCache directive, ASP.NET includes the VaryByHeader, VaryByCustom, VaryByControl, and Location attributes. Additionally, the Shared attribute can affect UserControls, as you will see later.

Caching in ASP.NET is implemented as an HttpModule that listens to all HttpRequests that come through the ASP.NET worker process. The OutputCacheModule listens to the application's ResolveRequestCache and UpdateRequestCache events, handles cache hits and misses, and returns the cached HTML, bypassing the Page Handler if need be.

VaryByParam

The VaryByParam attribute can specify which QueryString parameters cause a new version of the page to be cached, as shown here:

<%@ OutputCache Duration="90" VaryByParam="pageId;subPageId" %>

For example, if you have a page called navigation.aspx that includes navigation information in the QueryString (such as pageId and subPageId), the OutputCache directive shown here caches the page for every different value of pageId and subPageId. In this example, the number of pages is best expressed with an equation, as shown here:

cacheItems = (num of pageIds) * (num of subPageIds)

In this equation, cacheItems is the number of rendered HTML pages that would be stored in the cache. Pages are cached only after they are requested and pass through the OutputCacheModule. The maximum amount of cache memory in this case is used only after every possible combination is visited at least once. Although these are just potential maximums, creating an equation that represents your system's potential maximum is an important exercise.

If you want to cache a new version of the page based on any differences in the QueryString parameters, use VaryByParam = “*”, as in the following code:

<%@ OutputCache Duration="90" VaryByParam="*" %>

VaryByHeader

The VaryByHeader attribute works off of the different HTTP headers of the requests that come to the server.

It is important to “do the math” when using the VaryBy attributes. For example, you could add the VaryByHeader attribute and cache a different version of the page based on the browser's reported User-Agent HTTP header.

<%@ OutputCache Duration="90" VaryByParam="*" VaryByHeader="User-Agent"%>

The User-Agent identifies the user's browser type. ASP.NET can automatically generate different renderings of a given page that are customized to specific browsers, so it makes sense in many cases to save these various renderings in the cache. A Firefox user might have slightly different HTML than an Internet Explorer (IE) user, so you do not want to send all users the exact same post-rendered HTML. Literally dozens (if not hundreds) of User-Agent strings exist in the wild because they identify more than just the browser type. This OutputCache directive could multiply into thousands of different versions of this page being cached, depending on server load. In this case, you should measure the cost of the caching against the cost of re-creating the page dynamically.

warning

Always cache what will give you the biggest performance gain, and prove that assumption with testing. Don't “cache by coincidence” using attributes like VaryByParam = “*”. A common rule of thumb is to cache the least possible amount of data at first, and add more caching later if you determine a need for it. Remember that the server memory is a limited resource, so you may want to configure the use of disk caching in some cases. Be sure to balance your limited resources with security as a primary concern; do not put sensitive data on the disk.

VaryByControl

VaryByControl can be a very easy way to get some serious performance gains from complicated UserControls that render a lot of HTML that does not change often. For example, imagine a UserControl that renders a ComboBox showing the names of all the countries in the world. Perhaps those names are retrieved from a database and rendered in the combo box as follows:

<%@ OutputCache Duration="2592000" VaryByControl="comboBoxOfCountries" %>

Certainly, the names of the world's countries do not change that often, so the Duration might be set to a month (in seconds). This example makes use of a server control called comboBoxOfCountries and works with its cached output. The rendered output of the UserControl is cached, allowing a page using that control to reap performance benefits of caching the control while the page itself remains dynamic.

VaryByCustom

Although the VaryBy attributes offer a great deal of power, sometimes you need more flexibility. If you want to take the OutputCache directive from the previous navigation example and cache by a value stored in a cookie, you can add VaryByCustom. The value of VaryByCustom is passed into theGetVaryByCustomString method that can be added to the Global.asax.cs. This method is called every time the page is requested, and it is the function's responsibility to return a value.

A different version of the page is cached for each unique value returned. For example, say your users have a cookie called Language that has three potential values: en, es, and fr. You want to allow users to specify their preferred language, regardless of their language reported by their browser.Language also has a fourth potential value — it may not exist! Therefore, the OutputCache directive in the following example caches many versions of the page, as described in this equation:

cacheItems = (num of pageIds) * (num of subPageIds) * (4 possible Language values)

To summarize, suppose there were ten potential values for pageId, five potential subPageId values for each pageId, and four possible values for Language. That adds up to 200 different potential cached versions of this single navigation page. This math is not meant to scare you away from caching, but you should realize that with great (caching) power comes great responsibility.

Caching in ASP.NET involves a trade-off between CPU and memory — how hard is it to make this page, versus whether you can afford to hold 200 versions of it. If it is only 5KB of HTML, a potential megabyte of memory could pay off handsomely, versus thousands and thousands of database accesses. Since most pages will hit the database at least once during a page cycle, every page request served from the cache saves you a trip to the database. Efficient use of caching can translate into cost savings if fewer database servers and licenses are needed.

The following OutputCache directive includes pageId and subPageId as values for VaryByParam, and VaryByCustom passes in the value of “prefs” to the GetVaryByCustomString callback function in Listing 2.4 (shown shortly):

<%@ OutputCache Duration="90" VaryByParam="pageId;subPageId" VaryByCustom="prefs"%>

The code shown in Listing 2.4 returns the value stored in the Language cookie. The arg parameter to the GetVaryByCustomString method contains the string “prefs”, as specified in VaryByCustom.

Listing 2.4: GetVaryByCustomString Callback Method in the HttpApplication

public override string GetVaryByCustomString(HttpContext context, string arg)

{

if(arg.ToLower() == "prefs")

{

HttpCookie cookie = context.Request.Cookies["Language"];

if(cookie != null)

{

return cookie.Value;

}

}

return base.GetVaryByCustomString(context, arg);

}

The GetVaryByCustomString method in Listing 2.4 is used by the HttpApplication in Global.asax.cs, and will be called for every page that uses the VaryByCustom OutputCache directive. If your application has many pages that use VaryByCustom, you can create a switch statement and a series of helper functions to retrieve whatever information you want from the user's HttpContext and to generate unique values for cache keys.

Extending <outputCache>

With the release of ASP.NET 4, you are now able to extend how the OutputCache directive works, and have it instead work off of your own custom means to caching. This means that you can wire the OutputCache directive to any type of caching means, including distributed caches, cloud caches, disc, XML, or anything else you can dream up.

To accomplish this, you are required to create a custom output-cache provider as a class, and this class will need to inherit from the new System.Web.Caching.OutputCacheProvider class. In order to inherit from OutputCacheProvider, you must override the Add(), Get(), Remove(), and Set() methods to implement your custom version of output caching.

Once you have your custom implementation in place, the next step is to configure this in a configuration file — the machine.config or the web.config file. There have been some changes made to the <outputCache> element in the configuration file to allow you to apply your custom cache extensions.

The <outputCache> element is found within the <caching> section of the configuration file, and it now includes a new <providers> sub-element.

<caching>

<outputCache>

<providers>

</providers>

</outputCache>

</caching>

Within the new <providers> element, you can nest an <add> element to make the appropriate references to your new output cache capability you built by deriving from the OutputCacheProvider class.

<caching>

<outputCache defaultProvider="AspNetInternalProvider">

<providers>

<add name="myDistributedCacheExtension"

type="Wrox.OutputCacheExtension.DistributedCacheProvider,

DistributedCacheProvider" />

</providers>

</outputCache>

</caching>

With this new <add> element in place, your new extended output cache is available to use. One new addition here to also pay attention to is the new defaultProvider attribute within the <outputCache> element. In this case, it is set to AspNetInternalProvider, which is the default setting in the configuration file. This means that, by default, the output cache works as it always has done, and stores its cache in the memory of the computer that the program on which it is running.

With your own output cache provider in place, you can now point to this provider through the OutputCache directive on the page, as defined here:

<%@ OutputCache Duration="90" VaryByParam="*"

providerName="myDistributedCacheExtension" %>

If the provider name isn't defined, then the provider that is defined in the configuration's defaultProvider attribute is utilized.

Partial Page (UserControl) Caching

Similar to output caching, partial page caching enables you to cache only specific blocks of a web page. For example, you can cache only the center of the page the user sees. Partial page caching is achieved with the caching of user controls, so you can build your ASP.NET pages to utilize numerous user controls, and then apply output caching to the selected user controls. This, in essence, caches only the parts of the page that you want, leaving other parts of the page outside the reach of caching.

This is a nice feature and, if done correctly, it can lead to pages that perform better. This requires a modular design to be planned up front so that you can partition the components of the page into logical units composed of user controls.

Typically, UserControls are designed to be placed on multiple pages to maximize reuse of common functionality. However, when these UserControls (.ascx files) are cached with the @ OutputCache directive's default attributes, they are cached on a per-page basis. That means that even if a UserControl outputs the identical HTML when placed on pageA.aspx (as it does when placed on pageB.aspx), its output is cached twice. By enabling the Shared=“true” attribute, the UserControl's output can be shared among multiple pages and on sites that make heavy use of shared UserControls:

<%@ OutputCache Duration="300" VaryByParam="*" Shared="true" %>

The resulting memory savings can be surprisingly large, since you only cache one copy of the post-rendered user control, instead of caching a copy for each page. As with all optimizations, you must test both for correctness of output, as well as memory usage.

warning

If you have a UserControl using the OutputCache directive, remember that the UserControl exists only for the first request. If a UserControl has its HTML retrieved from the OutputCache, the control does not really exist on the .aspx page. Instead, a PartialCachingControl is created that acts as a proxy or ghost of that control.

Any code in the .aspx page that requires a UserControl to be constantly available will fail if that control is reconstituted from the OutputCache. So, be sure to always check for this type of caching before using any control. The following code fragment illustrates the kind of logic required when accessing a potentially cached UserControl:

protected void Page_Load()

{

if (PossiblyCachedUserControl != null)

{

// Place code manipulating PossiblyCachedUserControl here.

}

}

Looking at .NET 4's New Object Caching Option

If you have ever worked with the System.Web.Caching.Cache object, you know that it is quite powerful, and that it allows for you to even create a custom cache. This extensibility and power has changed under the hood of the Cache object though.

Driving this is the System.Runtime.Caching.dll. What was in the System.Web version has been refactored out, and everything was rebuilt into the new namespace of System.Runtime.Caching.

The reason for this change wasn't so much for the ASP.NET developer, but instead it was for other application types such as Windows Forms, Windows Presentation Foundation (WPF) apps, and more. The reason for this is because the System.Web.Caching.Cache object was so useful that other application developers were bringing over the System.Web namespace into their projects to make use of this object. So, in order to get away from a Windows Forms developer needing to bring in the System.Web.dll into a project just to use the Cache object it provided, this was all extracted out and extended with the System.Runtime.Caching namespace.

As an ASP.NET developer, you can still make use of the System.Web.Caching.Cache object just as you did in all the prior versions of ASP.NET. It isn't going away. However, it is important to note that as the .NET Framework evolves, the .NET team will be making their investments into theSystem.Runtime.Caching namespace, rather than System.Web.Caching.

This means that, over time, you will most likely see additional enhancements in the System.Runtime.Caching version that don't appear in the System.Web.Caching namespace as you might expect. With that said, it doesn't also mean that you need to move everything over to the new System.Runtime.Cachingnamespace in order to make sure you are the strategic path of Microsoft, as the two caches are managed together under the covers.

Now, let's run through an example of using the cache from the System.Runtime.Caching namespace. For this example, the ASP.NET page will simply use a Label control that will show the name of a user that is stored in an XML file. The first step is to create an XML file and name the file Username.xml, as shown in Listing 2.5.

Listing 2.5: The Contents of the Username.xml File

<?xml version="1.0" encoding="utf-8" ?>

<usernames>

<user>Bill Evjen</user>

</usernames>

With this XML file sitting in the root of your drive, now turn your attention to the Default.aspx code-behind page to use the name in the file, and present it into a single Label control on the page. Listing 2.6 shows the code-behind for the Default.aspx page.

Listing 2.6: Using the System.Runtime.Caching Namespace

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Caching;

using System.Xml.Linq;

namespace RuntimeCaching

{

public partial class Default : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

ObjectCache cache = MemoryCache.Default;

string usernameFromXml = cache["userFromXml"] as string;

if (usernameFromXml == null)

{

List<string> userFilePath = new List<string>();

userFilePath.Add(@"C:\Username.xml");

CacheItemPolicy policy = new CacheItemPolicy();

policy.ChangeMonitors.Add(new

HostFileChangeMonitor(userFilePath));

XDocument xdoc = XDocument.Load(@"C:\Username.xml");

var query = from u in xdoc.Elements("usernames")

select u.Value;

usernameFromXml = query.First().ToString();

cache.Set("userFromXml", usernameFromXml, policy);

}

Label1.Text = usernameFromXml;

}

}

}

Listing 2-6 makes use of the new cache at System.Runtime.Caching. You are going to need to reference this namespace in your ASP.NET project in order for this to work.

To start, you create a default instance of the cache object.

ObjectCache cache = MemoryCache.Default;

You then can work with this cache as you would with the traditional ASP.NET cache object.

string usernameFromXml = cache["userFromXml"] as string;

To get the cache started, you must create an object that defines what type of cache you are dealing with. You can build a custom implementation, or you can use one of the default implementations that are provided with .NET 4.

CacheItemPolicy policy = new CacheItemPolicy();

policy.ChangeMonitors.Add(new HostFileChangeMonitor(userFilePath));

The HostFileChangeMontior is a means to look at directories and file paths, and to monitor for change. So, for example, when the XML file changes, then this will trigger an invalidation of the cache. Other implementations of the ChangeMonitor object include the FileChangeMonitor and the SqlChangeMonitor. TheHostFileChangeMontior class implements the FileChangeMonitor class, and is a sealed class.

Running this example, you will notice that the text Bill Evjen is loaded into the cache on the first run, and this text will appear in the Label1 control. Keep your application running, and then go back to the XML file to change the value. You will then notice that this will cause the cache to be invalidated on the page's refresh.

Nowadays, many developers make use of a service-oriented architecture and, therefore, work with a set of services to get what they need done. The next section takes an important and quick look at implementing caching for your services.

Caching Web Services

Caching is an important feature in almost every application that you build with .NET. There are a lot of caching capabilities available to you in ASP.NET (as discussed in this chapter), but a certain feature of ASP.NET Web Services in .NET enables you to cache the Simple Object Access Protocol (SOAP) response sent to any of the service's consumers.

First, by way of review, remember that caching is the capability to maintain an in-memory store where data, objects, and various items are stored for reuse. This feature increases the responsiveness of the applications you build and manage. Sometimes, returning cached results can greatly affect performance.

XML web services use an attribute to control caching of SOAP responses — the CacheDuration property. Listing 2.7 shows its use.

Listing 2.7: Utilizing the CacheDuration Property

[WebMethod(CacheDuration=60)]

public string GetServerTime() {

return DateTime.Now.ToLongTimeString();

}

As you can see, CacheDuration is used within the WebMethod attribute much like the Description and Name properties. CacheDuration takes an Integer value that is equal to the number of seconds during which the SOAP response is cached.

When the first request comes in, the SOAP response is cached by the server, and the consumer gets the same timestamp in the SOAP response for the next minute. After that minute is up, the stored cache is discarded, and a new response is generated and stored in the cache again for servicing all other requests for the next minute.

Among the many benefits of caching your SOAP responses, you will find that the performance of your application is greatly improved when you have a response that is basically re-created again and again without any change.

Many developers stop at the software implementation of their solutions. Though, as a software developer, it is important to think about the hardware considerations as well. It is important to remember that how you establish your solution on the hardware, and how the hardware is configured, can have a dramatic impact on the overall performance. This next section takes a look at this topic.

Hardware Considerations

It is one thing to build an ASP.NET application that is coded to perform well, or works with caching to avoid subsequent calls to underlying data sources. Another consideration is to configure your application to take advantage of the hardware to which it is deployed. There are available resources that your ASP.NET takes advantage of as it runs.

Within the <system.net> element of your configuration file, you can control the maximum number of outbound HTTP connections that ASP.NET can perform. By default, it is set to 2, but you should readjust this number using the maxconnection attribute. Listing 2.8 shows the maxconnection attribute in use.

Listing 2.8: Setting the maxconnection Attribute

<configuration>

<system.net>

<connectionManagement>

<add name="www.swank.com" maxconnection="5" />

<add name="*" maxconnection="4" />

</connectionManagement>

</system.net>

</configuration>

Using the <connectionManagement> element, you can manage either particular URLs, or even IP addresses. In this case, up to five connections will be allowed to www.swank.com, while everything else is allowed up to four connections (defined with the asterisk).

The question then is what should the number be? It really comes down to your CPU capabilities. Microsoft recommends that it should be set to 12 times the number of CPUs on your server. Therefore, if you are using a dual-processor server, this number should be set to 24.

Your ASP.NET application makes use of threads that are from the .NET thread pool to run. Instead of spinning up new threads, there is a set thread pool that loans threads out, and when your application is done with the thread, it is simply returned to this thread pool for later reuse.

The thread pool makes use of the available hardware resources for managing the number of threads. Most importantly, it makes use of the CPU and the CPU utilization to determine thread pool sizes. In addition, your application makes I/O operations when it works off files or makes any service calls.

There are settings in the machine.config file that determine some of the limits in how ASP.NET will manage the number of threads it can deal with. If you are putting your ASP.NET application on a multiprocessor server, then you have more to take advantage of, but you need to adjust the default settings in the machine.config file in order to do that.

Another area in the configuration files to pay attention to is the <processModel> section. Here you can set some values that deal with how your ASP.NET application deals with threading. The <processModel> element has a number of attributes to pay attention to here. The first one to note is themaxIoThreads attribute.

The maxIoThreads attribute allows you to specify the maximum number of I/O threads that exist within the .NET thread pool that is used by the ASP.NET worker process. The default value is 20. Instead of using this default value, change this value to 100. You do not have to worry about how this relates to the number of CPUs at your disposal, because it will automatically be multiplied by the number of CPUs detected. Therefore, if your application is on a dual-processor server, then the value used will be 200.

The maxWorkerThreads attribute allows you to specify the maximum number of threads that exist within the ASP.NET worker process thread pool. This attribute also has a default value of 20. Like the maxIoThreads attribute, you are going to want to set the maxWorkerThreads attribute to 100. Again, this is a value that will be assigned to the number of CPUs detected automatically.

Another attribute, the minFreeThreads attribute, is found with the <httpRuntime> element of the machine.config file. Because the ASP.NET runtime uses the free threads available in its thread pool to fulfill requests, the minFreeThreads specifies the number of threads that ASP.NET guarantees is available within the thread pool. The default number of threads is set to 8. For complex applications that require additional threads to complete processing, this attribute simply ensures that the threads are available, and that the application will not be locked while waiting for a free thread to schedule more work. Microsoft recommends that you set this number to 88 times the number of CPUs on your server. Therefore, if you are using a dual-processor server, this number should be set to 176.

The last attribute to pay attention to here is the minLocalRequestFreeThreads attribute, also found with the <httpRuntime> element. This attribute controls the number of free threads dedicated for local request processing. The default value of this attribute is set to 4. For this attribute, Microsoft recommends that you set it to 76 times the number of CPUs on your server. Therefore, if you are using a dual-processor server, this number should be set to 152.

Beyond what you can do from a software and hardware perspective, you will need to be able to benchmark the performance of your applications so that you can see where you can realize performance gains. The next section takes a look at the performance counters that are available for your use.

Using Performance Counters

Utilizing performance counters is important if you want to monitor your applications as they run. What exactly is monitored is up to you. A plethora of available performance counters are at your disposal in Windows, and you will find that there are more than 60 counters specific to ASP.NET.

Viewing Performance Counters Through an Administration Tool

You can see these performance counters by opening the Performance dialog found in the Control Panel, and then Administration Tools if you are using Windows XP. If you are using Windows 7, select Control Panel → System and Security → Performance Information and Tools → Advanced Tools → Open Performance Monitor. Figure 2.4 shows the dialog opened in Windows 7.

Figure 2.4 Performance dialog in Windows 7

2.4

Clicking the plus sign in the menu enables you to add more performance counters to the list. You will find a number of ASP.NET-specific counters in the list illustrated in Figure 2.5.

Figure 2.5 ASP.NET-specific counters

2.5

The following list details some of the ASP.NET-specific performance counters that are at your disposal, along with a definition of the counter (also available by checking the Show Description check box in Windows 7 from within the dialog):

· Application Restarts — The number of times the application has been restarted during the web server's lifetime.

· Applications Running — The number of currently running web applications.

· Audit Failure Events Raised — The number of audit failures in the application since it was started.

· Audit Success Events Raised — The number of audit successes in the application since it was started.

· Error Events Raised — The number of error events raised since the application was started.

· Infrastructure Error Events Raised — The number of HTTP error events raised since the application was started.

· Request Error Events Raised — The number of runtime error events raised since the application was started.

· Request Execution Time — The number of milliseconds it took to execute the most recent request.

· Request Wait Time — The number of milliseconds the most recent request was waiting in the queue.

· Requests Current — The current number of requests, including those that are queued, currently executing, or waiting to be written to the client. Under the ASP.NET process model, when this counter exceeds the requestQueueLimit defined in the processModel configuration section, ASP.NET begins rejecting requests.

· Requests Disconnected — The number of requests disconnected because of communication errors or user terminations.

· Requests Queued — The number of requests waiting to be processed.

· Requests Rejected — The number of requests rejected because the request queue was full.

· State Server Sessions Abandoned — The number of sessions that have been explicitly abandoned.

· State Server Sessions Active — The current number of sessions currently active.

· State Server Sessions Timed Out — The number of sessions timed out.

· State Server Sessions Total — The number of sessions total.

· Worker Process Restarts — The number of times a worker process has restarted on the machine.

· Worker Processes Running — The number of worker processes running on the machine.

These are the performance counters for just the ASP.NET v2.0.50727 category. You will also find categories for other ASP.NET-specific items such as the following:

· ASP.NET

· ASP.NET Applications

· ASP.NET Apps v4.0.30319

· ASP.NET State Service

· ASP.NET v4.0.30319

Performance counters can give you a pretty outstanding view of what is happening in your application. The data retrieved by a specific counter is not a continuous thing, because the counter is really taking a snapshot of the specified counter every 400 milliseconds. So, be sure to take that into account when analyzing the data produced.

So far, this chapter has covered some of the more important topics when it comes to your application's performance. However, there are still a lot of little things that you can do to help the performance even more. The next section looks at some of these other items.

Tips and Tricks

Of course, there are a bunch of random things you can do for your ASP.NET application to ensure that it runs and is processed by your users as fast as possible. Many of the tricks have to do with keeping the number of requests down, and making these requests as small as possible.

There are a couple of good tools that you should be using to grade your ASP.NET applications. Some favorites include Firebug (found as an add-on for Firefox) and Fiddler (found at www.fiddlertool.com). These allow you to see all the requests the client makes to the server where your application is hosted.

Keep Requests Down to a Minimum

You want to keep the number of requests down to a minimum. There are a couple of ways you can do this. One way is to limit the number of JavaScript and Cascading Style Sheet (CSS) calls by combining these files into single files. So, instead of two or more .js or .css files, all of which the client needs to make a singular request for, you combine those files into a single .js or .css file. Overall, this does make a difference.

Make Use of Content Delivery Networks

When an end user makes a request to a page in your ASP.NET application, invariably it goes through a number of hops to get to the location of your server. The number of hops an end user must take dramatically increases the further away the user's machine is from the server. For example, if your user is calling a server in St. Louis, Missouri, from Tokyo, Japan, then there will be more hops required than a user who already resides in St. Louis.

Companies like to put their larger content on a system of servers around that world that act as an edge cache. Therefore, instead of always having everyone come to your server to get the file, you can get that file closer to the end user by distributing the file to reside on a series of networked servers scattered throughout the world. These types of systems are called Content Delivery Networks (CDNs). Popular companies that do this type of work include Akamai and EdgeCast.

As an ASP.NET architect, you might be able to take advantage of these types of edge capabilities that will then dramatically increase the perceived performance of your applications.

Recently, Microsoft has provided a free CDN capability for developers who are using jQuery and Ajax. If you are using ASP.NET 4, you can enable the use of Microsoft's Ajax CDN very easily. Microsoft has put up a number of JavaScript libraries in a CDN that you can freely take advantage of in your applications. The available JavaScript libraries include the following:

· jQuery

· jQuery UI

· jQuery Validation

· jQuery Cycle

· Ajax Control Toolkit

· ASP.NET Ajax

· ASP.NET MVC JavaScript Files

To make use of one of these JavaScript files from a CDN, you simply need to just change the URL that is used in the <script> tag, as shown in Listing 2.9.

Listing 2.9: Using the Microsoft Ajax CDN

<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.4.4.js"

type="text/javascript"></script>

Here you can see that instead of calling your own server for the jQuery 1.4.4 library, this library is instead serviced from ajax.aspnetcdn.com. This means that when the end user invokes this call for this jQuery library, it will only come from the Microsoft CDN, and will never come from your server. This will be considerably faster for your end users, and will save you bandwidth in the end.

note

For a full list of supported JavaScript libraries and their associated URLs, visit www.asp.net/ajaxlibrary/cdn.ashx.

If your ASP.NET page is using Secure Sockets Layer (SSL), then you will want to call the Microsoft Ajax CDN using SSL as well, because this is supported. Listing 2.10 shows how to use the same jQuery call with SSL.

Listing 2.10: Using the Microsoft Ajax CDN

<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.4.4.js"

type="text/javascript"></script>

If you are using the ScriptManager server control in your ASP.NET Ajax applications, then you are going to want to use the new EnableCdn attribute that is now available in the control. Setting the value of this attribute to true will cause the Microsoft Ajax CDN to be utilized instead of your local server. This is shown in Listing 2.11. It should be noted that this only works in ASP.NET 4.

Listing 2.11: Using the Microsoft Ajax CDN from the ScriptManager Control

<asp:ScriptManager ID="ScriptManager1" EnableCdn="true" runat="server" />

note

If you are using JavaScript libraries that are not found in the Microsoft Ajax CDN, Google offers a similar capability, and you can see if they are hosting what you are looking for. You can find the supported Google hosted libraries at http://code.google.com/apis/libraries/devguide.html#AjaxLibraries.

Enable the Browser to Cache Items Longer

There are elements to your ASP.NET page that do not change that often. For example, there are probably images that don't change that often, and it would be better to have these images stored in the end user's browser cache, rather than having that end user call for the same image over and over again as he or she comes to your pages. By default, all your page's artifacts are set to not be cached by the browser. You can change this behavior by setting an expiry date on the items you want cached.

A good approach is to put all of your JavaScript, CSS, and images in dedicated folders, and within those folders, control how long they are cached on the client. You can do this in a couple of different ways.

One way is to use the IIS Manager. Highlighting the folder you are working on within the IIS Manager, select the HTTP Response Headers icon in the available list of options, as shown in Figure 2.6.

Figure 2.6 HTTP Response Headers icon in the available list of options

2.6

Double-clicking the icon will give you a list of headers that are currently sent from IIS for each request. From here, you can click on the Set Common Headers link, and this will give you the dialog presented here in Figure 2.7.

Figure 2.7 Set Common HTTP Response Headers dialog

2.7

By selecting “Expire Web content,” you have the option to expire the content immediately, after a set interval, or on a specific date. As shown in Figure 2.7, you can see that the folder is set to expire its contents after 7 days. After clicking the OK button, this will generate a web.config file within that folder with the content shown in Listing 2.12.

Listing 2.12: The Generated web.config File

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

<system.webServer>

<staticContent>

<clientCache cacheControlMode="UseMaxAge"

cacheControlMaxAge="7.00:00:00" />

</staticContent>

</system.webServer>

</configuration>

One thing to keep in mind is that if you set an image to be cached for some time, and the end user does indeed have this image cached, if you have made modifications to this image during this time, the end user will still see the one that is contained within his or her cache. One way around this is to simply have different names for your images as you change them. In this way, the end user will be calling an entirely new image, rather than using the one with the same name that resides in the cache.

Enabling Content Compression

When an end user makes a request to your web server, a header is sent in the request. Contained within the header is a declaration of whether the end user's browser can support a compressed response. Listing 2.13 shows a sample header that supports compression.

Listing 2.13: A Request Header that States the Client Supports Compression

GET http://www.swank.com/ HTTP/1.1

Accept: text/html, application/xhtml+xml, */*

Accept-Language: en-US

User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64;

Trident/5.0)

Accept-Encoding: gzip, deflate

Connection: Keep-Alive

Host: www.swank.com

The important part of this header is the line Accept-Encoding: gzip, deflate. This tells the server that the client can support a compressed response. Sending back your responses compressed obviously helps with bandwidth.

IIS 7 enables you to easily compress your static or dynamic content. From the IIS Manager, highlight your site and select the Compression icon in the list of available options. You are provided with two options (if they are both installed) on the server. One is to enable dynamic content compression, and the other is to enable static content compression. You can see these options in Figure 2.8.

Figure 2.8 Compression options in IIS Manager

2.8

One thing to make note of is that if you enable dynamic compression, it takes work on the CPU to compress these responses on-the-fly, so monitor your CPU with this enabled to see if it makes sense for your application.

Location of Content in Your Pages

You might not think this would have an effect, but the placement of your auxiliary items on your page make a difference for the performance of your pages. The recommendation is to put all your CSS references at the top of your pages, and all the JavaScript references at the bottom of your page.

When dealing with CSS, you are going to want to put the CSS file in the <head> element of the ASP.NET page. This allows the page to load as items are downloaded to the client. As items start appearing on the page, it gives the end user the feeling that the page is loading fast.

Having your JavaScript <script> tags at the bottom of the page will not lock the download of the other page content, again, giving the appearance that the page is loading faster.

Make JavaScript and CSS External

In ASP.NET, it is possible to put your JavaScript and CSS code within the ASP.NET page itself. It is actually better to put all your JavaScript and CSS in external files (for example, MyJavaScript.js). The reason for this is that if the JavaScript and CSS code is contained within external files, you can easily allow caching of this content. If it is contained within the ASP.NET page, it is not cached so easily.

In addition, if you are using this JavaScript or CSS in more than one place in your application, you will not have to download the code multiple times. The idea would be to cache it once, and then reuse it multiple times.

Summary

The Internet has produced some great applications, but it has also made the development of these applications difficult. Because the Internet is a disconnected environment, (meaning that the only real time you are connected to an application or remote server is when you are making a request or getting a response), it is quite difficult at times to maintain state on it. However, ASP.NET has answered this difficulty with a number of solutions that, if used properly, can quickly make you forget about the disconnected world in which you work and play.

This chapter took a look at a lot of different things you can do to get your ASP.NET applications to perform. This chapter looked at how state works in your web applications, and the use of caching along with the Session object. This chapter also looked at how to configure your ASP.NET applications to take advantage of the hardware in which it is deployed. In addition to discussing performance counters for monitoring performance, this chapter also provided a set of tips and tricks to get the most from your applications.

About the Author

Bill Evjen is an active proponent of .NET technologies and community-based learning initiatives for .NET. In 2000, he founded the St. Louis .NET User Group (www.stlnet.org). He is also the founder and former executive director of the International .NET Association (www.ineta.org), which represents more than 500,000 members worldwide. He has authored or co-authored more than 20 books, and also works closely with Microsoft as a Microsoft Regional Director and an MVP. Evjen is the CIO for Swank Motion Pictures (www.swank.com). Swank Motion Pictures provides both public performance licensing rights and licensed movies to numerous non-theatrical markets, including worldwide cruise lines, U.S. colleges and universities, K-12 public schools and libraries, American civilian and military hospitals, motor coaches, Amtrak trains, correctional facilities, and other markets such as parks, art museums and businesses. Evjen graduated from Western Washington University in Bellingham, Washington, with a Russian language degree. When he isn't tinkering on the computer, he can usually be found at his summer house in Toivakka, Finland.