|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
IntroductionThis article is a follow-up to my article Building ASP.NET Pages Dynamically in the Code-Behind. The previous article presented the concept and included a very basic example. This article shows how to implement a more complicated sample project - collecting data from a form and displaying it back on the page as shown in the screenshot below: In my previous article, I also emphasized the importance of building your own class library to make the process more efficient. This example provides a richer class library - although still only a small portion of what you could develop for your own projects. There is a lot of code here and even more in the project. Take the classes for your own use, or just borrow ideas for your own modification. These classes evolved over time for specific projects on which I worked. I wouldn't expect them to fit perfectly into your projects. It is more important to understand the concepts and possibilities. The ClassesThis project makes use of the classes from my previous article. It adds classes to make building forms easier (that is, actual forms where the user enters data - not "forms" in the way ASP.NET refers to every Web page as a form). ErrorLabel - A Class for Displaying Error MessagesThis class is intended to only display error messages. Add the label to your page. When there is no text, the label is not visible. In the catch handler for any exceptions, add the exception to the label and it becomes visible, displaying the error to the user. The appearance of error messages is specified in the CSS file. Remember to use friendly error messages that the user can actually understand! using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Runtime.InteropServices;
///
MyTextBox - An Enhanced Text BoxThe using System;
using System.Web.UI.WebControls;
using System.Net.Mail;
/// <summary>
/// Summary description for MyTextBox.
/// </summary>
public class MyTextBox : System.Web.UI.WebControls.TextBox
{
// these constants tell us what kind of data this box accepts
// they are negative because any positive value is interpreted as
// a maximum text length
public const int tbUnlimited = 0;
public const int tbPassword = -1;
public const int tbInteger = -2;
public const int tbDate = -3;
public const int tbDecimal = -4;
public const int tbTime = -5;
public const int tbEmail = -6;
// FormatLength is the format value from the constants above
// if it is a positive value, it is the maximum length for the data field
protected int m_nFormatLength = 0;
// specifies if the value is required
protected bool m_bRequired = false;
// FieldName is accepted for display in error messages
protected string m_strFieldName = "";
public MyTextBox(string strID, int nFormatLength)
{
ID = strID;
m_nFormatLength = nFormatLength;
TextMode = TextBoxMode.SingleLine;
Rows = 1;
if (nFormatLength == tbPassword)
{
TextMode = TextBoxMode.Password;
}
CssClass = "mytextbox";
}
public void SetMultiline(int nRows)
{
TextMode = TextBoxMode.MultiLine;
Rows = nRows;
}
public void SetDate(DateTime dt)
{
try
{
Text = dt.ToShortDateString();
}
catch
{
Text = "";
}
}
public void SetText(string strValue)
{
Text = strValue;
}
public string GetText()
{
CheckRequired(m_strFieldName);
if (m_nFormatLength > 0)
{
// in this case, a maximum length is specified
if (Text.Length > m_nFormatLength)
{
// data exceeds the maximum length, so throw an exception
// note use of field name in the exception
throw new Exception("Field " + m_strFieldName +
" exceeds the maximum length of " + Convert.ToString(m_nFormatLength));
}
}
return Text;
}
public DateTime GetDate()
{
CheckRequired(m_strFieldName);
if (Text == string.Empty) return DateTime.FromOADate(0.0);
try
{
return Convert.ToDateTime(Text);
}
catch
{
throw new Exception("Field " + TextWriter.MakeItalicText(m_strFieldName) +
" is not in a valid date format.");
}
}
public DateTime GetTime()
{
CheckRequired(m_strFieldName);
if (Text == string.Empty) return DateTime.FromOADate(0.0);
try
{
return Convert.ToDateTime(Text);
}
catch
{
throw new Exception("Field " + TextWriter.MakeItalicText(m_strFieldName) +
" is not in a valid time format.");
}
}
public int GetInt()
{
CheckRequired(m_strFieldName);
try
{
return Convert.ToInt32(Text);
}
catch
{
throw new Exception("Field " + TextWriter.MakeItalicText(m_strFieldName) +
" is not in a valid integer format.");
}
}
public double GetDouble()
{
CheckRequired(m_strFieldName);
try
{
return Convert.ToDouble(Text);
}
catch
{
throw new Exception("Field " + TextWriter.MakeItalicText(m_strFieldName) +
" is not in a valid numeric format.");
}
}
protected void CheckRequired(string strField)
{
// function checks to see if value is required and provided
if (m_bRequired == false) return;
if (Text == string.Empty)
{
throw new Exception("Field " + TextWriter.MakeItalicText(strField) +
" is a required value.");
}
}
public void SetRequired(bool bValue, string strFieldName)
{
m_bRequired = bValue;
m_strFieldName = strFieldName;
}
public string GetEmail()
{
CheckRequired(m_strFieldName);
ValidateEmail();
return Text.ToLower();
}
protected void ValidateEmail()
{
if (m_bRequired == false && Text == string.Empty) return;
try
{
MailAddress addr = new MailAddress(Text);
}
catch
{
throw new Exception(TextWriter.MakeItalicText(m_strFieldName) +
" is not a valid email address.");
}
}
}
A Note on Throwing ExceptionsOne of the C# technical interview questions available on the Web states that you shouldn't throw exceptions from your own code. One should be careful about blanket statements about what you should or should not do in programming. C# finally gives us a language that has outstanding exception handling. It would be a shame not to make full use of it. This class is a perfect example of when throwing exceptions is appropriate. When data is unacceptable, throw an exception. Your database does it. There is no reason your custom data entry controls shouldn't either. FormTable - A Table for Building FormsThe using System;
using System.Web.UI.WebControls;
/// <summary>
/// A class for easily creating forms with columns of controls.
/// </summary>
public class FormTable : System.Web.UI.WebControls.Table
{
public FormTable()
{
CssClass = "formtable";
this.CellPadding = 2;
this.CellSpacing = 2;
Width = Unit.Percentage(100);
}
// this function adds a horizontal row with a caption
// to divide the form into sections
public void AddDivider(string strText)
{
Label l = new Label();
l.Text = TextWriter.MakeBoldText(strText);
TableRow row = AddRow();
row.CssClass = "formtabledivider";
TableCell cell = new TableCell();
cell.Width = Unit.Percentage(100);
cell.Controls.Add(l);
row.Cells.Add(cell);
cell.ColumnSpan = 2;
}
// add a row of text that goes all the way across the width of the form
public void AddRow(string strText)
{
TableRow row = AddRow();
TableCell cell = new TableCell();
cell.Width = Unit.Percentage(30);
cell.Text = strText;
cell.ColumnSpan = 2;
row.Cells.Add(cell);
}
// add a row of a control that goes all the way across the width of the form
public void AddRow(System.Web.UI.Control control)
{
TableRow row = AddRow();
TableCell cell = new TableCell();
cell.Width = Unit.Percentage(100);
cell.Controls.Add(control);
row.Cells.Add(cell);
cell.ColumnSpan = 2;
}
// add a row with a control - typically a button
// centered across the width of the form
public void AddButtonRow(System.Web.UI.Control control)
{
TableRow row = AddRow();
row.HorizontalAlign = HorizontalAlign.Center;
TableCell cell = new TableCell();
cell.Width = Unit.Percentage(100);
cell.Controls.Add(control);
row.Cells.Add(cell);
cell.ColumnSpan = 2;
cell.HorizontalAlign = HorizontalAlign.Center;
}
// add a row of with text for a field label and a control for accepting data
// text is in left column, control is in right column
// this is the function used most often
public void AddRow(string strText, System.Web.UI.Control control, bool bRequired)
{
TableRow row = AddRow();
TableCell cell = new TableCell();
if (bRequired)
{
// Add the required *
// Note the use of a CSS class to control display
cell.Text = TextWriter.MakeBoldText(strText) +
TextWriter.Span("required", "*");
}
else
{
cell.Text = strText;
}
cell.Width = Unit.Percentage(30);
row.Cells.Add(cell);
cell = new TableCell();
cell.Controls.Add(control);
cell.Width = Unit.Percentage(70);
row.Cells.Add(cell);
SetControlRequired(control, strText, bRequired);
}
// Adds a row to the table indicating that * fields are required
public void AddRequiredLabelRow()
{
string s;
s = TextWriter.Span("required", "*") + "" +
TextWriter.MakeBoldText("Required Value");
AddRow(s);
}
protected TableRow AddRow()
{
TableRow row = new TableRow();
Rows.Add(row);
row.Width = Unit.Percentage(100);
return row;
}
protected void SetControlRequired(System.Web.UI.Control control,
string strFieldName, bool bRequired)
{
if (control is MyTextBox)
{
// Only controls of MyTextBox support the required attribute
MyTextBox tb = (MyTextBox)control;
tb.SetRequired(bRequired, strFieldName);
}
}
}
ContactData - The Data ObjectWe need an object to hold the data from our form. We could specify the members as properties, but I am an old C++ programmer and old habits die hard. I like to avoid properties with getters and setters for two reasons:
/// <summary>
/// This is a class to hold contact data
/// It can include functions to read/write database if desired
/// </summary>
public class ContactData
{
protected string m_strFirstName = "";
protected string m_strLastName = "";
protected string m_strAddress1 = "";
protected string m_strCity = "";
protected string m_strStateName = "";
protected string m_strStateCode = "";
protected string m_strCountryName = "";
protected string m_strCountryCode = "";
protected string m_strPostalCode = "";
protected DateTime m_dtBirthday = DateTime.MinValue;
public ContactData()
{
//
// TODO: Add constructor logic here
//
}
/////////////////////////////////////////////////////////////////
// Get functions
public string GetFirstName()
{
return m_strFirstName;
}
public string GetLastName()
{
return m_strLastName;
}
// Code removed for brevity
///////////////////////////////////////////////////////////
// Set functions
public void SetFirstName(string strNew)
{
m_strFirstName = strNew;
}
public void SetLastName(string strNew)
{
m_strLastName = strNew;
}
// Code removed for brevity
} // end of class declaration
ContactFormTable - A FormTable for Collecting Contact DataNow we put all of the classes together to see how we easily create a table to collect contact data. If this was the only data entry form in our project, it would be a lot of work. However, on a large project with many pages of forms, it is nice to be able to build them out so easily.
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// This is the table that allows users to enter contact information
/// </summary>
public class ContactFormTable : FormTable
{
// declare the controls for the form
protected MyTextBox m_FirstNameTextBox;
protected MyTextBox m_LastNameTextBox;
protected MyTextBox m_AddressTextBox;
protected MyTextBox m_CityTextBox;
protected MyTextBox m_PostalCodeTextBox;
protected StateComboBox m_StateComboBox;
protected CountryComboBox m_CountryComboBox;
protected MyTextBox m_BirthdayTextBox;
public ContactFormTable()
{
BuildForm();
}
protected void BuildForm()
{
// Build out the form - note that first and last name are required
m_FirstNameTextBox = new MyTextBox("FirstName", MyTextBox.tbUnlimited);
AddRow("First Name", m_FirstNameTextBox, true);
m_LastNameTextBox = new MyTextBox("LastName", MyTextBox.tbUnlimited);
AddRow("Last Name", m_LastNameTextBox, true);
// in this case, the integer 100 is the maximum length for the value
m_AddressTextBox = new MyTextBox("Address", 100);
AddRow("Address", m_AddressTextBox, false);
m_CityTextBox = new MyTextBox("City", 100);
AddRow("City", m_CityTextBox, false);
m_StateComboBox = new StateComboBox("State");
AddRow("State", m_StateComboBox, false);
m_CountryComboBox = new CountryComboBox("Country");
AddRow("Country", m_CountryComboBox, false);
m_PostalCodeTextBox = new MyTextBox("PostalCode", 10);
AddRow("Postal Code", m_PostalCodeTextBox, false);
AddDivider("Personal Information");
// add a text box for the birthday and specify that it should be date format
m_BirthdayTextBox = new MyTextBox("Birthday", MyTextBox.tbDate);
AddRow("Birthday", m_BirthdayTextBox, false);
// add the row indicating required values
AddRequiredLabelRow();
}
public void FillForm(ContactData contact)
{
// fill the form from the contact object
m_FirstNameTextBox.SetText(contact.GetFirstName());
m_LastNameTextBox.SetText(contact.GetLastName());
m_AddressTextBox.SetText(contact.GetAddress1());
m_CityTextBox.SetText(contact.GetCity());
m_PostalCodeTextBox.SetText(contact.GetPostalCode());
// note that for the combo boxes, we are selecting by value - not by text
m_StateComboBox.SelectItemByValue(contact.GetStateCode());
m_CountryComboBox.SelectItemByValue(contact.GetCountryCode());
// set the birthday as a date
m_BirthdayTextBox.SetDate(contact.GetBirthday());
}
public void GetFormData(ContactData contact)
{
// build the contact object from form values
// this function will throw an exception if form data is not complete
contact.SetFirstName(m_FirstNameTextBox.GetText());
contact.SetLastName(m_LastNameTextBox.GetText());
contact.SetAddress1(m_AddressTextBox.GetText());
contact.SetCity(m_CityTextBox.GetText());
contact.SetPostalCode(m_PostalCodeTextBox.GetText());
contact.SetBirthday(m_BirthdayTextBox.GetDate());
// get state and country names and codes
contact.SetStateCode(m_StateComboBox.GetSelectedValue());
contact.SetCountryCode(m_CountryComboBox.GetSelectedValue());
contact.SetStateName(m_StateComboBox.GetSelectedText());
contact.SetCountryName(m_CountryComboBox.GetSelectedText());
}
}
Note that I used a couple of other classes - ContactInfoPanel - Display Contact Information on the PageWe also need to display our contact information on the page. I do this using the using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// Class to display a panel of contact information
/// </summary>
public class ContactInfoPanel : MyPanel
{
// A call to the base class specifies the CSS style for this panel
public ContactInfoPanel() : base("contactpanel")
{
}
public void BuildPanel(ContactData contact)
{
// Build the panel from a contact object
Controls.Clear(); // remove any existing controls
AddLiteral(TextWriter.MakeH2Text("Contact Information"));
AddContactInfo("First Name", contact.GetFirstName());
AddContactInfo("Last Name", contact.GetLastName());
AddContactInfo("Address", contact.GetAddress1());
AddContactInfo("City", contact.GetCity());
AddContactInfo("State Name", contact.GetStateName());
AddContactInfo("State Code", contact.GetStateCode());
AddContactInfo("Country Name", contact.GetCountryName());
AddContactInfo("Country Code", contact.GetCountryCode());
AddContactInfo("Birthday", contact.GetBirthday().ToShortDateString());
}
protected void AddContactInfo(string strFieldName, string strValue)
{
string s;
// be sure to safely encode to HTML any text that is entered
s = TextWriter.MakeBoldText(strFieldName) + "" +
System.Web.HttpUtility.HtmlEncode(strValue);
AddLiteral(TextWriter.MakeLine(s));
}
}
The PagesThose are the important classes for the project. Now let's see the look on the page. As I showed in Part 1, the only important line of code in the page markup is the placement of a <asp:PlaceHolder id="LocalPlaceHolder" runat="server"></asp:PlaceHolder>
The Code-BehindHere is the code-behind for the form. I add the using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class ContactForm : BasePage
{
// here are some of the controls that will appear on our page
protected ContactFormTable m_FormTable;
protected ErrorLabel m_ErrorLabel;
protected ContactInfoPanel m_ContactPanel;
// here is the contact data object to store form data
protected ContactData m_Contact = null;
protected void Page_Load(object sender, EventArgs e)
{
try
{
// create the contact object
m_Contact = new ContactData();
// add the error label as the first control so errors are
// at the top of the page
m_ErrorLabel = new ErrorLabel();
LocalPlaceHolder.Controls.Add(m_ErrorLabel);
// add some title information
MyLiteral lit = new MyLiteral(TextWriter.MakeH1Text
("Enter Contact Information"));
LocalPlaceHolder.Controls.Add(lit);
// build the contact info panel
m_ContactPanel = new ContactInfoPanel();
LocalPlaceHolder.Controls.Add(m_ContactPanel);
m_ContactPanel.BuildPanel(m_Contact);
// add a spacer for aesthetics
MyPanel spacer = new MyPanel("spacer");
LocalPlaceHolder.Controls.Add(spacer);
// Add the form
m_FormTable = new ContactFormTable();
LocalPlaceHolder.Controls.Add(m_FormTable);
// Add a submit button - the table centers it
SubmitButtonTable buttontable = new SubmitButtonTable("Submit");
LocalPlaceHolder.Controls.Add(buttontable);
buttontable.m_Button.Click +=new EventHandler(SubmitButton_Click);
}
catch (Exception ex)
{
m_ErrorLabel.SetErrorText(ex);
}
}
protected void SubmitButton_Click(object sender, EventArgs e)
{
try
{
// get the data from the form. Remember that any missing or invalid data
// will throw an exception, so wrap this in an exception handler
m_FormTable.GetFormData(m_Contact);
// now fill the contact panel with the updated data
m_ContactPanel.BuildPanel(m_Contact);
}
catch (Exception ex)
{
m_ErrorLabel.SetErrorText(ex);
}
}
} // end of class
An Alternative Postback Processing MethodIf you want a more classic ASP approach to handling form postback, you can create a function for handling postback in the code behind and call it from a script block in the HTML. Make sure your script block occurs before the PlaceHolder! Otherwise, it won't work. The HTML would be modified to look like this: <!-- Make sure you call process postback operations BEFORE the placeholder -->
<%
if (IsPostBack)
{
ProcessPostback();
}
%>
<asp:PlaceHolder id="LocalPlaceHolder" runat="server"></asp:PlaceHolder>
In the code-behind, we would omit the button event handler and instead implement this function called from the markup. protected void ProcessPostback()
{
// this function is called from the page directly on any postback event.
// Another option is to use a button click event handler
if (IsPostBack == false) return; // A little extra error checking
try
{
// get the data from the form. Remember that any missing or invalid data
// will throw an exception, so wrap this in an exception handler
m_FormTable.GetFormData(m_Contact);
// now fill the contact panel with the updated data
m_ContactPanel.BuildPanel(m_Contact);
}
catch (Exception ex)
{
m_ErrorLabel.SetErrorText(ex);
}
}
The ProjectThe project was written using Visual Studio 2005 and ASP.NET 2.0 in C#. ConclusionAs I stated in the first article, the idea of moving all presentation to the code-behind, leaving nothing but a place holder in the markup, is a little extreme and may not be for every project. However, it is an important concept to understand. This article shows how you can extend the basic .NET classes to make building pages dynamically easier. Even if you do not adopt the technique in full, learning to enhance the basic .NET classes and learning to implement a better class design in the code-behind are important tools to improve your ASP.NET development. History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||