65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.38/5 (14投票s)

2008年5月6日

CPOL

6分钟阅读

viewsIcon

48537

downloadIcon

1034

本文介绍了一种在代码隐藏中动态构建整个 ASP.NET 网页内容的技术。

引言

本文是我上一篇文章《在代码隐藏中动态构建 ASP.NET 页面》的续篇。上一篇文章介绍了概念并包含了一个非常基础的示例。本文展示了如何实现一个更复杂的示例项目——从表单收集数据并在页面上将其显示回来,如下图所示。

DynamicFormProject

在我之前的文章中,我还强调了构建自己的类库以提高效率的重要性。这个示例提供了一个更丰富的类库——尽管仍然只是您可以在自己的项目中开发的很小一部分。

这里有大量的代码,项目中还有更多。您可以直接使用这些类,或者借鉴其中的想法进行修改。这些类是根据我工作的具体项目随时间演变而来的。我不认为它们会完美地适用于您的项目。更重要的是理解概念和可能性。

本项目使用了上一篇文章中的类。它添加了一些类,使构建表单(即用户输入数据的实际表单,而不是 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 的属性,原因有两个:

  1. 我经常重载 getter 或 setter 并传递不同的参数。
  2. 我希望我的所有 GetSet 函数都出现在 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());
    }
}

请注意,我使用了几个其他类 - StateComboBoxCountryComboBox。这些类都包含在项目中。

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>

代码隐藏

这是表单的代码隐藏。我添加了 ErrorLabelContactInfoPanel、表单本身、一个按钮以及一些其他美学元素。按钮有一个事件处理程序,该处理程序从表单读取数据并将其显示在面板上。我喜欢这种方法的一点是异常在 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 日:首次发布
© . All rights reserved.