asynchronous ASP.NET


Thursday, October 11, 2007

If an ASP.NET webpage is involved in intense I/O operations (e.g., intense database, file, or web service calls) and the webpage is heavily used, performance problems can occur if worker process threads are waiting for I/O threads to become available (see my last article for more information).  In order to create scalable, efficient ASP.NET sites, asynchronous methods can be utilized to free up worker threads that would normally be frozen (and unusable) waiting for I/O threads to return.  The simplest way to employ asynchronous code in your ASPX page is to use the AddOnPreRenderCompleteAsync method in the Page_Load page method.  This method accepts two parameters, both delegates, which allow you to specify methods that will handle the beginning and end of the asynchronous operation you are trying to accomplish.  To make this asynchronous method work, you must also make sure to add the Async="true" page attribute to your web form’s Page directive.

This asynchronous code will fire between the PreRender and PreRenderComplete events of the page’s event lifecycle.  This means that the worker thread that was assigned to the HTTP request for this page will be put back in the thread pool right after the PreRender event has completed.  When the asynchronous code has finished, a worker thread will be pulled from the worker thread pool and the page will continue along its event lifecycle.

To see an example of this asynchronous model in action, you can go to MSDN (http://msdn2.microsoft.com/en-us/library/ba6dhx95(VS.80).aspx) or check out Jeff Prosise’s webcast from TechEd 2007 (http://msevents.microsoft.com/cui/WebCastEventDetails.aspx?EventID=1032345482&EventCategory=4&culture=en-US&CountryCode=US).


5:45:55 PM   | c# | asp.net | ado.net |
 
efficient paging via ObjectDataSource paging


Friday, August 31, 2007

When using paging with data-bound controls, performance issues can arise when accessing large amounts of data.  By default, all rows needed for the data-bound control will be acquired even if paging is only exposing a small subset of these rows per page index.  Utilizing an ObjectDataSource (ODS) and enabling paging functionality within the ODS allows a specified amount of rows to be accessed from the database per page index.  Setting the ODS to use paging requires a few extra properties to be utilized within the ODS: EnablingPaging, MaximumRowsParameterName, StartRowIndexParameterName, and SelectCountMethod to be specific.

In this example, I am utilizing the following ODS:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" EnablePaging="True" SelectCountMethod="GetDepartmentCount" SelectMethod="GetDepartments" TypeName="TempBLL" MaximumRowsParameterName="maximumRows" StartRowIndexParameterName="startRowIndex">

The ODS points to the following class:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Data.SqlClient;

[System.ComponentModel.DataObject]
public class TempBLL
{
    [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select,
    
true)]

    public DataTable GetDepartments(int startRowIndex, int maximumRows)
    {
        using (SqlConnection connection = new SqlConnection("Data Source=localhost;Initial
        Catalog=AdventureWorks;Trusted_Connection=Yes"
))

        {
            SqlCommand command = connection.CreateCommand();
            command.CommandType = CommandType.StoredProcedure;
            command.CommandText = "HumanResources.uspGetDepartments";
            command.Parameters.Add(new SqlParameter("@FirstRow", SqlDbType.Int));
            command.Parameters["@FirstRow"].Value = startRowIndex + 1;
            command.Parameters.Add(new SqlParameter("@MaxRows", SqlDbType.Int));
            command.Parameters["@MaxRows"].Value = maximumRows;
            DataSet result = new DataSet();
            SqlDataAdapter adapter = new SqlDataAdapter(command);
            adapter.Fill(result);

            HttpContext.Current.Items["DepartmentRowCount"] = (int)result.Tables[1].Rows[0][0];
            return result.Tables[0];
        }
    }

    public int GetDepartmentCount()
    {
        return (int)HttpContext.Current.Items["DepartmentRowCount"];
    }
}

When using paging within the ODS, the method specified in the SelectMethod property must, at the least, have a parameter to indicate the starting row index and a parameter to indicate how many rows should be returned per page.  The names of these parameters are set in the StartRowIndexParameterName and MaximumRowsParameterName properties of the ODS respectively.

The purpose of the customized SelectMethod is to provide a mechanism by which only a pre-determined number of rows will be returned from the database per page index.  The ODS will take care of passing in the appropriate arguments for the two custom parameters.  The “maximumRows” parameter is determined from the data-bound control’s PageSize property and the “startRowIndex” parameter automatically increases based on the currently selected page index.  For example, if you only want 5 rows to show up per page on a GridView, simply set the GridView’s PageSize property to 5 and the ODS will automatically send the value 5 into the SelectMethod’s parameter that is named in the MaximumRowsParameterName property.  This value can then be used to limit the amount of rows being grabbed from the database (as I did with the uspGetDepartments stored procedure).

The SelectCountMethod property points to the second method within TempBLL.cs.  This method lets the data-bound control know the total amount of rows so it can display the correct number of page links.  This is necessary because the paging enabled ODS will only be returning a subset of the total rows.  I am utilizing the HttpContext in order to prevent two round-trips to the database since the SelectCountMethod is called after the SelectMethod.  I originally got the HttpContext idea from Fritz Onion.

I am using SQL Server 2005 in this example, so I am able to use the ROW_NUMBER() function in my T-SQL to grab the appropriate group of rows per page index:

CREATE PROCEDURE [HumanResources].[uspGetDepartments]
      @FirstRow int,
      @MaxRows int
AS
BEGIN
      WITH OrderedDepartments AS
      (
            SELECT d.DepartmentID, d.[Name], ROW_NUMBER() OVER (ORDER BY d.[Name]) AS
           
'RowNumber'

            FROM HumanResources.Department d
      )
      SELECT od.DepartmentID, od.[Name]
      FROM OrderedDepartments od
      WHERE RowNumber BETWEEN @FirstRow AND (@FirstRow + @MaxRows) - 1 

      SELECT COUNT(*) FROM HumanResources.Department
END

With this stored procedure, I am able to get the proper subset of rows and the total row count.  The multiple result sets are placed within distinct tables when the stored procedure is called in the SelectMethod.  This allows the SelectMethod to grab the subset of rows needed for the current page index and retrieve the total row count in one database call.


11:23:08 PM   | c# | sql | asp.net | ado.net |
 
typed datasets


Monday, August 14, 2006

The power of typed datasets is a very impressive feature in Visual Studio 2005 (VS).  Creating a typed dataset is as easy as adding a dataset item to your project (an XSD file) which will launch a wizard to help you setup the dataset.  This typed dataset file is actually an XML schema definition file that describes the dataset and will be compiled at runtime only when needed.  After setting up a typed dataset, you are equipped with many standard abilities such as SELECT, INSERT, DELETE, and UPDATE capabilities.  But in addition to these functions (which can be highly customized), there are many ways to increase the functionality of the dataset.  One way is to add customized SQL queries into the TableAdapters created within the typed dataset.  These SQL calls can be ad hoc statements or stored procedures (new or already existing).  Each of these customized queries is referenced through a method call added to the typed dataset class.  As can be imagined, there is quite a bit of auto-generated code being created by VS behind the scenes (switch to class view and you can analyze this code by choosing 'Go To Definition' from any of the methods that are part of the dataset's class).  Make sure not to add any custom code into these auto-generated classes because it will be destroyed if the class is regenerated.  Instead, with .NET 2.0's partial class functionality, it is fairly easy to add any code customization you may want to your own class files.  In case you are new to the concept of partial classes, they make it possible to split a class's definition over two or more source files.  So, VS uses the auto-generated file for its code and you can use your own file to add your methods, properties, etc.  At compile time, all partial class definitions are merged to create a single class definition.

Typed datasets come in very handy when working with one of the most common asp.net controls, the gridview.  The gridview can easily be tied to a typed dataset and many standard functions (e.g., sorting and paging) are setup for you.  This saves the developer time as they no longer need to concentrate on this type of common, repetitive coding.

Another important aspect of typed datasets is type safety.  Since typed datasets have a schema defining the dataset's data types, data mismatches will generate errors at compile time, making typed datasets much more robust.

6:58:22 PM   | c# | asp.net | ado.net |
 
3-tier architecture


Saturday, August 12, 2006

A little while back I made an attempt to find documentation that would help me define the most appropriate way to design .NET projects.  I was aware that it was important to have a Data Access Layer (DAL), a Business Logic Layer (BLL), and a User Interface Layer (UI), but I was unable to successfully find any clear or concise literature to help define architecture guidelines.

Well, yesterday I was very happy to find that Microsoft has released a whole series of tutorials entitled "3-Tier Architecture for ASP.NET 2.0."  These articles were released in June of this year and provide some very useful guidelines on how to get started with a 3-tier architecture in your .NET applications.

11:38:55 PM   | c# | asp.net | ado.net |