Code Customization Model: Best Practices for Code Customization
While every customization and every need will be different, we have compiled a list of best practices that we recommend you follow. These best practices will help you add customizations at the most appropriate location for the vast majority of needs.
- Razi Mohiuddin, President of Iron Speed, Inc.

September 27, 2006
Iron Speed Designer V4.0

Where to add customizations?

In general you can handle events or override methods at the page class level, at the table or record control level or even at an individual field control level. For example, if you want to initialize the Country field to United States on a page, you can handle the PreRender event at the Page level, at the Record Control level or even the Country field level. So what is the best place to add this code customization?

We recommend that you add your code customizations at the Record Control class level. There are many reasons for this including:

If you need a value from the underlying database record, you will have direct access to it by calling the GetRecord method or using the DataSource property.

  • When you have a table control displayed on a page, each row of the page is created dynamically at run time based on the number of rows being displayed. If you customize at the page or the table control level, you need to determine the exact row you are working on in your code customization. By doing it in the record control, you will always have access to the specific record you are interested in handling.
  • The code customization will be the same whether you are working within a table or just displaying a single record.
What events to handle or methods to override?

Some code customizations can be performed by handling the following events. For each event, please pay special attention to the recommended class for handling the event.

DataBind Method

If you want to initialize a value, format a value, compute a value, hide or display a control based on some criteria, or change the look and feel of a control, we recommend you override the DataBind method for the Record Control class to make these customizations. Remember to first call the MyBase.DataBind or base.DataBind to perform the underlying functions.

C#:

public override void DataBind()
{
    base.DataBind();
 
    // Pre-initialize Title if blank
    if (this.ContactTitle.Text == "")
    {
        this.ContactTitle.Text = "Manager";
    }
}

Visual Basic .NET:

Public Overrides Sub DataBind()
    MyBase.DataBind()
 
    ' Pre-initialize Title if blank
    If Me.ContactTitle.Text = "" Then
        Me.ContactTitle.Text = "Manager"
    End If
End Sub

Validate Method

Two specific validators can be automatically generated based on the values selected in the Page Properties dialog box as specified below:

  • RequiredFieldValidator: Selecting the Required checkbox on the Display tab of the Page Properties dialog box will generate a Required Field Validator for a TextBox, Checkbox and File Upload controls.
  • MaxLengthValidator: Specifying a maximum length on the Display tab of the Page Properties dialog box will generate a MaxLengthValidator for a TextBox control.
In addition to these two validators, you can add your own custom validation logic very easily by overriding the Validate method at the Record Control class level. The Validate method is called from the SaveData method before the GetUIData method is called. Note that GetUIData retrieves the data from the user interface controls and converts it into the internal format required for saving. For example, a date specified as a text string “12-1-2006” will be converted to a Date object with the appropriate month, day and year values.

To validate fields from the user interface controls, use their user interface controls to validate the data. Validation errors must be grouped together and an exception thrown to abort the saving of data.

C#:

public override void Validate()
{
    base.Validate();
 
    // Additional Validation
    if (this.State.Text != "CA")
    {
        throw new Exception("State must be CA (California).");
    }
}

Visual Basic .NET:

Public Overrides Sub Validate()
    MyBase.Validate()
 
    ' Additional Validation     If Me.State.Text <> "CA" Then
        Throw New Exception("State must be CA (California).")
    End If
End Sub

GetUIData Method

If you want to make changes to the values before they are saved, we recommend you override the GetUIData method at the Record Control class level. The GetUIData method is called from within SaveData and retrieves all of the values from the user interface controls prior to the data being saved in the database. You can even set fields that are not displayed to the user, such as audit control fields.

C#:

public override void GetUIData()
{
    base.GetUIData();
 
    // Set additional field values here
    this.DataSource.LastUpdateDate = DateTime.Now();
}

Visual Basic .NET:

Public Overrides Sub GetUIData()
    MyBase.GetUIData()
 
    ' Set additional field values here
    Me.DataSource.LastUpdateDate = DateTime.Now()
End Sub

CommitTransaction Method

If you want to access the Id of the record or send an email after the record is saved, we recommend you override the CommitTransaction method at the page class level. The CommitTransaction is defined in the BasePage class and calls DBUtils.CommitTransaction. If you override the CommitTransaction method, make sure to call the base CommitTransaction.

C#:

public override void CommitTransaction(object sender)
{
    // Call the base CommitTransaction
    base.CommitTransaction(sender);
 
    // Use the Me.CustomersRecordControl.GetRecord() to retrieve the record that was just updated.
    CustomersRecord myRecord;
    myRecord = this.CustomersRecordControl.GetRecord();
 
    // Send a confirmation email with the CustomerId.
    try
    {
        BaseClasses.Utils.MailSender email = new BaseClasses.Utils.MailSender();
        email.AddFrom("fromAddress@company.com");
        email.AddTo("toAddress@company.com");
        email.AddBCC("bccAddress@company.com");
        email.SetSubject("Confirmation");
        email.SetContent("Thank you for your request. Your Customer Id is: " +
        myRecord.CustomerID);
        email.SendMessage();
 
    }
    catch (System.Exception ex)
    {
        // Report the error message to the user.
        Utils.RegisterJScriptAlert(this, "UNIQUE_SCRIPTKEY", "Could not send an email. Error was:
        " + ex.Message);
    }
}

Visual Basic .NET:

Public Overrides Sub CommitTransaction(ByVal sender As Object)
    ' Call the base CommitTransaction
    MyBase.CommitTransaction(sender)
 
    ' Use the Me.CustomersRecordControl.GetRecord() to retrieve the record that was just
    updated.
    Dim myRecord As CustomersRecord
    myRecord = Me.CustomersRecordControl.GetRecord()
 
    ' Send a confirmation email with the CustomerId.
    Try
        Dim email As New BaseClasses.Utils.MailSender
        email.AddFrom("fromAddress@company.com")
        email.AddTo("toAddress@company.com")
        email.AddBCC("bccAddress@company.com")
        email.SetSubject("Confirmation")
        email.SetContent("Thank you for your request. Your Customer Id is: " &
        myRecord.CustomerID)
        email.SendMessage()
 
    Catch ex As System.Exception
        ' Report the error message to the user.
        Utils.RegisterJScriptAlert(Me, "UNIQUE_SCRIPTKEY", "Could not send an email. Error was: "
        & ex.Message)
    End Try End Sub

Redirect Method

If you want to modify the URL before redirection, we recommend you override the CommitTransaction method (described above) at the Page class level and redirect after the commit. The SaveButton_Click method calls the SaveButton_Click_Base method on the page class to save and commit the database records. Once the save and commit happens, the SaveButton_Click_Base redirects to the URL specified on the Page Properties dialog box. As such you cannot add code after the call to SaveButton_Click_Base method in SaveButton_Click because once the redirect happens, control will not return back to the SaveButton_Click method.

C#:

public void SaveButton_Click(object sender, EventArgs args)
{
    SaveButton_Click_Base(sender, args);
    // Code below will never get executed since SaveButton_Click_Base will
    // redirect to another page. To customize, either replace SaveButton_Click_Base
    // functionality here, or override CommitTransaction.
}

Visual Studio .NET:

Public Sub SaveButton_Click(ByVal sender As Object, ByVal args As EventArgs)
    SaveButton_Click_Base(sender, args)
    ' Code below will never get executed since SaveButton_Click_Base will
    ' redirect to another page. To customize, either replace SaveButton_Click_Base
    ' functionality here, or override CommitTransaction.
End Sub

CreateWhereClause Method

If you want to modify the query before it is executed, the best place to do it is by overriding the CreateWhereClause method at the Table Control level for Show Table pages, and the CreateWhereClause method at the Record Control class level for the Add, Edit, and Show Record pages. Please note that CreateWhereClause is not available at the Page level since each table and record control has its own query. You can override the CreateWhereClause and add your own where clause.

C#:

protected override BaseClasses.Data.WhereClause CreateWhereClause()
{
    WhereClause wc;
 
    wc = base.CreateWhereClause();
 
    if (IsNothing(wc))
    {
        // Get a blank where clause if the base function returned null.
        wc = new WhereClause();
    }
 
    wc.iAND(CustomersTable.City, Starts_With, "Mountain");
}

Visual Basic .NET:

Protected Overrides Function CreateWhereClause() As BaseClasses.Data.WhereClause
    Dim wc As WhereClause
 
    wc = MyBase.CreateWhereClause()
 
    If IsNothing(wc) Then
        ' Get a blank where clause if the base function returned null.
        wc = New WhereClause
    End If
 
    wc.iAND(CustomersTable.City, Starts_With, "Mountain")
End Function

PopulateFilter Method

If you want to pre-initialize a filter with a URL, cookie, or session value, the best place to do this is to override the PopulateFilter method for the specific field filter at the Table Control class. You can call the base method and then set the current value. You must also override the CreateWhereClause method to add the initial setting of the filter to the where clause. You can also customize the Where Clause used by the Populate Filter to limit the type of records retrieved, such as only Active customers. For additional customization, you can even replace the entire PopulateFilter method with your own method.

C#:

protected override WhereClause CreateWhereClause()
{
    // Call the MyBase.CreateWhereClause()
    WhereClause wc = base.CreateWhereClause();
 
    // If MyBase.CreateWhereClause() returns nothing then create a new
    // instance of WhereClause
    if ((wc == null))
    {
        wc = new WhereClause();
    }
 
    // Add a where clause based on the querystring
    string country = this.Page.Request.QueryString("Country");
    if (!this.Page.IsPostBack && country != "")
    {
        wc.iAND(CustomersTable.Country, EqualsTo, country);
    }
    return wc;
}
 
// -----------------------------------------------------------------------------------------------------------
protected override void PopulateCityFilter(string selectedValue, int maxItems)
{
    string country = this.Page.Request.QueryString("Country");
 
    // The selected value only will be set when the page loads for the first time.
    if (!this.Page.IsPostBack && country != "")
    {
        base.PopulateCityFilter(country, maxItems);
    }
    else
    {
        base.PopulateCityFilter(selectedValue, maxItems);
    }
}

Visual Basic .NET:

Protected Overrides Function CreateWhereClause() As WhereClause
      ' Call the MyBase.CreateWhereClause()
    Dim wc As WhereClause = MyBase.CreateWhereClause()
 
      ' If MyBase.CreateWhereClause() returns nothing then create a new
      ' instance of WhereClause
    If (IsNothing(wc)) Then
        wc = New WhereClause
    End If
 
    ' Add a where clause based on the querystring
    Dim country As String = Me.Page.Request.QueryString("Country")
    If Not (Me.Page.IsPostBack) AndAlso country <> "" Then
        wc.iAND(CustomersTable.Country, EqualsTo, country)
    End If
 
    Return wc
End Function
 
‘ -----------------------------------------------------------------------------------------------------------
Protected Overrides Sub PopulateCountryFilter(ByVal selectedValue As String, ByVal maxItems As Integer)  
      Dim country As String = Me.Page.Request.QueryString("Country")
 
    ' The selected value only will be set when the page loads for the first time.
    If Not (Me.Page.IsPostBack) AndAlso country <> "" Then
        MyBase.PopulateCountryFilter(country, maxItems)
    Else
        MyBase.PopulateCountryFilter(selectedValue, maxItems)
    End If
End Sub

Don’t Forget the IsPostBack Flag

By far the most common mistake made by .NET developers is to ignore the IsPostBack flag when handling an event or overriding a method. Note that each method and event handler will be executed at least twice, once when the page is being displayed and once when the user presses a button to save or go to another page. If you set AutoPostBack on a field to handle a TextChanged or SelectedItemChanged event, then each of the event handlers will be executed three or more times. When you add custom logic, make sure you check for the IsPostBack flag to decide whether to execute your code the first time the page is displayed, or only during a postback such as a button click or always.

C#:

if (!this.Page.IsPostBack)
{
    // This code executes when the page is first loaded.
}
else
{     // This code executes during a button click or other postback.
}
// This code executes in all cases.

Visual Basic .NET:

If Not (Me.Page.IsPostBack) Then
    ' This code executes when the page is first loaded.
Else
    ' This code executes during a button click or other postback.
End If
' This code executes in all cases.

Additional Page Lifecycle References

Understanding ASP.NET View State and Page Lifecycle:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/viewstate.asp

The ASP.NET Page Object Model:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/aspnet-pageobjectmodel.asp

About the Author

Razi Mohiuddin
Co-Founder, President & CEO of Iron Speed, Inc.

Mr. Mohiuddin co-founded numerous Internet and software companies, including Onsale, Inc. (later Egghead.com and now Amazon.com), Ambia Corporation, an electronic document publishing software company later acquired by Infodata Systems Inc., and Software Partners, Inc., a software consulting company that developed StreetSmart, e.Schwab, FundMap, SchwabLink and Retirement Planner software for Charles Schwab & Co.

Mr. Mohiuddin earned his BS in Computer Science from the University of Illinois, Chicago.



  Privacy Statement