在代码隐藏中动态构建 ASP.NET 网页 - 第二部分:表单示例






3.38/5 (14投票s)
本文介绍了一种在代码隐藏中动态构建整个 ASP.NET 网页内容的技术。
引言
本文是我上一篇文章《在代码隐藏中动态构建 ASP.NET 页面》的续篇。上一篇文章介绍了概念并包含了一个非常基础的示例。本文展示了如何实现一个更复杂的示例项目——从表单收集数据并在页面上将其显示回来,如下图所示。

在我之前的文章中,我还强调了构建自己的类库以提高效率的重要性。这个示例提供了一个更丰富的类库——尽管仍然只是您可以在自己的项目中开发的很小一部分。
这里有大量的代码,项目中还有更多。您可以直接使用这些类,或者借鉴其中的想法进行修改。这些类是根据我工作的具体项目随时间演变而来的。我不认为它们会完美地适用于您的项目。更重要的是理解概念和可能性。
类
本项目使用了上一篇文章中的类。它添加了一些类,使构建表单(即用户输入数据的实际表单,而不是 ASP.NET 将每个网页称为表单时的“表单”)更加容易。
ErrorLabel - 用于显示错误消息的类
此类仅用于显示错误消息。将标签添加到您的页面。当没有文本时,标签不可见。在任何异常的 catch 块中,将异常添加到标签,它就会变得可见,从而向用户显示错误。错误消息的外观在 CSS 文件中指定。请记住使用用户能够真正理解的友好错误消息!
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;
///
/// Class for displaying error messages
/// </summary>
public class ErrorLabel : Label
{
public ErrorLabel()
{
CssClass = "errorblock"; // set the CSS style
Visible = false; // keep label hidden unless there is text
}
public void SetErrorText(string strError)
{
Visible = true; // Make it visible
// add or append the error message
if (Text == string.Empty)
{
Text = TextWriter.MakeParagraph(strError);
}
else
{
Text += TextWriter.MakeParagraph(strError);
}
}
public void SetErrorText(string strError, Exception e)
{
SetErrorText(strError + " " + e.Message);
}
public void SetErrorText(Exception e)
{
SetErrorText(e.Message);
}
}
MyTextBox - 增强型文本框
MyTextBox
类继承自标准的文本框。它添加了数据验证功能,用于检查必填字段、最大长度和数据类型。如果数据验证失败,则会引发异常。类的代码如下所示。
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.");
}
}
}
关于引发异常的说明
网上 C# 技术面试题之一指出,你不应该从自己的代码中引发异常。在关于编程中应该做什么和不应该做什么的笼统声明时,应该小心。C# 终于给了我们一个具有出色异常处理能力的语言。浪费它将是一种耻辱。此类是引发异常的恰当示例。当数据不可接受时,引发异常。您的数据库就是这样做的。您的自定义数据输入控件也没有理由不能这样做。
FormTable - 用于构建表单的表格
FormTable
类允许您通过编程快速将控件添加到表单中。它是一个两列的表格。左列是字段标签。右列包含控件。请注意其中到处使用的 CSS 样式。
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 - 数据对象
我们需要一个对象来保存表单中的数据。我们可以将成员指定为属性,但我是一名老 C++ 程序员,旧习惯难改。我喜欢避免使用带有 getter 和 setter 的属性,原因有两个:
- 我经常重载 getter 或 setter 并传递不同的参数。
- 我希望我的所有
Get
和Set
函数都出现在 Intellisense 弹出列表中。如果您将它们保留为属性,它们会根据名称散布在列表中。
/// <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 - 用于收集联系人数据的 FormTable
现在我们将所有类放在一起,看看如何轻松创建一个收集联系人数据的表单。如果这是我们项目中的唯一数据录入表单,那将是一项艰巨的任务。然而,在一个包含许多表单页面的大型项目中,能够如此轻松地构建它们是很不错的。
ContactFormTable
继承自 FormTable
。表单从 ContactData
对象填充。提交时,表单会填充一个 ContactDataObject
。
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());
}
}
请注意,我使用了几个其他类 - StateComboBox
和 CountryComboBox
。这些类都包含在项目中。
ContactInfoPanel - 在页面上显示联系人信息
我们还需要在页面上显示我们的联系人信息。我使用 ContactInfoPanel
类来完成此操作。它继承自 MyPanel
,这是我在上一篇文章中介绍的一个类。它显示了屏幕截图中表单上方矩形的联系人信息。
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));
}
}
页面
这些是项目的重要类。现在让我们看看页面上的效果。
正如我在第一部分中展示的那样,页面标记中唯一重要的代码行是 PlaceHolder
控件的放置。这个 PlaceHolder
将包含页面上的所有控件。
<asp:PlaceHolder id="LocalPlaceHolder" runat="server"></asp:PlaceHolder>
代码隐藏
这是表单的代码隐藏。我添加了 ErrorLabel
、ContactInfoPanel
、表单本身、一个按钮以及一些其他美学元素。按钮有一个事件处理程序,该处理程序从表单读取数据并将其显示在面板上。我喜欢这种方法的一点是异常在 Page_Load
事件中的处理方式。所有控件的创建都包含在异常处理程序中。任何引发的异常都会在 ErrorLabel
中显示错误。还将表单处理包装在事件处理程序中。此事件处理程序将捕获任何表单验证错误。
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
另一种回发处理方法
如果您想要更经典的 ASP 方法来处理表单回发,可以在代码隐藏中创建一个函数来处理回发,并从 HTML 中的脚本块调用它。确保您的脚本块位于 PlaceHolder
**之前**!否则,它将不起作用。HTML 会修改成如下所示:
<!-- Make sure you call process postback operations BEFORE the placeholder -->
<%
if (IsPostBack)
{
ProcessPostback();
}
%>
<asp:PlaceHolder id="LocalPlaceHolder" runat="server"></asp:PlaceHolder>
在代码隐藏中,我们将省略按钮事件处理程序,而是实现这个从标记中调用的函数。
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);
}
}
项目
该项目使用 Visual Studio 2005 和 ASP.NET 2.0 以 C# 编写。
结论
正如我在第一篇文章中所述,将**所有**表示移到代码隐藏,只在标记中留下一个占位符的想法有点极端,可能不适合所有项目。但是,这是一个重要的概念需要理解。本文展示了如何扩展基本的 .NET 类以简化动态构建页面。即使您不完全采用这种技术,学习增强基本的 .NET 类并学习在代码隐藏中实现更好的类设计也是改进 ASP.NET 开发的重要工具。
历史
- 2008 年 5 月 6 日:首次发布