|
|
|
|
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.
|
|
|
|
|
|