Design and Implementation Guidelines for Web Clients

Retired Content
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

 

patterns & practices Developer Center

Microsoft Corporation

November 2003

Applies to:
   Microsoft .NET Framework
   ASP.NET

Contents

How To: Define a Formatter for Business Entity Objects

How To: Perform Data Binding in ASP.NET Web Forms

How To: Design Data Maintenance Forms to Support Create, Read, Update, and Delete Operations

How To: Execute a Long-Running Task in a Web Application

How To: Use the Trusted Subsystem Model

How To: Use Impersonation/Delegation with Kerberos Authentication

How To: Use Impersonation/Delegation with Basic or Forms Authentication

How To: Localize Windows Forms

How To: Define a Catch-All Exception Handler in Windows Forms-based Applications

Note   This code samples focus on the concepts in this appendix and as a result may not comply with Microsoft security recommendations. The code samples presented in this appendix have been written for Windows Server 2003 and the .NET Framework 1.1.

How To: Define a Formatter for Business Entity Objects

This example shows how to define a custom formatter class to control how business entity objects are displayed in the presentation layer.

There are three classes in this example:

  • ReflectionFormattable–This is a base class that performs custom formatting for business entity objects. The class uses a formatting string to display selective properties of a business entity object in a particular format. For example, a formatting string such as "{LastName}, {Name}" displays the LastName and Name properties for a business entity object, separated by a comma and a space.
  • CustomerEntity–This is a sample business entity class. CustomerEntity inherits from ReflectionFormattable, to make use of its formatting capabilities.
  • CustomFormatting–This is a simple ASP.NET Web page that uses the formatting capabilities of the ReflectionFormattable class to display a CustomerEntity object in a particular format.

The following sections describe these classes.

Defining the ReflectionFormattable Class

The ReflectionFormattable class implements the IFormattable interface, and provides basic formatting capabilities that can be inherited by business entity objects.

ReflectionFormattable has a ToString method that receives a format string and extracts property names enclosed in braces {}. The method then uses reflection to obtain the value of each requested property on the current business entity object.

using System;
using System.Collections;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;

// Defines a custom formatting syntax that is used to create string using the 
// properties for a given entity.
[ComVisible(false)]
public class ReflectionFormattable
    : IFormattable
{
  // The regular expresion used to parse the custom syntax
  const string format = @"\{(\w+)\}";

  // Object to use for synchronized operations in thread-safe code
  static object SyncRoot = new object();

  // Cached compiled regular expression
  static Regex regEx = null;
  
  // Constructor safe for multi-threaded operations.
  protected ReflectionFormattable()
  {
    if (regEx == null)
    {
      lock (SyncRoot)
      {
        if (regEx == null)
        {
          regEx = new Regex(format, RegexOptions.Compiled);
        }
      }
    }
  }

  // ToString overload for the formattable object
  public string ToString(string format, IFormatProvider formatProvider)
  {
    if (format != null && format.Length != 0 && format.Trim().Length != 0)
    {
      string tempString = format;

      // Use the regular expression
      MatchCollection matchCollection;
      lock (typeof(Regex))
      {
        matchCollection = regEx.Matches( format );
      }

      // Use the matches to find the properties on the current instance
      foreach (Match m in matchCollection)
      {
        if (m.Groups.Count > 1)
        {
          foreach (Capture c in m.Groups[1].Captures)
          {
            if (c.Value != null && 
                c.Value.Length != 0 && c.Value.Trim().Length != 0)
            {
              tempString = tempString.Replace( "{" + c.Value + "}", 
                                               GetPropertyValue(c.Value) );
            }
          }
        }
      }

      // Return the formatted string
      return tempString;
    }
    else
    {
      // Call base ToString method, because the format is null or an empty string
      return ToString();
    }
  }

  // Use reflection to get the value of a property with a specified name
  protected string GetPropertyValue( string name )
  {
    PropertyInfo pi = this.GetType().GetProperty(name);
    if (pi != null)
    {
      object value = pi.GetValue(this, null);
      return value == null ? "" : value.ToString();
    }
    return "{" + name + "}";
  }
}
  

Defining the CustomerEntity Class

CustomerEntity is a sample business entity class for this sample. The class has three properties (and associated private fields), and inherits formatting capabilities from ReflectionFormattable.

using System;
using System.Runtime.InteropServices;

public class CustomerEntity 
  : ReflectionFormattable
{
  // Entity ID
  public int ID
  {
    get { return _id;  }
    set { _id = value; }
  } int _id = 0;
    
  // Customer name
  public string Name
  {
    get { return _name;  }
    set { _name = value; }
  } string _name = "";

  // Customer last name
  public string LastName
  {
    get { return _lastName;  }
    set { _lastName = value; }
  } string _lastName = "";

  // Customer date created
  public DateTime DateCreated
  {
    get { return _dateCreated;  }
    set { _dateCreated = value; }
  } DateTime _dateCreated = DateTime.Now;
}
  

Defining the CustomFormatting Class

CustomFormatting is an ASP.NET Web page that asks the user to enter details for a CustomerEntity object. When the user clicks the Set values button, the customer details are displayed in the format {ID}–{LastName}, {FirstName}.

Figure B.1 shows how the page appears in the browser.

Click here for larger image

Figure B.1. Formatting business entity objects in an ASP.NET Web page

The relevant portions of code for the CustomFormatting class are shown in the following code sample. Notice that the SetTitle method calls ToString on the CustomerEntity object, passing the format string "({ID}) - {LastName}, {Name}" as the first parameter:

 [ComVisible(false)]
public class CustomFormatting : System.Web.UI.Page
{
  private CustomerEntity customer = new CustomerEntity();

  protected System.Web.UI.WebControls.TextBox txtId;
  protected System.Web.UI.WebControls.TextBox txtName;
  protected System.Web.UI.WebControls.TextBox txtLastName;
  protected System.Web.UI.WebControls.Calendar clDateCreated;
  protected System.Web.UI.WebControls.ImageButton btnCalendar;

  protected System.Web.UI.WebControls.Button btnClear;
  protected System.Web.UI.WebControls.Button btnSetValues;

  protected System.Web.UI.WebControls.Label lblDateCreated;
  protected System.Web.UI.WebControls.Label lblTitle;
  protected System.Web.UI.WebControls.Label lblMessage;
  
  private void Page_Load(object sender, System.EventArgs e)
  {
    if (!IsPostBack)
    {
      clDateCreated.SelectedDate = DateTime.Now;
      lblDateCreated.Text = clDateCreated.SelectedDate.ToShortDateString();
    }

    try
    {
      // Populate the CustomerEntity object with data 
      customer.ID = Int32.Parse(txtId.Text);
      customer.Name = txtName.Text;
      customer.LastName = txtLastName.Text;
      customer.DateCreated = clDateCreated.SelectedDate;

      // Set the title on the form
      SetTitle();
    }
    catch (Exception ex)
    {
      // Exception-handling code...
    }
    lblMessage.Visible = false;
  }

  private void btnSetValues_Click(object sender, System.EventArgs e)
  {
    try
    {
      // Populate the CustomerEntity object with data 
      customer.ID = Int32.Parse(txtId.Text);
      customer.Name = txtName.Text;
      customer.LastName = txtLastName.Text;
      customer.DateCreated = clDateCreated.SelectedDate;

      // Set the title on the form
      SetTitle();
    }
    catch (Exception ex)
    {
      lblMessage.Text = "Incorrect data: " + ex.Message;
      lblMessage.Visible = true;
    }
  }

  private void SetTitle()
  {
    lblTitle.Text = (customer.ID == 0) ? 
                        "New customer" : 
                         customer.ToString("({ID}) - {LastName}, {Name}", null);
  }

  // Plus other code...
}
  

How To Perform Data Binding in ASP.NET Web Forms

Data binding in ASP.NET Web Forms is more straightforward than data binding in Windows Forms:

  • In Windows Forms, objects are kept in memory. A notification mechanism is required between the objects and the controls they are bound to.
  • In Web Forms, objects are bound to controls at the server. The data-bound output is returned to the client; there is no live data at the client. All user actions, such as editing and paging through the data, are achieved by postbacks and ViewState interactions at the server.

As a consequence of this simplified behavior in Web Forms, entity classes do not have to implement any special interfaces in order to support data binding. Likewise, you can easily create data-bound collections of entity objects by adding them to an ArrayList or any class that implements IEnumerable.

The following sections describe how to perform data binding in three particular scenarios:

  • Data Binding an Entity Object to Simple Controls
  • Data Binding a Collection of Entity Objects to a DataList Control
  • Data Binding a Collection of Entity Objects to a DataGrid Control

Code samples are included where appropriate, to illustrate how to define entity classes and type-safe collections, and to show how to bind them to Web server controls.

Note   For simplicity in this example, the application does not validate the details entered by the user.

Data Binding an Entity Object to Simple Controls

This section describes how to data bind entity objects to simple controls on a Web Form, such as labels and text boxes.

This section includes the following topics:

  • Defining an Entity Class for Data Binding
  • Adding an Entity Object to a Web Form
  • Specifying Data Binding Expressions for a Control
  • Performing Data Binding at Run Time

A simple entity class and ASP.NET Web Form are presented to demonstrate the key points in this section.

Defining an Entity Class for Data Binding

If you want to data bind entity objects to Web server controls, it is a good idea for your entity class to inherit from System.ComponentModel.Component. This enables Visual Studio .NET to display entity objects in the Components Tray in the Designer window, to simplify design-time usage of the entity objects.

When you define a class that inherits from System.ComponentModel.Component, Solution Explorer displays the class with a "UI control" icon by default. If you double-click the icon, Visual Studio .NET displays the class in design view instead of code view. To suppress this unwanted behavior, annotate your class with the [System.ComponentModel.DesignerCategory("Code")] attribute. Solution Explorer then displays the class with a "code" icon, and double-clicking the icon opens the class in code view.

The following code sample shows an entity class named CustomerEntity to illustrate these points.

using System;
using System.ComponentModel;

[System.ComponentModel.DesignerCategory("Code")]
public class CustomerEntity : Component
{
  // Customer ID
  public int ID
  {
    get { return _id;  }
    set { _id = value; }
  } int _id = 0;
    
  // Customer first name
  public string FirstName
  {
    get { return _name;  }
    set { _name = value; }
  } string _name = "";

  // Customer last name
  public string LastName
  {
    get { return _lastName;  }
    set { _lastName = value; }
  } string _lastName = "";

  // Customer date created
  public DateTime DateCreated
  {
    get { return _dateCreated;  }
    set { _dateCreated = value; }
  } DateTime _dateCreated = DateTime.Now;
}
  

The CustomerEntity class inherits from System.ComponentModel.Component for design-time support in Visual Studio .NET. Also, the class is annotated with the [System.ComponentModel.DesignerCategory("Code")] attribute as described previously. CustomerEntity defines four public properties, to get and set private fields defined in the class.

Adding an Entity Object to a Web Form

When you define a class that inherits from System.ComponentModel.Component, you can add instances of the class directly to a Web Form in Visual Studio .NET. There are two ways to do this:

  • Add the entity class to the Toolbox, and then drag an instance of the class onto the form.

  • Create an entity object programmatically in the code-behind class for the Web Form. In this approach, you must declare a protected variable and initialize it in the InitializeComponent method. The following code sample illustrates this approach.

    public class SimpleBinding : System.Web.UI.Page
    {
      // Declare a protected instance variable, to refer to an entity object
      protected CustomerEntity customer;
    
      private void InitializeComponent()
      {  
        // Create an entity object, and assign it to the instance variable
        this.customer = new CustomerEntity();
    
        // Plus other component-initialization code...
      }
    
      // Plus other members in the Web Form...
    } 
    
    

When you create the entity object using one of these two approaches, the entity object appears in the Component Tray in the Visual Studio .NET Designer.

Specifying Data Binding Expressions for a Control

This section describes how to specify data binding expressions for a control to bind a particular property on the control to a specific property on an entity object.

The sample form shown in Figure B.2 has four labels that can be bound to the ID, FirstName, LastName, and DateCreated properties of a CustomerEntity object. The form also has buttons to allow the user to navigate backward and forward through a collection of CustomerEntity objects:

Ff647281.f0bdiforwc02(en-us,PandP.10).gif

Figure B.2. Web Form containing simple controls, which can be data bound to an entity object

To specify a data binding expression for a control

  1. Select the control that you want to perform data binding on.
  2. In the Properties window, locate the (DataBindings) property and click the ellipsis button (…).
  3. In the DataBindings property editor, select one of the control's bindable properties. Then use either simple binding or complex binding to bind the property as appropriate.

When you bind a property on a control, Visual Studio .NET adds a binding expression to the .aspx file to describe the data binding. For example, the following code sample shows how an <asp:label> control can be data-bound to the DateCreated property on a customer component.

<asp:label 
      id="lblDateCreated" 
       
      Text='<%# DataBinder.Eval(customer, "DateCreated", "{0:d}") %>'>
</asp:label>
  

The next section describes how and when the binding expressions are evaluated at run time.

Performing Data Binding at Run Time

To evaluate a binding expression at run time, call the DataBind method on the control. You can also call the DataBind method at the page level, which conveniently causes the call to be applied recursively to all controls on the page.

This section shows a sample implementation for the Web Form introduced earlier to illustrate how to perform data binding. The form creates some sample CustomerEntity objects in its static constructor and inserts them into an ArrayList. The form also has an integer variable to indicate the index of the currently-bound CustomerEntity object:

public class SimpleBinding : System.Web.UI.Page
{
  // CustomerEntity component (as defined earlier), which is bound to controls
  protected CustomerEntity customer;

  // Collection of sample CustomerEntity objects
  private static ArrayList CustomerList = new ArrayList();

  // Index of currently-bound CustomerEntity object
  private int current = 0;

  // Static constructor, to create CustomerEntity objects and add to collection
  static SimpleBinding()
  {
    // Set customers on the list
    CustomerEntity tempCustomer = new CustomerEntity();
    tempCustomer.ID = 5;
    tempCustomer.FirstName = "Guy";
    tempCustomer.LastName = "Gilbert";
    tempCustomer.DateCreated = new DateTime( 1948, 3, 9 );
    CustomerList.Add(tempCustomer);

    tempCustomer = new CustomerEntity();
    tempCustomer.ID = 11;
    tempCustomer.FirstName = "Kendall";
    tempCustomer.LastName = "Keil";
    tempCustomer.DateCreated = new DateTime( 1974, 9, 4 );
    CustomerList.Add(tempCustomer);

    // Plus other sample entities...
  }

  // Plus other members...
}
  

The form's OnPreRender method persists the current index in the page ViewState, so that the current index is available in a postback.

public class SimpleBinding : System.Web.UI.Page
{
  protected override void OnPreRender(EventArgs e)
  {
    ViewState["Index"] = current;
    base.OnPreRender (e);
  }

  // Plus other members...
}
  

The form's Page_Load method retrieves the current index from the ViewState. The method sets the "customer" variable to refer to the current CustomerEntity object in the ArrayList, and then calls DataBind to bind all the controls on the form. This causes the data-bound labels on the form to display the details for the current customer. This causes the form to display the details for the newly selected CustomerEntity object.

public class SimpleBinding : System.Web.UI.Page
{
  private void Page_Load(object sender, System.EventArgs e)
  {
    if (IsPostBack)
    {
      current = (int)ViewState["Index"];
    }

    customer = CustomerList[current] as CustomerEntity;
    DataBind();
  }

  // Plus other members...
}
  

The form also has event handler methods for the previous button (<) and next button (>) on the form.

public class SimpleBinding : System.Web.UI.Page
{
  private void btnPrev_Click(object sender, System.EventArgs e)
  {
    if (--current <= 0)
    {
      current = 0;
    }
    customer = CustomerList[current] as CustomerEntity;
    DataBind();
  }

  private void btnNext_Click(object sender, System.EventArgs e)
  {
    if (++current >= CustomerList.Count) 
    {
      current = CustomerList.Count - 1;
    }
    customer = CustomerList[current] as CustomerEntity;
    DataBind();    
  }

  // Plus other members...
}
  

When the user clicks the previous or next buttons, the current index is decreased or increased as appropriate. The DataBind method causes the data binding expressions to be re-evaluated for each control on the form, to display the details for the newly selected CustomerEntity object.

Data Binding a Collection of Entity Objects to a DataList Control

This section describes how to bind a collection of entity objects to a DataList control. This allows the user to view all the entity objects at the same time, and to edit and delete entity objects in the collection.

This section includes the following topics:

  • Defining a Typed Entity Collection Class
  • Adding an Entity Collection Object to a Web Form
  • Designing Item Templates for a DataList Control
  • Specifying Data Binding Expressions for a DataList Control
  • Performing Data Binding at Run Time

This section uses the CustomerEntity class from the previous section, but it uses a new ASP.NET Web Form to illustrate data binding techniques for a DataList control.

Defining a Typed Entity Collection Class

A DataList control can be bound to a collection object; the DataList displays each of the collection's items in a separate row.

You can either bind a DataList to a generic collection type such as ArrayList, or to a typed collection class that works with a particular type of entity object. The latter approach is recommended, because it enables Visual Studio .NET to expose the entity's properties when you define data binding expressions at design time.

The following code sample shows a typed collection class named CustomerCollection to hold a collection of CustomerEntity objects.

using System;
using System.ComponentModel;
using System.Collections;

[System.ComponentModel.DesignerCategory("Code")]
public class CustomerCollection : CollectionBase, IComponent
{
  // Default constructor
  public CustomerCollection()
  {}

  // Add a CustomerEntity object to the collection
  public int Add(CustomerEntity customer)
  {
    return base.InnerList.Add(customer);
  }

  // Remove a CustomerEntity object from the collection
  public void Remove(CustomerEntity customer)
  {
    base.InnerList.Remove(customer);
  }

  // Access a CustomerEntity object by its index in the collection
  public CustomerEntity this[int index]
  {
    get { return base.InnerList[index] as CustomerEntity; }
    set { base.InnerList[index] = value; }
  }

  // Implement the IComponent members
  public event System.EventHandler Disposed;
  ISite IComponent.Site
  {
    get { return _site;  }
    set { _site = value; }
  } ISite _site = null;

  // Implement the IDisposable members (IComponent inherits from IDisposable)
  public event System.EventHandler Disposed;

  void IDisposable.Dispose()
  {
    // We've nothing to dispose explicitly
    if (Disposed != null)
      Disposed(this, EventArgs.Empty);
  }
}
  

CustomerCollection inherits from System.Collections.CollectionBase and provides type-safe methods to add, remove, and access items in the collection. CustomerCollection also implements the IComponent interface, to allow CustomerCollection objects to be treated as components in the Visual Studio .NET Designer.

Adding an Entity Collection Object to a Web Form

There are two ways to add an entity collection object to a Web Form in Visual Studio .NET:

  • Add the entity collection class to the Toolbox, and then drag an instance of the class onto the form.

  • Create an entity collection object programmatically in the code-behind class for the Web Form. In this approach, you must declare a protected variable and initialize it in the InitializeComponent method. The following code sample illustrates this approach.

    public class DataListBinding : System.Web.UI.Page
    {
      // Declare an instance variable to refer to an entity collection object
      protected CustomerCollection customers;
    
      private void InitializeComponent()
      {    
        this.customers = new CustomerCollection();
    
        // Plus other component-initialization code...
      }
    
      // Plus other members in the Web form...
    }
    
    

When you create the entity collection object using one of these two approaches, the object appears in the Component Tray in the Visual Studio .NET Designer.

Designing Item Templates for a DataList Control

This section describes how to design item templates for a DataList control to govern how entity objects are displayed and edited in the DataList control.

To create and configure a DataList control in a form

  1. Drag a DataList control from the Toolbox and drop it onto your Web Form.
  2. Right-click the DataList control. In the shortcut menu, point to Edit Template and then click Item Templates.
  3. In the ItemTemplate section, add simple controls (such as Label controls) to display each of the properties of an entity object.
  4. Add two buttons to the ItemTemplate section to enable the user to delete or edit the entity object. Set the buttons' CommandName properties to Delete and Edit respectively, so that the buttons raise DeleteCommand and EditCommand events on the DataList control.
  5. In the EditItemTemplate section, add editable controls (such as Textbox controls) to edit each of the properties of an entity object.
  6. Add two buttons to the EditItemTemplate section to enable the user to save or cancel edits on the entity object. Set the buttons' CommandName properties to Update and Cancel respectively, so that the buttons raise UpdateCommand and CancelCommand events on the DataList control.
  7. Right-click the DataList control, and then click End Template Editing in the shortcut menu.

Figure B.3 shows sample Item Templates to display and edit CustomerEntity objects.

Ff647281.f0bdiforwc03(en-us,PandP.10).gif

Figure B.3. Designing item templates for a DataList control

The ItemTemplate section in Figure B.3 has four Label controls, to display the ID, FirstName, LastName, and DateCreated properties for a CustomerEntity object. There are also two ImageButton controls to delete or edit the entity object.

The EditItemTemplate section in Figure B.3 has four Textbox controls, to edit the ID, FirstName, LastName, and DateCreated properties for a CustomerEntity object. There are also two ImageButton controls to save or cancel the edits.

Specifying Data Binding Expressions for a DataList Control

Before you can specify data binding expressions for a DataList control, you must bind the DataList control to a data source.

To bind the DataList control to a data source

  1. Select the DataList control that you want to perform data binding on.
  2. In the Properties window, set the DataSource property to the entity collection object that you want to bind to (for example, the "customers" collection object created previously).

This causes Visual Studio .NET to add a datasource attribute to the <asp:datalist> tag in the .aspx file.

<asp:datalist id="dlCustomers"  datasource="<%# customers %>" >
</asp:datalist>
  

After you bind the DataList control to an entity collection object, you must define data binding expressions to specify how the DataList control displays and edits entity objects in each row.

To define data binding expressions

  1. Open the ItemTemplate for the DataList control.
  2. Specify data binding expressions for each of the bindable controls in the ItemTemplate, as follows:
    1. Select a bindable control (such as the lblDateCreated control) in the template.
    2. Open the DataBindings property editor on the bindable control.
    3. Select one of the control's properties and bind it to a property on the entity object.
  3. Repeat steps 1 and 2 for the EditItemTemplate for the DataList control.

The DataBindings property editor makes it easy for you to bind to specific properties on your entity class. The entity's properties are listed in the Container.DataItem node because you are using a typed collection class.

When you define data binding expressions for controls in a DataList template, Visual Studio .NET adds code such as the following to your .aspx file.

<asp:label 
      id="lblDateCreated" 
       
      Text='<%# DataBinder.Eval(Container, 
                               "DataItem.DateCreated", 
                               "{0:dd-MM-yyyy}") %>'>
</asp:label>
  

The next section describes how and when the binding expressions are evaluated at run time.

Performing Data Binding at Run Time

This section describes how a Web Form can bind a DataList control to CustomerEntity objects held in a CustomerCollection object. The code-behind class for the Web Form has a static constructor to create the CustomerEntity objects and insert them into a static CustomerCollection object.

public class DataListBinding : System.Web.UI.Page
{
  // CustomerCollection component (as defined earlier), bound to a DataList
  protected CustomerCollection customers;

  // Static collection of CustomerEntity objects
  private static CustomerCollection CustomersList = new CustomerCollection();

  // Create CustomerEntity objects and add to static collection
  static DataListBinding()
  {
    CustomerEntity tempCustomer = new CustomerEntity();
    tempCustomer.ID = 5;
    tempCustomer.FirstName = "Guy";
    tempCustomer.LastName = "Gilbert";
    tempCustomer.DateCreated = new DateTime( 1948, 3, 9 );
    CustomersList.Add(tempCustomer);

    tempCustomer = new CustomerEntity();
    tempCustomer.ID = 11;
    tempCustomer.FirstName = "Kendall";
    tempCustomer.LastName = "Keil";
    tempCustomer.DateCreated = new DateTime( 1974, 9, 4 );
    CustomersList.Add(tempCustomer);

   // Plus other sample entities...
  }

  // Plus other members...
}
  

The form's Page_Load method performs data binding the first time the page is displayed. Note that the Page_Load method does not perform data binding on subsequent postbacks; this task is performed by the other event handler methods, described later.

public class DataListBinding : System.Web.UI.Page
{
  private void Page_Load(object sender, System.EventArgs e)
  {
    if (!IsPostBack)
    {
      // Set the "customers" instance variable to refer to the 
      // static CustomerCollection object, "CustomersList" 
      customers = CustomersList;

      // Re-evaluate all the data binding expressions on this page
      DataBind();
    }

    lblMessage.Visible = false;
  }

  // Plus other members...
}
  

The form defines event handler methods for the DeleteCommand, EditCommand, UpdateCommand, and CancelCommand events on the DataList control as follows:

  • DeleteCommand event handler method–This method removes the currently selected entity from the collection, and rebinds the collection to the DataList control.

    private void dlCustomers_DeleteCommand(
                            object source, 
                            System.Web.UI.WebControls.DataListCommandEventArgs e)
    {
      // Remove the item, and rebind
      customers = CustomersList;
      customers.RemoveAt(e.Item.ItemIndex);
      DataBind();
    }
    
    
  • EditCommand event handler method–This method sets the EditItemIndex property on the DataList control, to tell the DataList control which entity in the collection is to be edited.

    private void dlCustomers_EditCommand( 
                            object source, 
                            System.Web.UI.WebControls.DataListCommandEventArgs e)
    {
      // Set the edit index
      dlCustomers.EditItemIndex = e.Item.ItemIndex;
    
      // Rebind
      customers = CustomersList;
      DataBind();
    }
    
    
  • UpdateCommand event handler method–This method updates the currently selected entity object with the new values entered by the user while in edit mode.

    private void dlCustomers_UpdateCommand( 
                            object source, 
                            System.Web.UI.WebControls.DataListCommandEventArgs e)
    {
      // Find the TextBox controls for the current item on the DataList
      TextBox id    = e.Item.FindControl("txtId") as TextBox;
      TextBox fname = e.Item.FindControl("txtFirstName") as TextBox;
      TextBox lname = e.Item.FindControl("txtLastName") as TextBox;
      TextBox date  = e.Item.FindControl("txtDateCreated") as TextBox;
    
      // Get the appropriate entity and update values
      CustomerEntity customer = CustomersList[e.Item.ItemIndex];
    
      // Set the new values
      if (id != null && fname != null && lname != null && date != null)
      {
        try
        {
          customer.ID = Int32.Parse(id.Text);
          customer.FirstName = fname.Text;
          customer.LastName = lname.Text;
          customer.DateCreated = DateTime.Parse(date.Text);
        }
        catch (Exception ex)
        {
          lblMessage.Text = "Incorrect data: " + ex.Message;
          lblMessage.Visible = true;
        }
    
        // Switch back to view mode
        dlCustomers.EditItemIndex = -1;
    
        // Rebind
        customers = CustomersList;
        DataBind();
      }
      else
      {
        throw new Exception( "Invalid page structure" );
      }
    }
    
    
  • CancelCommand event handler method–This method sets the EditItemIndex property on the DataList control to -1, to tell the DataList control that no entity is to be edited. This causes the DataList to redraw the entity in view mode instead of edit mode.

    private void dlCustomers_CancelCommand( 
                            object source, 
                            System.Web.UI.WebControls.DataListCommandEventArgs e)
    {
      // Reset the edit index
      dlCustomers.EditItemIndex = -1;
    
      // Rebind
      customers = CustomersList;
      DataBind();
    }
    
    

These event handler methods enable the user to view, edit, and delete CustomerEntity objects by using the DataList control on the Web Form.

Data Binding a Collection of Entity Objects to a DataGrid Control

This section describes how to bind a collection of entity objects to a DataGrid control. This allows the user to view all the entity objects in a grid on the Web page, and to edit and delete entity objects.

This section includes the following topics:

  • Performing Simple Data Binding to a DataGrid Control
  • Performing Custom Data Binding to a DataGrid Control

This section uses the CustomerEntity and CustomerCollection classes introduced in the earlier sections of this chapter.

Performing Simple Data Binding to a DataGrid Control

This section describes the default data binding support provided by the DataGrid control.

To perform simple data binding to a DataGrid control

  1. Write code in your form to create the data that you want to be displayed in the DataGrid control. For example, the following code creates an ArrayList object and populates it with sample CustomerEntity objects.

    public class GridBinding : System.Web.UI.Page
    {
      // Customer list to be bound
      private ArrayList customerList = new ArrayList();
    
      // Populate the customer list
      private void SetupSources()
      {
        CustomerEntity tempCustomer = new CustomerEntity();
        tempCustomer.ID = 11;
        tempCustomer.FirstName = "Kendall";
        tempCustomer.LastName = "Keil";
        tempCustomer.DateCreated = new DateTime(2003, 10, 10);
        customerList.Add(tempCustomer);
    
        tempCustomer = new CustomerEntity();
        tempCustomer.ID = 22;
        tempCustomer.FirstName = "Jennifer";
        tempCustomer.LastName = "Riegle";
        tempCustomer.DateCreated = new DateTime(2003, 10, 10);
        customerList.Add(tempCustomer);
    
        // Plus other sample entities...
      }
    }
    
    
  2. Drag a DataGrid control from the Toolbox and drop it onto your Web Form.

  3. Write code in the Page_Load method to bind the DataGrid control to your data.

    public class GridBinding : System.Web.UI.Page
    {
      // DataGrid control
      protected System.Web.UI.WebControls.DataGrid dgMain;
    
      private void Page_Load(object sender, System.EventArgs e)
      {
        // Populate the customer list
        SetupSources();
    
        // Bind the DataGrid to the customer list
        dgMain.DataSource = customerList;
        dgMain.DataBind();
      }
    }
    
    

Figure B.4 shows how the DataGrid control appears when it is rendered in the browser.

Ff647281.f0bdiforwc04(en-us,PandP.10).gif

Figure B.4. Simple data binding for a DataGrid control

The DataGrid control has an AutogenerateColumns property, which is true by default. This causes the DataGrid control to perform default formatting for the columns when the control is rendered.

Performing Custom Data Binding to a DataGrid Control

This section describes how to perform custom formatting for a DataGrid control to improve its visual appearance when it is rendered.

To perform custom data binding to a DataGrid control

  1. In the Designer, select the DataGrid control.
  2. Right-click the DataGrid control, and then click Property Builder on the shortcut menu.
  3. In the Property Builder dialog box, customize the appearance of the DataGrid control as required. For example:
    • Click the General tab in the navigation pane, and then specify the data source, headers and footers, and sorting capabilities of the DataGrid control.
    • Click the Columns tab in the navigation pane, and then choose the properties of the entity object that you want to display in the DataGrid control. You can change the formatting string for these properties to specify how values are formatted in the columns. Alternatively, you can click the Convert this column into a Template Column link to use the templating feature described for the DataList control earlier in this chapter.
    • Click the Paging tab in the navigation pane to allow paging in the DataGrid control. You can also define the number of rows per page, and customize how the user moves between pages.
    • Click the Format tab in the navigation pane to specify fonts, colors, and other formatting characteristics for the columns, headers, and footers on the DataGrid control.
    • Click the Borders tab in the navigation pane to specify cell margins and borders on the DataGrid control.

The Property Builder dialog box also allows you to add Delete, Edit, Update, and Cancel buttons to the DataGrid control to enable the user to edit data directly in the DataGrid control.

To add Edit, Update, Cancel, and Delete buttons to the DataGrid control

  1. In the Property Builder dialog box, click the Columns tab in the navigation pane.
  2. In the Available columns list, expand Button Columns, select Edit, Update, Cancel, and click the > button. This adds the Edit, Update, Cancel button column to the Selected columns list.
  3. In the Available columns list, expand Button Columns, click Delete, and then click the > button. This adds the Delete button column to the Selected columns list.

You must then define event handler methods for the DeleteCommand, EditCommand, UpdateCommand, and CancelCommand events on the DataGrid control as follows. (These methods are similar to the corresponding DataList methods shown earlier in this chapter.)

private void grdCustomers_DeleteCommand(
                       object source, 
                       System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  customers = CustomersList;
  customers.RemoveAt(e.Item.ItemIndex);
  DataBind();    
}

private void grdCustomers_EditCommand(
                       object source, 
                       System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  customers = CustomersList;
  dgCustomers.EditItemIndex = e.Item.ItemIndex;
  DataBind();
}

private void grdCustomers_UpdateCommand(
                       object source, 
                       System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  // Find the updated controls
  TextBox id    = (TextBox) e.Item.Cells[0].Controls[0];
  TextBox fname = (TextBox) e.Item.Cells[1].Controls[0];
  TextBox lname = (TextBox) e.Item.Cells[2].Controls[0];
  TextBox date  = (TextBox) e.Item.Cells[3].Controls[0];

  // Get the appropriate entity and update values
  CustomerEntity customer = CustomersList[e.Item.ItemIndex];

  // Set the new values
  try
  {
    customer.ID = Int32.Parse(id.Text);
    customer.FirstName = fname.Text;
    customer.LastName = lname.Text;
    customer.DateCreated = DateTime.Parse(date.Text);
  }
  catch (Exception ex)
  {
    lblMessage.Text = "Incorrect data: " + ex.Message;
    lblMessage.Visible = true;
  }

  // Switch back to view mode
  dgCustomers.EditItemIndex = -1;

  // Rebind
  customers = CustomersList;
  DataBind();
}

private void grdCustomers_CancelCommand(
                       object source, 
                       System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  dgCustomers.EditItemIndex = -1;
  customers = CustomersList;
  DataBind();    
}
  

The DataGrid control now allows the user to edit the properties in an entity object, as shown in Figure B.5.

Ff647281.f0bdiforwc05(en-us,PandP.10).gif

Figure B.5. Custom data binding for a DataGrid control

When the user clicks the Edit link on a row in the DataGrid control, text boxes appear in each column to enable the user to edit the values; the user can click Update or Cancel to save or cancel these changes to the underlying entity object. Alternatively, the user can click Delete to delete an entity object.

How To: Design Data Maintenance Forms to Support Create, Read, Update, and Delete Operations

This section describes how to design and implement Web Forms to support Create, Read, Update, and Delete ("CRUD") operations on data in a data store.

This section includes the following topics:

  • Defining Business Entities
  • Defining Data Access Logic Components
  • Defining Business Components
  • Designing CRUD Web Forms

The examples in this section are based on three business entities: Customer, Order, and OrderItem. Figure B.6 shows the relationships between these business entities.

Ff647281.f0bdiforwc06(en-us,PandP.10).gif

Figure B.6. Relationships between business entities

As shown in Figure B.6, each customer can have many orders, and each order can have many order items.

Defining Business Entities

There are several ways to represent business entities as they are passed internally between the components and layers in a distributed application, including:

  • Data set
  • Typed data set
  • Data reader
  • XML
  • Custom "business entity" objects

This example uses typed data sets because they are straightforward to create and maintain. Additionally, typed data sets can be easily bound to DataGrids and other controls on a Web Form.

You can either define a single data set that encompasses all the business entities or create a separate typed data set for each business entity. The latter approach is preferred because it is easier to add new typed data sets if the business model grows in the future.

To define a typed data set for a business entity

  1. Open a Visual Studio .NET project.
  2. In Solution Explorer, right-click the project. On the shortcut menu, point to Add, and then click Add New Item.
  3. In the Add New Item dialog box, select the Data Set template. Enter a name for the new data set, such as CustomerDS.xsd, and then click OK.
  4. Open Server Explorer in Visual Studio .NET, and then locate the database table that you want to create a data set for.
  5. Drag the database table onto the Designer surface. Visual Studio .NET creates an XML schema to represent the columns in the database table.
  6. Right-click the Designer surface, and make sure there is a check mark next to the Generate Dataset menu item.
  7. Save the new file. Visual Studio .NET creates a typed data set class (for example, CustomersDS.cs), based on the information in the XML schema.

To view the typed data set class, click the Show all Files icon in the toolbar in Solution Explorer and expand the XML schema file (for example, CustomerDS.xsd). This reveals the typed data set class file (for example, CustomerDS.cs). The class has properties and methods to provide named and type-safe access to the fields in the business entity.

Defining Data Access Logic Components

Data access logic components encapsulate access to the underlying data in the data store. Data access logic components typically have methods to perform the following tasks:

  • Create a new entity in the data store
  • Update an existing entity in the data store
  • Delete an existing entity in the data store
  • Get a single entity in the data store
  • Get all entities in the data store

For simplicity and maintainability, it is a good idea to define a separate data access logic component for each business entity. You can define a common base class to hold any behavior or properties that are the same across the data access logic components. For example, the BaseDalc class shown in the following code sample defines common features for the CustomerDalc, OrderDalc, and OrderItemDalc subclasses. BaseDalc retrieves the "ConnectionString" setting from the application configuration file (Web.config), and stores this setting in an instance variable named connectionStr. This setting contains the connection string that all data access logic components use when they want to access the database.

using System;
using System.Configuration;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;

namespace CRUDSample.DataAccessComponent
{
  // Base class for all Data Access Logic Components
  [ComVisible(false)]
  public class BaseDalc
  {
    // Database connection string
    protected string connectionStr;

    // Retrieve the resource file corresponding to this assembly
    protected static ResourceManager ResMgr = 
         new ResourceManager(typeof(BaseDalc).Namespace + ".DalcMessages", 
                             Assembly.GetExecutingAssembly()); 
    
    // Default constructor
    public BaseDalc()
    {
      connectionStr = ConfigurationSettings.AppSettings["ConnectionString"];
      if (connectionStr == null)
      {
        throw new ConfigurationException(
          ResMgr.GetString("ConfigurationException.ConnectionStringNotFound"));
      }
    }

    // Get the resource file corresponding to this assembly
    protected ResourceManager ResourceMgr
    {
      get { return ResMgr; }
    }  
  }
}
  

To implement the data access logic component for a particular business entity, follow these guidelines:

  • It is a good idea to write stored procedures to perform CRUD operations for a particular business entity in the database. Stored procedures give you an opportunity to centralize data access logic in the database and to achieve reuse.
  • Devise a consistent naming convention for your CRUD stored procedures. For example, the CRUD stored procedures for the "customer" business entity might be named Customer_Insert, Customer_Update, Customer_Delete, Customer_SelectByID, and Customer_SelectAll.
  • Evaluate using the Data Access Application Block for optimized data access code that helps you call stored procedures and issue SQL text commands against a SQL server database. The Data Access Application Block is available on MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/daab-rm.asp).

The following code shows a sample implementation for the CustomerDalc data access logic component. CustomerDalc inherits from BaseDalc, and provides CRUD methods relating to the "customer" business entity; these methods make use of the Data Access Application Block for efficiency.

using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.ApplicationBlocks.Data;
using System.Runtime.InteropServices;

using CRUDSample.BusinessEntity;

namespace CRUDSample.DataAccessComponent
{
  [ComVisible(false)]
  public class CustomerDalc : BaseDalc
  {
    // Default constructor
    public CustomerDalc() : base()
    {}
    
    // Create a new customer record in the data store
    public int Create(string companyName, string contactName, 
                      string address, string city,  string region,
                      string postalCode, string country, string email)
    {
      try
      {
        // Execute the "insert" stored procedure
        int customerId = (int)SqlHelper.ExecuteScalar(this.connectionStr, 
                                                     "Customer_Insert", 
                                                      companyName, contactName, 
                                                      address, city, region, 
                                                      postalCode, country, email);
        // Return the customer id
        return customerId;
      }
      catch (SqlException e)
      {
        throw new TechnicalException(
           this.ResourceMgr.GetString("TechnicalException.CantCreateCustomer", 
           System.Globalization.CultureInfo.CurrentUICulture), e);
      }
    }

    // Update an existing customer record in the data store
    public void Update(int customerId, string companyName, string contactName, 
                       string address, string city, string region, 
                       string postalCode, string country, string email)
    {
      try
      {
        // Execute the "update" stored procedure
        SqlHelper.ExecuteNonQuery(this.connectionStr, 
                                 "Customer_Update", customerId, 
                                  companyName, contactName, 
                                  address, city, region, 
                                  postalCode, country, email);
      }
      catch (SqlException e)
      {
        throw new TechnicalException(
           this.ResourceMgr.GetString("TechnicalException.CantUpdateCustomer", 
           System.Globalization.CultureInfo.CurrentUICulture), e);
      }
    }

    // Delete an existing customer record in the data store
    public void Delete(int customerId)
    {
      try
      {
        // Execute the "delete" stored procedure
        SqlHelper.ExecuteNonQuery(this.connectionStr, 
                                 "Customer_Delete", 
                                  customerId); 
      }
      catch (SqlException e)
      {
        throw new TechnicalException(
           this.ResourceMgr.GetString("TechnicalException.CantDeleteCustomer", 
           System.Globalization.CultureInfo.CurrentUICulture), e);
      }
    }

    // Return the customer with the specified ID    
    public CustomerDs.Customer GetById(int customerId)
    {
      try
      {
        CustomerDs customer = new CustomerDs();
       
        // Execute the "select single entity" stored procedure
        using (SqlDataReader reader = SqlHelper.ExecuteReader(
                                                     this.connectionStr, 
                                                    "Customer_SelectByID", 
                                                     customerId))
        {
          // Populate the dataset with reader rows
          SQLHelperExtension.Fill(reader, 
                                  customer, 
                                  customer.Customers.TableName, 
                                  0, 1); 
        }
      
        // Return the first row
        return customer.Customers[0];
      }
      catch (SqlException e)
      {
        throw new TechnicalException(
           this.ResourceMgr.GetString("TechnicalException.CantGetCustomer", 
           System.Globalization.CultureInfo.CurrentUICulture), e);
      }
    }

    // Return all customers, as a typed data set object
    public CustomerDs GetAll()
    {
      try
      {
        CustomerDs customer = new CustomerDs();
      
        // Execute the "select all entities" stored procedure
        using (SqlDataReader reader = SqlHelper.ExecuteReader(
                                                     this.connectionStr, 
                                                    "Customer_SelectAll"))
        {
          SQLHelperExtension.Fill(reader, 
                                  customer, 
                                  customer.Customers.TableName, 
                                  0, 0); 
        }
      
        // Return the customer data set
        return customer;
      }
      catch (SqlException e)
      {
        throw new TechnicalException(
           this.ResourceMgr.GetString("TechnicalException.CantGetAllCustomers", 
           System.Globalization.CultureInfo.CurrentUICulture), e);
      }
    }
  }
}
  

The OrderDalc and OrderItemDalc data access logic components follow a similar pattern to the CustomerDalc data access logic component; OrderDalc provides CRUD methods for "order" business entities, and OrderItemDalc provides CRUD methods for "order item" business entities.

Defining Business Components

The general purpose of business components is to encapsulate business rules in a distributed application. Business components shield other parts of the application, such as the presentation layer, from direct interactions with data access logic components.

In CRUD applications, there are usually few business rules that need to be encapsulated by the business components. Therefore, the methods in a business component typically act as simple wrappers for the underlying methods in the data access logic component.

The following code shows a sample implementation for the CustomerBc business component. Each method in CustomerBc calls the corresponding method in the CustomerDalc data access logic component.

using System;
using System.Runtime.InteropServices;
using CRUDSample.BusinessEntity;
using CRUDSample.DataAccessComponent;
 
namespace CRUDSample.BusinessComponent
{
  [ComVisible(false)]
  public class CustomerBc : BaseBc
  {
    // Default constructor
    public CustomerBc() : base()
    {}

    // Create a new customer
    public int Create(string companyName, string contactName, 
                      string address, string city,  string region,
                      string postalCode, string country, string email)
    {
      // Create the DALC component
      CustomerDalc customerDalc = new CustomerDalc();
      
      // Create a new customer using the DALC component
      int customerId = customerDalc.Create(companyName, contactName, 
                                           address, city, region, 
                                           postalCode, country, email);
      // Return the customer id
      return customerId;
    }

    // Update an existing customer
    public void Update(int customerId, string companyName, string contactName, 
                       string address, string city, string region, 
                       string postalCode, string country, string email)
    {
      // Create the DALC component
      CustomerDalc customerDalc = new CustomerDalc();

      // Update an existing customer using the DALC component
      customerDalc.Update(customerId, companyName, contactName, address, 
                          city, region, postalCode, country, email);
    }

    // Delete an existing customer
    public void Delete(int customerId)
    {
      // Create the DALC component
      CustomerDalc customerDalc = new CustomerDalc();
      
      // Delete an existing customer using the DALC component
      customerDalc.Delete(customerId);
    }

    // Return the customer with the specified ID
    public CustomerDs.Customer GetById(int customerId)
    {
      // Create the DALC component
      CustomerDalc customerDalc = new CustomerDalc();
      
      // Get the specified customer using the DALC component
      return customerDalc.GetById(customerId); 
    }

    // Return all customers, as a typed dataset object
    public CustomerDs GetAll()
    {
      // Create the DALC component
      CustomerDalc customerDalc = new CustomerDalc();
      
      // Get all customers using the DALC component
      return customerDalc.GetAll();
    }
  }
}
  

The OrderBc and OrderItemBc business components follow a similar pattern to the CustomerBc business component.

Designing CRUD Web Forms

This section describes how to design CRUD Web Forms to enable users to create, read, update, and delete customer records, order records, and order item records in the data store. There are six Web Forms in this sample scenario:

  • CustomerList.aspx–Displays a list of customer records in a DataGrid control and allows the user to delete a customer record.
  • EditCustomer.aspx–Enables the user to create a new customer record and edit an existing customer record.
  • OrderList.aspx–Displays a list of a customer's order records in a DataGrid control and allows the user to delete an order record.
  • EditOrder.aspx–Enables the user to create a new order record and edit an existing order record.
  • OrderItemList.aspx–Displays a list of order item records in a DataGrid control and allows the user to delete an order item record.
  • EditOrderItem.aspx–Enables the user to create a new order item record and edit an existing order item record.

The following sections describe each of these Web pages, focusing on the CRUD operations performed by each Web page.

Defining the CustomerList.aspx Web Page

The CustomerList.aspx Web page displays customer records in a DataGrid control, as shown in Figure B.7.

Ff647281.f0bdiforwc07(en-us,PandP.10).gif

Figure B.7. Customer list Web page

Notice the following features in CustomerList.aspx:

  • The Web page contains a DataGrid control that displays a page of customers, one customer per row. The arrows at the bottom of the DataGrid enable the user to page backward and forward through customer records in the data store; this is an important feature in CRUD forms, where there might be many records to display.
  • The DataGrid shows a subset of information for each customer, instead of displaying all the fields. It is a good idea to display a subset of information in CRUD forms, because there may be too many fields to display neatly in a single DataGrid.
  • The first column in the DataGrid contains a hyperlink to a details Web page (EditCustomer.aspx), where the user can display and edit full details for an individual customer.
  • Each row in the DataGrid has a "Delete" hyperlink, to delete an existing customer in the data store. Each row also has a "View orders" hyperlink, which redirects the user to another Web page (OrderList.aspx) to display the orders for this customer.
  • The "New" hyperlink at the bottom of CustomerList.aspx redirects the user to the details Web page (EditCustomer.aspx), where the user can enter details for a new customer.

To implement these features in the CustomerList.aspx Web page

  1. In Visual Studio .NET, drag a DataGrid control from the Toolbox onto the Web page.
  2. Right-click the DataGrid control and select Property Builder from the shortcut menu.
  3. In the Property Builder dialog box, click the Columns tab in the navigation pane.
  4. Design the first (hyperlink) column as follows:
    • In the Available columns list, select HyperLink Column.
    • Click the > button to add the HyperLink Column to the Selected columns list.
    • In the Text field text box, enter "CompanyName" so that the hyperlink text displays the company name.
    • In the URL field text box, enter "CustomerID" so that the CustomerID property is used to generate the URL of the hyperlink.
    • In the URL format string text box, enter "EditCustomer.aspx?CustomerId={0}" so that the hyperlink redirects the user to EditCustomer.aspx to edit the current customer.
  5. Design the "Company" column as follows:
    • In the Available columns list, select Bound Column.
    • Click the > button to add the Bound Column to the Selected columns list.
    • In the Header text text box, enter "Company" as the header text for this column.
    • In the Data Field text box, enter "CompanyName" so that the column is bound to the CompanyName property on the customer record.
  6. Repeat step 5 for the "Contact," "Email," and "Address" columns. Bind the columns to the ContactName, Email, and Address properties on the customer record.
  7. Design the "Delete" column as follows:
    • In the Available columns list, select Template Column.

    • Click the > button to add the Template Column to the Selected columns list.

      Note   After you finish using the Property Builder, you must add a "Delete" link button control into this template column to perform the "delete" command. To add a "Delete" link button control, edit the template for the DataGrid control; in the ItemTemplate section for this column, drag a LinkButton control from the Toolbox, and set its CommandName property to Delete.

  8. Design the "View orders" column as follows:
    • In the Available columns list, select HyperLink Column.
    • Click the > button to add the HyperLink Column to the Selected columns list.
    • In the Text text box, enter "View orders" as the fixed text for the hyperlink.
    • In the URL field text box, enter "CustomerID" so that the CustomerID property is used to generate the URL of the hyperlink.
    • In the URL format string text box, enter "OrderList.aspx?CustomerId={0}" so that the hyperlink redirects the user to OrderList.aspx to display the orders for the current customer.
  9. Specify paging behavior for the DataGrid control, as follows:
    • Click the Paging tab in the navigation pane of the Property Builder dialog box.
    • Place a check mark in the Allow paging check box.
    • Enter a suitable number in the Page size text box (for example, 10).
    • Place a check mark in the Show navigation buttons check box, and configure the page navigation details as you require.

The CustomerList.aspx Web page also has a "New" hyperlink to enable the user to create new customer records in the data store.

To add this hyperlink to the Web page

  1. View the HTML for the Web page.

  2. Add an <a> HTML element as follows, to define a hyperlink to the EditCustomer.aspx Web page.

    <a href="EditCustomer.aspx">New</a>
    
    

    Note that EditCustomer.aspx is the same Web page that is used to edit existing customer records, but the absence of a customer ID in the URL indicates that a new customer record is to be created.

The next task is to write code in the code-behind class for CustomerList.aspx.

To implement the functionality of the Web page

  1. Add code to the Page_Load method to bind the DataGrid control to the customer records in the data store. The following code uses a helper method named LoadCustomers, because the task of loading customers will be needed elsewhere in the Web page. The LoadCustomers method creates a CustomerBc business component and calls the GetAll method to return all the customer records as a typed data set. The DataGrid control is then bound to the Customers table in the typed data set.

    using CRUDSample.BusinessComponent; 
    using CRUDSample.BusinessEntity;
    
    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class CustomerList : System.Web.UI.Page
      {
        // DataGrid to display a list of customers
        protected System.Web.UI.WebControls.DataGrid dgCustomers;
    
        private void Page_Load(object sender, System.EventArgs e)
        {
          if (!Page.IsPostBack)
          {
            LoadCustomers();
          }
        }
    
        private void LoadCustomers()
        {
          // Create a "customer" business component
          CustomerBc customerBc = new CustomerBc();
    
          // Get all customers
          CustomerDs customer = customerBc.GetAll();
    
          // Bind the DataGrid control to the "Customers" table in the dataset
          dgCustomers.DataSource = customer.Customers;
          dgCustomers.DataBind();
        }
        // Plus other members...
      }
    }
    
    
  2. Define an event handler for the ItemCreated event on the DataGrid. The event handler method adds client-side script to the Delete button for the new item. The client-side script displays an "Are you sure?" confirmation message when the user tries to delete the item to give the user an opportunity to cancel the deletion.

    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class CustomerList : System.Web.UI.Page
      {
        private void customersGrid_ItemCreated(object sender, 
                                               DataGridItemEventArgs e)
        {
          if (e.Item.ItemType == ListItemType.Item || 
              e.Item.ItemType == ListItemType.AlternatingItem)
          {
            // Add an "Are you sure?" confirmation dialog box to the Delete button
            LinkButton deleteButton = 
                              e.Item.FindControl("lnkDelete") as LinkButton;
            if (deleteButton != null)
              deleteButton.Attributes.Add(
                               "onclick", 
                               "return window.confirm('Are you sure ?');");
          }
        }
        // Plus other members...
      }
    }
    
    
  3. Define an event handler to delete the selected customer record from the data store.

    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class CustomerList : System.Web.UI.Page
      {
        // Handle "item" events from the DataGrid control
        private void customersGrid_ItemCommand(object source, 
                                               DataGridCommandEventArgs e)
        {
          // Is it the "Delete" command?
          if (e.CommandName == "Delete")
          {
            // Get the CustomerID of the selected customer
            int customerId = (int)dgCustomers.DataKeys[e.Item.ItemIndex];
    
            // Delete the selected customer
            CustomerBc customerBc = new CustomerBc();
            customerBc.Delete (customerId);
    
            // Update the page index
            if (dgCustomers.Items.Count == 1 && dgCustomers.CurrentPageIndex > 0)
            {
              dgCustomers.CurrentPageIndex -= 1;
            }
    
            // Rebind
            LoadCustomers();
          }
        }
        // Plus other members...
      }
    } 
    
    
  4. Define an event handler method for the PageIndexChanged event on the DataGrid control. Implement the event handler method as follows to support paging behavior in the DataGrid control.

    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class CustomerList : System.Web.UI.Page
      {
        private void customersGrid_PageIndexChanged(
                                          object source, 
                                          DataGridPageChangedEventArgs e)
        {
          // Adjust the current page index
          dgCustomers.CurrentPageIndex = e.NewPageIndex;
    
          // Rebind
          LoadCustomers();
        }
        // Plus other members...
      }
    }
    
    

The preceding code samples show how to display a collection of "customer" business entities in a DataGrid control, and how to incorporate hyperlinks that enable the user to create new customers, edit existing customers, and delete existing customers in the data store.

Defining the EditCustomer.aspx Web Page

The EditCustomer.aspx Web page allows the user to edit details for an existing customer record and to enter details for a new customer record. EditCustomer.aspx is shown in Figure B.8, with some sample data entered by the user.

Ff647281.f0bdiforwc08(en-us,PandP.10).gif

Figure B.8. Edit-customer Web page

Notice the following features in EditCustomer.aspx:

  • The Web page contains a series of text boxes, one for each field in the "customer" business entity.
  • If the user invokes the Web page to edit an existing customer record, the text fields are populated with the customer's current details. However, if the user invokes the Web page to create a new customer record, the text fields are blank initially.
  • When the user clicks the Ok button, the Web page either updates an existing record or creates a new record, depending on whether the user is editing an existing customer or creating a new customer. The user is then redirected back to the CustomerList.aspx Web page to view the customer list.
  • When the user clicks the Cancel button, the user is immediately redirected back to CustomerList.aspx without any modifications to the data store.

To design this Web page, add Label, TextBox, and Button controls to the form as shown in Figure B.8. There are no special user interface issues that you must take into account for this Web page.

To implement the functionality for the EditCustomer.aspx Web page

  1. Add code to the Page_Load method to populate the text boxes on the form if the user is editing an existing customer record; in this scenario, the query string contains a CustomerId parameter to identify the current customer. The customer ID must be stored in the ViewState, so that it is available in subsequent postbacks.

    using CRUDSample.BusinessComponent;
    using CRUDSample.BusinessEntity;   
    
    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class EditCustomer : System.Web.UI.Page
      {
        private void Page_Load(object sender, System.EventArgs e)
        {
          if (!Page.IsPostBack) 
          {
            // Is the user editing an existing customer record?
            if (Request.QueryString["CustomerId"] != null)
            {
              // Add customerID to ViewState, so that it is available in postbacks
              ViewState.Add("CustomerId", 
                             int.Parse(Request.QueryString["CustomerId"]));
    
              // Create a "customer" business component
              CustomerBc customerBc = new CustomerBc();
    
              // Get the specified customer's details from the data store
              CustomerDs.Customer customer = 
                             customerBc.GetById((int)ViewState["CustomerId"]);
    
              // Populate the form controls with customer details
              txtCompany.Text = customer.CompanyName;
              txtContact.Text = customer.ContactName;
              txtAddress.Text = customer.Address;
              txtCity.Text = customer.City;
              txtRegion.Text = customer.Region;
              txtPostal.Text = customer.PostalCode;
              txtCountry.Text = customer.Country;
              txtEmail.Text = customer.Email;
            }
          }
        }
        // Plus other members...
      }
    }
    
    
  2. Define an event handler method for the OK button-click event. If the ViewState contains a CustomerId value, update the current customer record in the data store; otherwise, create a new customer record in the data store. Finally, redirect the user back to the CustomerList.aspx Web page.

    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class EditCustomer : System.Web.UI.Page
      {
        private void btnOk_Click(object sender, System.EventArgs e)
        {
          if (!Page.IsValid)
            return;
    
          // Create a "customer" business component
          CustomerBc customerBc = new CustomerBc();
    
          // If the ViewState contains a CustomerID value, we are in "edit mode"
          if (ViewState["CustomerId"] != null) 
          {
            // Update the specified customer record in the data store
            customerBc.Update( (int)ViewState["CustomerId"], 
                                txtCompany.Text, txtContact.Text, 
                                txtAddress.Text, txtCity.Text, txtRegion.Text, 
                                txtPostal.Text, txtCountry.Text, txtEmail.Text );
          }
          else
          {
            // Create a new customer record in the data store
            customerBc.Create (txtCompany.Text, txtContact.Text, 
                               txtAddress.Text, txtCity.Text, txtRegion.Text, 
                               txtPostal.Text, txtCountry.Text, txtEmail.Text );
          }
    
          // Redirect the user back the "customer list" Web page
          Response.Redirect("CustomerList.aspx");
        }
        // Plus other members...
      }
    }
    
    
  3. Define an event handler method for the Cancel button-click event to redirect the user back to the CustomerList.aspx Web page.

    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class EditCustomer : System.Web.UI.Page
      {
        private void btnCancel_Click(object sender, System.EventArgs e)
        {
          Response.Redirect("CustomerList.aspx");
        }
        // Plus other members...
      }
    }
    
    

The preceding code samples show how to edit an existing customer record in the data store, and how to create a new customer record in the data store.

Defining the OrderList.aspx Web Page

The OrderList.aspx Web page displays orders for a particular customer. This Web page is displayed when the user clicks the "View orders" hyperlink on the CustomerList.aspx Web page; the customer's ID is appended to the query string for OrderList.aspx to identify the current customer.

OrderList.aspx displays the order records in a DataGrid control, as shown in Figure B.9.

Click here for larger image

Figure B.9. Order list Web page

There are many similarities between the OrderList.aspx Web page and the CustomerList.aspx Web page introduced earlier:

  • OrderList.aspx contains a DataGrid control that displays a page of orders, one order per row. The DataGrid supports paging, in case there are many orders to display.
  • The first column in the DataGrid contains a hyperlink to a details Web page (EditOrder.aspx), where the user can display and edit full details for a particular order.
  • Each row in the DataGrid has a "Delete" hyperlink to delete an existing order in the data store. Each row also has a "Browse items" hyperlink, which redirects the user to another Web page (OrderItemList.aspx) to display the order items in a particular order.
  • The "New" hyperlink at the bottom of OrderList.aspx redirects the user to the details Web page (EditOrder.aspx), where the user can enter details for a new order.
  • The "Back to customers" hyperlink at the bottom of OrderList.aspx redirects the user back to the CustomerList.aspx Web page, to redisplay the list of customers.

To design the visual interface for the OrderList.aspx Web page, follow the general guidelines presented earlier in this chapter for the CustomerList.aspx Web page. Note the following points:

  • The "Id" column is a hyperlink column in the DataGrid property builder. The URLfield is OrderID, and the URL format string is EditOrder.aspx?OrderId={0}.
  • The "Date," "Shipped," and "Freight" columns are bound columns in the DataGrid property builder. These columns are bound to the OrderDate, ShippedDate, and Freight properties on the order record.
  • The "Delete" column is a template column in the DataGrid property builder. This template column contains a LinkButton control, whose CommandName property is DeleteCommand.
  • The "Browse items" column is a hyperlink column in the DataGrid property builder. The URLfield is OrderID, and the URL format string is OrderItemList.aspx?OrderId={0}.
  • The "New" hyperlink beneath the DataGrid control is a HyperLink control. The href property is EditOrder.aspx?CustomerId={0}, to create a new order for the current customer.
  • The "Back to customers" hyperlink beneath the DataGrid control is a HyperLink control. The href property is CustomerList.aspx, to redirect the user back to the "customer list" page.

To implement the functionality for the OrderList.aspx Web page

  1. Add code to the Page_Load method, to bind the DataGrid control to the order records for the current customer. Retrieve the customer's ID from the query string, and store it in the ViewState so that it is available in subsequent postbacks. In the following code sample, the task of loading orders and binding them to the DataGrid control is performed in a helper method named LoadOrders.

    using CRUDSample.BusinessEntity;
    using CRUDSample.BusinessComponent;  
    
    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class OrderList : System.Web.UI.Page
      {
        private void Page_Load(object sender, System.EventArgs e)
        {
          if (!Page.IsPostBack)
          {
            // Add customerID to the ViewState, so it is available in postbacks
            ViewState.Add("CustomerId", 
                           int.Parse(Request.QueryString["CustomerId"]));
    
            // Rebind the DataGrid control 
            LoadOrders();
    
            // Append the customerID to the "New" hyperlink, 
            // to identify the current customer in the hyperlink
            lnkNew.href = 
              String.Format(System.Globalization.CultureInfo.CurrentUICulture, 
                            lnkNew.href, 
                            ViewState["CustomerId"]);
          }
        }
    
        private void LoadOrders()
        {
          // Create an "order" business component
          OrderBc orderBc = new OrderBc();
    
          // Get all orders for the current customer
          int customerId = (int)ViewState["CustomerId"];
          OrderDs order = orderBc.GetByCustomer(customerId);
    
          // Bind the DataGrid control to the data set
          dgOrders.DataSource = order.Orders;
          dgOrders.DataBind();
        }
        // Plus other members...
      }
    }
    
    
  2. Define an event handler for the "item created" event on the DataGrid. The event handler method adds client-side script to the Delete button for the new item. The client-side script displays an "Are you sure?" confirmation message when the user tries to delete the item to give the user an opportunity to cancel the deletion.

    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class OrderList : System.Web.UI.Page
      {
        private void ordersGrid_ItemCreated(object sender, 
                                            DataGridItemEventArgs e)
        {
          if (e.Item.ItemType == ListItemType.Item || 
              e.Item.ItemType == ListItemType.AlternatingItem)
          {
            // Add an "Are you sure?" confirmation dialog box to the Delete button
            LinkButton deleteButton = 
                              e.Item.FindControl("lnkDelete") as LinkButton;
    
            if (deleteButton != null)
              deleteButton.Attributes.Add(
                               "onclick", 
                               "return window.confirm('Are you sure ?');");
          }
        }
        // Plus other members...
      }
    }
    
    
  3. Define an event handler method for to delete the selected order record from the data store.

    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class OrderList : System.Web.UI.Page
      {
        // Handle "item" events from the DataGrid control
        private void ordersGrid_ItemCommand(object source, 
                                            DataGridCommandEventArgs e)
        {
          // Is it the "Delete" command?
          if (e.CommandName == "Delete")
          {
            // Get the OrderID of the selected order
            int orderId = (int)dgOrders.DataKeys[e.Item.ItemIndex];
    
            // Delete the selected order
            OrderBc orderBc = new OrderBc();
            orderBc.Delete(orderId);
    
            // Update the page index
            if (dgOrders.Items.Count == 1 && dgOrders.CurrentPageIndex > 0)
            {
              dgOrders.CurrentPageIndex -= 1;
            }
    
            // Rebind
            LoadOrders();
          }
        }
        // Plus other members...
      }
    }
    
    
  4. Define an event handler method for the PageIndexChanged event on the DataGrid control. Implement the event handler method as follows, to support paging behavior in the DataGrid control.

    namespace CRUDSample.WebUI
    {
      [ComVisible(false)]
      public class OrderList : System.Web.UI.Page
      {
        private void ordersGrid_PageIndexChanged(object source, 
                                                 DataGridPageChangedEventArgs e)
        {
          // Adjust the current page index
          dgOrders.CurrentPageIndex = e.NewPageIndex;
    
          // Rebind
          LoadOrders();
        }
        // Plus other members...
      }
    }
    
    

The preceding code samples show how to display a collection of "order" business entities in a DataGrid control, and how to incorporate hyperlinks that enable the user to create new orders, edit existing orders, and delete existing orders.

Defining the EditOrder.aspx Web Page

The EditOrder.aspx Web page allows the user to edit details for an existing order record, and to enter details for a new order record. EditOrder.aspx is shown in Figure B.10 with some sample data entered by the user.

Click here for larger image

Figure B.10. Edit-order Web page

The EditOrder.aspx Web page is visually and functionally equivalent to the EditCustomer.aspx Web page, except that it works with "order" records instead of "customer" records. For a description of the mechanisms and techniques used to implement both of these Web pages, see "Defining the EditCustomer.aspx Web Page" earlier in this chapter.

The code-behind class for EditOrder.aspx is shown in the following code sample. This code follows a similar pattern to the code-behind class for EditCustomer.aspx.

using CRUDSample.BusinessComponent; 
using CRUDSample.BusinessEntity; 

namespace CRUDSample.WebUI
{
  [ComVisible(false)]
  public class EditOrder : System.Web.UI.Page
  {
    private void Page_Load(object sender, System.EventArgs e)
    {
      if (!Page.IsPostBack) 
      {
        // Is the user editing an existing order record?
        if (Request.QueryString["OrderId"] != null)
        {
          // Add orderID to the ViewState, so that it is available in postbacks
          ViewState.Add("OrderId", int.Parse(Request.QueryString["OrderId"]));

          // Create an "order" business component
          OrderBc orderBc = new OrderBc();
        
          // Get the specified order's details from the data store
          OrderDs.Order order = orderBc.GetById((int)ViewState["OrderId"]);

          // Populate the form controls with order details
          txtDate.Text    = order.OrderDate.ToString("yyyy/MM/dd");
          txtShipped.Text = order.ShippedDate.ToString("yyyy/MM/dd");
          txtFreight.Text = order.Freight.ToString();

          // Also add the customerID to the ViewState
          ViewState.Add("CustomerId", order.CustomerID);
        }
        else
        {
          // This is a new order, so get the customer's ID from the Query String
          // and add it to the ViewState
          ViewState.Add("CustomerId",  
                         int.Parse(Request.QueryString["CustomerId"]));
        }
      }
    }

    private void btnOk_Click(object sender, System.EventArgs e)
    {
      // Create an "order" business component
      OrderBc orderBc = new OrderBc();

      // If the ViewState contains an OrderID value, we are in "edit" mode
      if (ViewState["OrderId"] != null)
      {
        int orderId = (int)ViewState["OrderId"];

        // Update the specified order record in the data store
        orderBc.Update( orderId, 
                       (int)ViewState["CustomerId"],
                        DateTime.Parse(txtDate.Text), 
                        DateTime.Parse(txtShipped.Text), 
                        decimal.Parse(txtFreight.Text) );
      }
      else
      {
        // Create a new order record in the data store
        orderBc.Create( (int)ViewState["CustomerId"],
                         DateTime.Parse(txtDate.Text), 
                         DateTime.Parse(txtShipped.Text), 
                         decimal.Parse(txtFreight.Text) );
      }

      // Redirect the user back to the "order list" Web page for this customer
      Response.Redirect("OrderList.aspx?CustomerId=" + 
                           ViewState["CustomerId"].ToString());
    }

    private void btnCancel_Click(object sender, System.EventArgs e)
    {
      Response.Redirect("OrderList.aspx?CustomerId=" + 
                           ViewState["CustomerId"].ToString());
    }
  }
}
 
  

The preceding code samples show how to edit an existing order record in the data store, and how to create a new order record in the data store.

Defining the OrderItemList.aspx Web Page

The OrderItemList.aspx Web page displays the order items that comprise a particular order. This Web page is displayed when the user clicks the "Browse items" hyperlink on the OrderList.aspx Web page; the order ID is appended to the query string for OrderItemList.aspx to identify the order.

OrderItemList.aspx displays the order item records in a DataGrid control, as shown in Figure B.11.

Ff647281.f0bdiforwc11(en-us,PandP.10).gif

Figure B.11. Order-item list Web page

The OrderItemList.aspx Web page is visually and functionally equivalent to the OrderList.aspx Web page, except that it works with "order item" records instead of "order" records. For a description of the mechanisms and techniques used to implement both of these Web pages, see "Defining the OrderList.aspx Web Page" earlier in this chapter.

Defining the EditOrderItem.aspx Web Page

The EditOrderItem.aspx Web page allows the user to edit details for an existing order item record and to enter details for a new order item record. EditOrderItem.aspx is shown in Figure B.12, with some sample data entered by the user.

Click here for larger image

Figure B.12. Edit order-item Web page

The EditOrderItem.aspx Web page is visually and functionally equivalent to the EditOrder.aspx Web page, except that it works with "order item" records instead of "order" records. For a description of the mechanisms and techniques used to implement both of these Web pages, see "Defining the EditOrder.aspx Web Page" earlier in this chapter.

How To: Execute a Long-Running Task in a Web Application

This example shows how to create threads to perform long-running tasks in an ASP.NET Web application.

In this example, the user enters credit card details in a "payment" Web page (Payment.aspx). When the user submits the details, the payment page creates a worker thread to authorize the credit card details as a background task. In the meantime, the user is redirected to a "result" Web page (Result.aspx). The result page continually refreshes itself until the worker thread has completed the credit card authorization task.

There are four classes in this example:

  • Payment–This is the code-behind class for the payment Web page.
  • CCAuthorizationService–This is a service agent class that sends credit card transactions to a credit card authority.
  • ThreadResults–This is a helper class that stores the credit card authorization results from the worker threads.
  • Result–This is the code-behind class for the result Web page.

The following sections describe these classes.

Note   For simplicity in this example, the application does not validate the details entered by the user.

Defining the Payment Class

The Payment class is the code-behind class for the Payment.aspx Web page. This Web page asks the user to enter payment details, as shown in Figure B.13.

Click here for larger image

Figure B.13. Credit card payment page

The Payment class has three important members:

  • RequestId field–This is a globally unique identifier (GUID) that identifies each credit card authorization task. Every time the user submits credit card details to the payment Web page, a new GUID will be generated.
  • Page_Load method–This method is called when the payment Web page is first displayed, and on each subsequent postback. This method performs the following tasks:
    • When the payment Web page is first displayed, Page_Load initializes the controls on the page.
    • On subsequent postbacks, Page_Load creates a new request ID, and then calls the AuthorizePayment method in a worker thread. In the meantime, the user is redirected to the result Web page, Result.aspx. The request ID is appended to the query string for Result.aspx to identify the worker thread that the result Web page is waiting for.
  • AuthorizePayment method–This method synchronously calls the Authorize method in the CCAuthorizationService class to authorize the credit card details. When the authorization is complete, the Authorize method returns an authorization ID. The authorization ID is added to the ThreadResults collection to indicate to the result Web page that the authorization is complete.

The following is the code for the Payment class.

 
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Threading;

public class Payment : System.Web.UI.Page
{
  // GUID, holds a unique ID for this authorization request
  protected Guid RequestId;
  
  private void Page_Load(object sender, System.EventArgs e)
  {
    if (Page.IsPostBack)
    {
      // This is a postback, so authorize the credit card details...

      // Create a new request id
      RequestId = Guid.NewGuid();
                
      // Create and start a worker thread, to process the credit card details
      ThreadStart ts = new ThreadStart(AuthorizePayment);
      Thread workerThread = new Thread(ts);
      workerThread.Start();
        
      // Redirect to the "result page" (append the requestId to the query string)
      Response.Redirect("Result.aspx?RequestId=" + RequestId.ToString());
    }
    else
    {
      // This is not a postback, so initialize the Web UI controls
      int currentYear = DateTime.Now.Year;

      // Populate the expiration date dropdown lists
      for (int index = currentYear; index < currentYear + 6; index++)
        cmbExpDateYear.Items.Add(new ListItem(index.ToString(), 
                                              index.ToString()));

      for (int index = 1; index < 13; index++)
        cmbExpDateMonth.Items.Add(new ListItem(index.ToString(), 
                                               index.ToString()));

      for (int index = 1; index < 32; index++)
        cmbExpDateDay.Items.Add(new ListItem(index.ToString(), 
                                             index.ToString()));
    }
  }

  // Send an authorization request to the credit card authority.
  // This method is executed by the worker thread.
  private void AuthorizePayment()
  {
    // Send the request
    int authorizationId = CCAuthorizationService.Authorize(
                                   cmbCCType.Items[cmbCCType.SelectedIndex].Value, 
                                   txtCCNumber.Text,
                                   DateTime.MaxValue,
                                   Double.Parse(txtAmount.Text)); 

    // Add the authorization id to the result collection
    ThreadResults.Add(RequestId, authorizationId);
  }
  
  // Plus other members...
}
  

Defining the CCAuthorizationService Class

The CCAuthorizationService class interacts with a back-end credit card agency to authorize the user's credit card details. The exact mechanism for credit card authorization is irrelevant; the important point is that the authorization task is likely to take several seconds (or minutes) to complete.

The following code shows a mock-up implementation for the CCAuthorizationService class. The Authorize method waits for 5 seconds, and then returns a random authorization ID.

using System;
using System.Threading;

public sealed class CCAuthorizationService
{
  public static int Authorize(string ccType, 
                              string ccNumber, 
                              DateTime expDate, 
                              double amount)
  {
    // Wait for the credit card authority...
    Thread.Sleep(new TimeSpan(0, 0, 0, 7, 0));

    // Return the authorization id
    return new Random(0).Next(100); 
  }
}
  

Defining the ThreadResults Class

The ThreadResults class holds a collection of results from worker threads, so that the result page can access the result of each credit card authorization task when it is complete.

ThreadResults holds the results in a hash table. The keys in the hash table are the request IDs, and the values in the hash table are the corresponding authorization IDs. The ThreadResults class provides methods to insert, query, and remove items in the hash table.

The following is the code for the ThreadResults class.

using System;
using System.Collections;

public sealed class ThreadResults
{
  private static Hashtable results = new Hashtable();

  public static object Get(Guid itemId)
  {
    return results[itemId];
  }

  public static void Add(Guid itemId, object result)
  {
    results[itemId] = result;
  }

  public static void Remove(Guid itemId)
  {
    results.Remove(itemId);
  }

  public static bool Contains(Guid itemId)
  {
    return results.Contains(itemId);
  }
}
  

Defining the Result Class

The Result class is the code-behind class for the Result.aspx Web page. This Web page displays a "wait" message while the credit card authorization is taking place as shown in Figure B.14.

Ff647281.f0bdiforwc14(en-us,PandP.10).gif

Figure B.14. The result page displays a "wait"' message while the long-running task is taking place

When the credit card authorization is complete, the Result.aspx Web page displays the authorization ID as shown in Figure B.15.

Ff647281.f0bdiforwc15(en-us,PandP.10).gif

Figure B.15. The result page displays the authorization ID when the long-running task is complete

The Page_Load method in the Result class retrieves the RequestId parameter from the HTTP query string and tests whether the worker thread has completed authorizing this request:

  • If the authorization request is not yet complete, a Refresh header is added to the HTTP response. This causes the Web page to reload itself automatically in 2 seconds.
  • If the authorization request is complete, the authorization ID is retrieved from the ThreadResults class and is displayed on the Web page.
  • The following is the code for the Result class.
using System;

public class Result : System.Web.UI.Page
{
  protected System.Web.UI.WebControls.Label lblMessage;
    
  private void Page_Load(object sender, System.EventArgs e)
  {
    // Get the request id
    Guid requestId = new Guid(Page.Request.QueryString["RequestId"].ToString());

    // Check the thread result collection
    if(ThreadResults.Contains(requestId))
    {
      // The worker thread has finished

      // Get the authorization id from the thread result collection
      int authorizationId = (int)ThreadResults.Get(requestId); 
        
      // Remove the result from the collection
      ThreadResults.Remove(requestId);

      // Show the result
      lblMessage.Text = "You payment has been sent succesfully. " + 
                        "The authorization id is " + authorizationId.ToString();
    }
    else
    {
      // The worker thread has not yet finished authorizing the payment details.
      // Add a refresh header, to refresh the page in 2 seconds.
      Response.AddHeader("Refresh", "2");
    }
  }
  // Plus other members...
}

How To: Use the Trusted Subsystem Model

The trusted subsystem model enables middle-tier services to use a fixed identity to access downstream services and resources. The security context of the original caller does not flow through the service at the operating system level, although the application may choose to flow the original caller's identity at the application level. It may need to do so to support back-end auditing requirements or to support per-user data access and authorization.

You can define multiple trusted identities if necessary, based on the role membership of the caller. For example, you might have two groups of users, one who can perform read/write operations on a database, and the other who can perform read-only operations. In such a scenario, you can map each role to a different trusted identity in the underlying system.

The main advantages of using he trusted subsystem model are scalability, simple back-end ACL management, and protection of data from direct access by users.

To make use of the trusted subsystem model, you need to make the following configuration changes in IIS and ASP.NET on the Web server:

  • Configure IIS authentication–Configure IIS so that it authenticates the original user. You can use Windows authentication (Basic, Digest, Integrated Windows, or Certificate authentication), Forms authentication, or Passport authentication.

  • Configure ASP.NET authentication–Edit Web.config for the ASP.NET Web application so that it specifies which authentication mechanism to use for this application. Add an <authentication> element that specifies the authentication mode ("Windows," "Forms," or "Passport").

    <authentication mode="Windows"/>  -or-
    <authentication mode="Forms"/>    -or-
    <authentication mode="Passport"/>
    
    

    You must also ensure impersonation is disabled. Impersonation is a mechanism whereby the user's original security context is passed on to downstream applications so that they can perform their own authentication and authorization tests on the user's real credentials. Impersonation is disabled by default in ASP.NET, unless you have an <identity impersonate="true"/> element. To explicitly disable impersonation, add the following element to Web.config.

    <identity impersonate="false"/>
    
    

How To: Use Impersonation/Delegation with Kerberos Authentication

In the impersonation/delegation model, the ASP.NET Web application authenticates and authorizes the user at the first point of contact as before. These credentials are flowed to downstream applications, to enable the downstream applications to perform their own authentication and authorization tests using the real security credentials of the original user.

To enable impersonation/delegation with Kerberos authentication, you must make the following configuration changes in IIS and ASP.NET on the Web server:

  • Configure IIS authentication–Configure IIS for the Web application's virtual root, so that it uses Integrated Windows authentication (that is, Kerberos authentication).

  • Configure ASP.NET authentication–Edit Web.config for the ASP.NET Web application. You must set Windows authentication as the authentication mechanism and enable impersonation.

    <authentication mode="Windows"/>
    <identity impersonate="true"/>
    
    

Note   Impersonation/delegation with Kerberos authentication and delegation is feasible only if all computers are running Windows 2000 or later. Furthermore, each user account that needs to be impersonated must be stored in Active Directory and must not be configured as "Sensitive and cannot be delegated."

How To: Use Impersonation/Delegation with Basic or Forms Authentication

In the impersonation/delegation model, the ASP.NET Web application authenticates and authorizes the user at the first point of contact as before. These credentials are flowed to downstream applications, to enable the downstream applications to perform their own authentication and authorization tests using the real security credentials of the original user.

To enable impersonation/delegation with Basic authentication or Forms authentication, you must make the following configuration changes in IIS and ASP.NET on the Web server:

  • Configure IIS authentication–Configure IIS for the Web application's virtual root so that it uses Basic authentication or Forms authentication.

  • Configure ASP.NET authentication–Edit Web.config for the ASP.NET Web application so that it uses either Basic authentication or Forms authentication.

    <authentication mode="Basic"/>   -or-  
    <authentication mode="Forms"/>
    
    

    You must also enable impersonation.

    <identity impersonate="true"/>
    
    

You must also write code in your ASP.NET Web application to obtain the user's name and password:

  • If you use Basic authentication, get the user's name and password from HTTP server variables.

    string username = Request.ServerVariables["AUTH_USER"];
    string password = Request.ServerVariables["AUTH_PASSWORD"];
    
    
    
  • If you use Forms authentication, get the user's name and password from fields in the HTML logon form. For example, if the logon form has text fields named txtUsername and txtPassword, you can get the user name and password as follows.

    string username = txtUsername.Text;
    string password = txtPassword.Text;
    
    

How To: Localize Windows Forms

In Windows Forms-based applications, you can use the Visual Studio .NET Forms Designer to create localized versions of your form.

To create localized versions of your form

  1. Design your form in the usual manner, laying out all the necessary controls. Any text labels you assign at this stage will be used by your application's default culture.
  2. After you design the form, set the form's Localizable property to True.
  3. For each alternative culture that your application will support, create a localized version of the form as follows:
    1. Set the form's Language property to the culture you want to work with.
    2. Modify each of the controls on your form to comply with the culture you are working with. For example, change labels on the form to display text in the appropriate natural language for the culture.

As you add cultures to your form, new .resx files are created for the form. To view the .resx files, make sure you have selected the Show All Files option in Solution Explorer. The .resx files appear beneath your form file; for example, if you add support for French (fr-FR) to a form named MyForm, Visual Studio. NET will create MyForm.fr-FR.resx.

When you run the application, the run time uses the most appropriate resource file to render the user interface based on the Thread.CurrentThread.CurrentUICulture setting. In general, you want to specify a culture so that every part of the application's user interface is appropriate to that culture; therefore, you must set the culture before the InitializeComponent method is called. The following code sample sets the current culture and current UI culture in the constructor of the MyForm class.

using System.Windows.Forms;
using System.Threading;
using System.Globalization;

public class MyForm : System.Windows.Forms.Form
{
  // Constructor
  public MyForm()
  {
    // Set the culture and UI culture before InitializeComponent is called
    Thread.CurrentThread.CurrentCulture   = new CultureInfo("fr-FR");
    Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR");

    // Initialize components, using the specified culture and UI culture
    InitializeComponent();

    // Perform any additional initialization, as appropriate
    // ...
  }
  // Plus other members...
}
  

For additional information about how localize Windows Forms, see "Let Your Apps Span the Globe with Windows Forms and Visual Studio .NET" on MSDN (https://msdn.microsoft.com/en-us/magazine/cc301720.aspx).

How To: Define a Catch-All Exception Handler in Windows Forms-based Applications

In Windows Forms-based applications, you can define a catch-all exception handler method to catch all un-trapped thread exceptions. In the exception handler method, you can take appropriate recovery actions—usually to display a user-friendly error message.

To define a catch-all exception handler

  1. Implement an event handler method for the ThreadException event of the System.Windows.Forms.Application class.
  2. Add the event handler method to the Application.ThreadException event. The best place to do this is in the Main method of your application.

The following code sample implements a catch-all event handler method that publishes exceptions using the Microsoft Exception Management Application Block.

using System.Windows.Forms;
using System.Threading;
using Microsoft.ApplicationBlocks.ExceptionManagement;

public class MyForm : System.Windows.Forms.Form
{
  // Catch-all event handler method
  protected static void CatchAllExceptions(object Sender, 
                                           ThreadExceptionEventArgs e)
  {
    // Publish the exception
    ExceptionManager.Publish(ex);
  }

  // Main method in the application
  public static void Main()
  {
    // Create a new ThreadExceptionEventHandler delegate instance
    // and add it to the Application.ThreadException event
    Application.ThreadException += 
        new ThreadExceptionEventHandler(CatchAllExceptions);

    // Run the Windows Forms application
    Application.Run(new MyForm());
  }
}
  

For more information about the Microsoft Exception Management Application Block, see MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/emab-rm.asp).

Start | Previous

patterns & practices Developer Center

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

© Microsoft Corporation. All rights reserved.