Developing High-Performance ASP.NET Applications

The guidelines in this topic list techniques that you can use to help maximize the throughput of ASP.NET Web applications. The guidelines are divided into the following sections:

  • Page and server control processing

  • State management

  • Data access

  • Web applications

  • Coding practices

Page and Server Control Processing

The following guidelines suggest ways to work with ASP.NET pages and controls efficiently.

  • Avoid unnecessary round trips to the server   There are circumstances in which using ASP.NET server controls and performing postback event handling are unnecessary. For example, validating user input in ASP.NET Web pages can often take place on the client before that data is submitted to the server. In general, if you do not need to relay information to the server to be verified or written to a data store, you can improve the page's performance and user experience by avoiding code that causes a round trip to the server. You can also use client callbacks to read data from the server instead of performing a full round trip. For details, see Implementing Client Callbacks Programmatically Without Postbacks in ASP.NET Web Pages.

    If you develop custom server controls, consider having them render client-side code for browsers that support ECMAScript (JavaScript). By using server controls in this way, you can dramatically reduce the number of times information is sent to the Web server. For more information, see Developing Custom ASP.NET Server Controls.

  • Use the Page object's IsPostBack property to avoid performing unnecessary processing on a round trip   If you write code that handles server control postback processing, you will sometimes want code to execute only the first time the page is requested rather than on each postback. Use the IsPostBack property to conditionally execute code depending on whether the page is generated in response to a server control event.

  • Save server control view state only when necessary   Automatic view state management enables server controls to repopulate their property values on a round trip without your having to write any code. However, this feature affects performance because a server control's view state is passed to and from the server in a hidden form field. It is helpful to understand when view state helps you and when it hinders your page's performance. For example, if you are binding a server control to data on every round trip, saved view state is not useful, because the control's values are replaced with new values during data binding. In that case, disabling view state saves processing time and reduces the size of the page.

    View state is enabled for all server controls by default. To disable it, set the control's EnableViewState property to false, as in the following DataGrid server control example:

    <asp:datagrid EnableViewState="false" datasource="..." 
       runat="server"/>
    

    You can also disable view state for an entire page by using the @ Page directive. This is useful when you do not post back to the server from a page:

    <%@ Page EnableViewState="false" %>
    

    Note

    The EnableViewState attribute is also supported in the @ Control directive to specify whether view state is enabled for a user control.

    To analyze the size of view state used by the server controls on your page, enable tracing for the page by including the trace="true" attribute in the @ Page directive. In the trace output, look at the Viewstate column of the Control Hierarchy table. For information about tracing and how to enable it, see ASP.NET Tracing Overview.

  • Leave buffering on unless you have a specific reason to turn it off   There is a significant performance cost for disabling buffering of ASP.NET Web pages. For more information, see the Buffer property.

  • Use the Transfer method of the Server object or cross-page posting to redirect between ASP.NET pages in the same application   For details, see Redirecting Users to Another Page.

State Management

The following guidelines suggest ways to make state management efficient.

  • Disable session state when you are not using it   Not all applications or pages require per-user session state; you should disable session state if it is not needed. To disable session state for a page, set the EnableSessionState attribute in the @ Page directive to false, as in the following example:

    <%@ Page EnableSessionState="false" %>
    

    Note

    If a page requires access to session variables but will not create or modify them, set the EnableSessionState attribute in the @ Page directive to ReadOnly.

    You can also disable session state for XML Web service methods. For more information, see XML Web Services Created Using ASP.NET and XML Web Service Clients.

    To disable session state for an application, set the Mode attribute to Off in the SessionState section of the application's Web.config file, as in the following example:

    <sessionState mode="Off" />
    
  • Choose the appropriate session-state provider for your application needs   ASP.NET provides multiple ways to store session data for your application: in-process session state, out-of-process session state as a Windows service, and out-of-process session state in a SQL Server database. (You can also create a custom session state provider to store session data in a data store of your choosing.) Each has its advantages, but in-process session state is by far the fastest solution. If you are storing only small amounts of volatile data in session state, it is recommended that you use the in-process provider. Out-of-process session state options are useful if you scale your application across multiple processors or multiple computers, or where you would like to retain session data if a server or process is restarted. For more information, see ASP.NET Session State.

Data Access

The following guidelines suggest ways to make data access in your application efficient.

  • Use SQL Server and stored procedures for data access   Of all the data access methods provided by the .NET Framework, using SQL Server for data access is the recommended choice for building high-performance, scalable Web applications. When using the managed SQL Server provider, you can get an additional performance boost by using compiled stored procedures wherever possible instead of SQL commands. For information about using SQL Server stored procedures, see Configuring Parameters (ADO.NET).

  • Use the SqlDataReader class for a fast forward-only data cursor   The SqlDataReader class provides a forward-only data stream retrieved from a SQL Server database. If you can use a read-only stream in your ASP.NET application, the SqlDataReader class offers higher performance than the DataSet class. The SqlDataReader class uses SQL Server's native network data-transfer format to read data directly from a database connection. For example, when binding to the SqlDataSource control, you will achieve better performance by setting the DataSourceMode property to DataReader. (Using a data reader incurs loss of some functionality.) Also, the SqlDataReader class implements the IEnumerable interface, which enables you to bind data to server controls as well. For more information, see the SqlDataReader class. For information about how ASP.NET accesses data, see Accessing Data with ASP.NET.

  • Cache data and page output whenever possible   ASP.NET provides mechanisms for caching page output or data when they do not need to be computed dynamically for every page request. In addition, designing pages and data requests to be cached, particularly in areas of your site where you expect heavy traffic, can optimize the performance of those pages. Using the cache appropriately can improve the performance of your site more than using any other feature of the .NET Framework.

    When using ASP.NET caching, note the following. First, do not cache too many items. There is a memory cost for caching each item. Items that are easily recalculated or rarely used should not be cached. Second, do not assign cached items a short expiration time. Items that expire quickly cause unnecessary turnover in the cache and can cause extra work for cleanup code and for the garbage collector. You can monitor the turnover in the cache due to items expiring by using the Cache Total Turnover Rate performance counter associated with the ASP.NET Applications performance object. A high turnover rate can indicate a problem, especially when items are removed before they expire. (This situation is sometimes known as memory pressure.)

    For information about how to cache page output and data requests, see ASP.NET Caching Overview.

  • **Use SQL cache dependency appropriately   **ASP.NET supports both table-based polling and query notification, depending on the version of SQL Server you are using. Table-based polling is supported by all versions of SQL Server. In table-based polling, if anything in a table changes, all listeners are invalidated. This can cause unnecessary churn in the application. Table-based polling is not recommended for tables that have many frequent changes. For example, table-based polling would be recommended on a catalog table that changes infrequently. It would not be recommended for an orders table, which would have more frequent updates. Query notification is supported by SQL Server 2005. Query notification supports specific queries, which reduces the number of notifications sent when a table is changed. While it can provide better performance than table-based polling, it does not scale to thousands of queries.

    For more information on SQL cache dependency, see Walkthrough: Using ASP.NET Output Caching with SQL Server or Caching in ASP.NET with the SqlCacheDependency Class.

  • **Use data source paging and sorting rather the UI (user interface) paging and sorting   **The UI paging feature of data controls such as DetailsView and GridView can be used with any data source object that supports the ICollection interface. For each paging operation, the data control queries the data source for the entire data collection and selects the row or rows to display, discarding the remaining data. If a data source implements DataSourceView and if the CanPage property returns true, the data control will use data source paging instead of UI paging. In that case, the data control will query for only the row needed for each paging operation. Thus, data source paging is more efficient than UI paging. Only the ObjectDataSource data source control supports data source paging. To enable data source paging on other data source controls, you must inherit from the data source control and modify its behavior.

  • **Balance the security benefit of event validation with its performance cost   **Controls that derive from the System.Web.UI.WebControls and System.Web.UI.HtmlControls classes can validate that an event originated from the user interface that was rendered by the control. This helps prevent the control from responding to spoofed event notification. For example, the DetailsView control can prevent processing of a Delete call (which is not inherently supported in the control) and being manipulated into deleting data. This validation has some performance cost. You can control this behavior using the EnableEventValidation configuration element and the RegisterForEventValidation method. The cost of validation depends on the number of controls on the page, and is in the range of a few percent.

    Security Note:

    It is strongly recommended that you do not disable event validation. Before disabling event validation, you should be sure that no postback could be constructed that would have an unintended effect on your application.

  • **Avoid using view state encryption unless necessary   **View state encryption prevents the users from being able to read the values in the hidden view state field. A typical scenario is a GridView control that carries an identifier field in the DataKeyNames property. The identifier field is needed to coordinate updates to records. Because you do not want the identifier visible to users, you can encrypt view state. However, encryption has a constant performance cost for initialization and an additional cost that depends on the size of view state being encrypted. The encryption is set up for each page load, so the same performance effect occurs on every page load.

  • **Use SqlDataSource caching, sorting, and filtering   **If the DataSourceMode property of the SqlDataSource control is set to DataSet, the SqlDataSource is able to cache the result set from a query. If data is cached in this way, the filtering and sorting operations of the control (configured with the FilterExpression and SortParameterName properties) use the cached data. In many cases your application will run faster if you cache the entire dataset, and use the FilterExpression and SortParameterName properties to sort and filter, rather than using SQL queries with "WHERE" and "SORT BY" clauses that access the database for each select action.

Web Applications

The following guidelines suggest ways to make your Web applications as a whole work efficiently.

  • Consider precompiling   A Web application is batch-compiled on the first request for a resource such as an ASP.NET Web page. If no page in the application has been compiled, batch compilation compiles all pages in a directory in chunks to improve disk and memory usage. You can use the ASP.NET Compilation Tool (Aspnet_compiler.exe) to precompile a Web application. For in-place compilation, the compilation tool calls the ASP.NET runtime to compile the site in the same manner as when a user requests a page from the Web site. You can precompile a Web application so that the UI markup is preserved, or precompile the pages so that source code cannot be changed. For more information, see How to: Precompile ASP.NET Web Sites.

  • Run Web applications out-of-process on Internet Information Services 5.0   By default, ASP.NET on IIS 5.0 will service requests using an out-of-process worker process. This feature has been tuned for fast throughput. Because of its features and advantages, running ASP.NET in an out-of-process worker process is recommended for production sites.

  • Recycle processes periodically   You should recycle processes periodically, for both stability and performance. Over long periods of time, resources with memory leaks and bugs can affect Web server throughput, and recycling processes cleans up memory from these types of problems. However, you should balance the need to periodically recycle with recycling too often, because the cost of stopping the worker process, reloading pages, and re-obtaining resources and data can override the benefits of recycling.

    ASP.NET Web applications running on Windows Server 2003, which uses IIS 6.0, do not need to have the process model setting adjusted because ASP.NET will use the IIS 6.0 process model settings.

  • Adjust the number of threads per worker process for your application if necessary   The request architecture of ASP.NET tries to achieve a balance between the number of threads executing requests and available resources. The architecture allows only as many concurrently executing requests as there is CPU power available. This technique is known as thread gating. However, there are conditions in which the thread-gating algorithm does not work well. You can monitor thread gating in the Windows Performance monitor using the Pipeline Instance Count performance counter associated with the ASP.NET Applications performance object.

    When an ASP.NET Web page calls an external resource, such as when performing database access or XML Web service requests, the page request generally stops until the external resource responds, freeing the CPU to process other threads. If another request is waiting to be processed and a thread is free in the thread pool, the waiting request begins processing. The result can be a high number of concurrently executing requests and many waiting threads in the ASP.NET worker process or application pool, which hinders the Web server's throughput, adversely affecting performance.

    To mitigate this, you can manually set the limit on the number of threads in the process by changing the MaxWorkerThreads and MaxIOThreads attributes in the processModel section of the Machine.config file.

    Note

    Worker threads are for processing ASP.NET requests, while IO threads are used to service data from files, databases, or XML Web services.

    The values assigned to the process model attributes are the maximum number of each type of thread per CPU in the process. For a two-processor computer, the maximum number is twice the set value. For a four-processor computer, it is four times the set value. The defaults are good for one-processor or two- processor computers, but having 100 or 200 threads in the process for computers with more than two processors can be more detrimental than beneficial to performance. Too many threads in a process tend to slow down the server because of extra context switches, causing the operating system to spend CPU cycles on maintaining threads rather than processing requests. The appropriate number of threads is best determined through performance testing of your application.

  • For applications that rely extensively on external resources, consider enabling Web gardening on multiprocessor computers   The ASP.NET process model helps enable scalability on multiprocessor computers by distributing work to several processes, one per CPU, each with processor affinity set to a CPU. This technique is called Web gardening. If your application uses a slow database server or calls COM objects that have external dependencies (to name only two possibilities), it can be beneficial to enable Web gardening for your application. However, you should test how well your application performs in a Web garden before you decide to enable it for a production Web site.

  • Disable debug mode   Always disable debug mode before deploying a production application or conducting any performance measurements. If debug mode is enabled, the performance of your application can suffer. For syntax information about setting debug mode, see Editing ASP.NET Configuration Files.

  • Tune the configuration files for your Web server computer and for specific applications to suit your needs   By default, ASP.NET configuration is set to enable the widest set of features and to try to accommodate the most common scenarios. Some default configuration settings can be changed to improve the performance of your applications, depending on what features you use. The following list includes configuration settings you should consider:

    • Enable authentication only for applications that need it   By default, the authentication mode for ASP.NET applications is Windows, or integrated NTLM. In most cases it is best to disable authentication in the Machine.config file and enable it in the Web.config files only for applications that need it.

    • Configure your application to the appropriate request and response encoding settings   The ASP.NET default encoding is UTF-8. If your application uses only ASCII characters, configure your application for ASCII for a slight performance improvement.

    • Consider disabling AutoEventWireup for your application   Setting the AutoEventWireup attribute to false in the Machine.config file means that the page will not bind page events to method based on a name match (for example, Page_Load). If you disable AutoEventWireup, your pages will get a slight performance boost by leaving the event wiring to you instead of performing it automatically.

      If you want to handle page events, use one of two strategies. The first strategy is to override the methods in the base class. For example, you can override the OnLoad method of the Page object for the page load event instead of using a Page_Load method. (Be sure to call the base method to ensure all events are raised.) The second strategy is to bind to the events using the Handles keyword in Visual Basic or delegate wire-up in C#.

    • Remove unused modules from the request-processing pipeline   By default, all features are left active in the HttpModules node in your server computer's Machine.config file. Depending on which features your application uses, you may be able to remove unused modules from the request pipeline to get a small performance boost. Review each module and its functionality and customize it to your needs. For example, if you do not use session state and output caching in your application, you can remove each from the HttpModules list so that requests do not have to invoke these modules without performing any other meaningful processing.

Coding Practices

The following guidelines suggest ways to write efficient code.

  • Do not rely on exceptions in your code   Exceptions can cause performance to suffer significantly, so you should avoid using them as a way to control normal program flow. If it is possible to detect in code a condition that would cause an exception, do so rather than catching the exception itself and handling the condition. Common scenarios to detect in code include checking for null, assigning a value to a String that will be parsed into a numeric value, or checking for specific values before applying math operations. The following example demonstrates code that could cause an exception and code that tests for a condition. Both produce the same result.

    // This is not recommended.
    try {
       result = 100 / num;
    }
    catch (Exception e) {
      result = 0;
    }
    
    // This is preferred.
    if (num != 0)
       result = 100 / num;
    else
      result = 0;
    
    ' This is not recommended.
    Try
       result = 100 / num
    Catch (e As Exception)
      result = 0
    End Try
    
    ' This is preferred.
    If Not (num = 0)
       result = 100 / num
    Else
      result = 0
    End If
    
  • Rewrite call-intensive COM components in managed code   The .NET Framework provides an easy way to interoperate with traditional COM components. The benefit is that you can take advantage of the features of .NET while preserving your existing investments in COM components. However, there are some circumstances in which the performance cost of keeping your old components makes it worthwhile to migrate your components to managed code. Every situation is unique, and the best way to decide whether you need to port a component is to run performance measurements on your Web site. It is recommended that you investigate porting any COM component that is called often to managed code.

    In many cases, it is not possible to migrate legacy components to managed code, particularly when initially migrating your Web applications. In such circumstances, one of the biggest performance impediments is marshaling data from unmanaged to managed environments. Therefore, when interoperating, perform as many tasks as possible on one side or the other and then make a single call rather than a series of smaller calls. For example, all strings in the common language runtime are in Unicode, so you should convert any strings to Unicode in your component before you make a call to managed code.

    Release any COM objects or native resources as soon as they have finished processing. This enables other requests to use them and minimizes the performance issues associated with requiring the garbage collector to release them later.

  • Avoid single-threaded apartment (STA) COM components   By default, ASP.NET does not allow STA COM components to run in a page. To run them, you must include the ASPCompat=true attribute in the @ Page directive in the .aspx file. This switches the thread pool used for page execution to an STA thread pool, while also making the HttpContext and other built-in objects available to the COM object. Avoiding STA COM components is a performance optimization because it avoids any call marshaling from multithreaded apartment (MTA) to STA threads.

    If you must use an STA COM component, avoid making numerous calls during an execution and try to send as much information as possible during each call. Also, avoid creating STA COM components during the construction of the page. For example, in the following code, the SampleSTAComponent would be instantiated at page construction time, which is created from a thread that is not the STA thread that runs the page. This can have an adverse performance impact, since it will require marshaling between MTA and STA threads to construct the page.

    <%@ Page Language="VB" ASPCompat="true" %>
    <script runat=server>
    Dim myComp as new SampleSTAComponent()
    Public Sub Page_Load()
        myComp.Name = "Sample"
    End Sub
    </script>
    <html>
    <%
        Response.Write(Server.HtmlEncode(myComp.SayHello))
    %>
    </html>
    

    The preferred mechanism is to delay object creation until the code is executing under an STA thread, as in the following example:

    <%@ Page Language="VB" ASPCompat="true" %>
    <script runat=server>
    Dim myComp
    Public Sub Page_Load()
        myComp = new SampleSTAComponent()
        myComp.Name = "Sample"
    End Sub
    </script>
    <html>
    <%
        Response.Write(Server.HtmlEncode(myComp.SayHello))
    %>
    </html>
    

    The recommended practice is to construct COM components and external resources only when needed or in the Page_Load method.

    You should never store STA COM components in a shared resource (such as the cache or session state) where they can be accessed by threads other than the one that constructed them. Even if an STA thread makes a call to an STA COM component, only the thread that constructed the STA COM component can service the call, which entails marshaling the call to the creator thread. This marshaling can have significant performance penalties and scalability problems. In such cases, consider making the COM component into an MTA COM component or rewriting the component in managed code.

See Also

Concepts

Optimizing Performance in ASP.NET

Monitoring ASP.NET Application Performance

Performance Counters for ASP.NET

ASP.NET Caching