Skip to content

8. ASP 服务器组件 - 2

8.1. 简介

我们将继续研究用户界面,重点探讨:

  • 数据验证组件
  • 如何将数据绑定到服务器组件
  • HTML 服务器组件

8.2. 所有数据验证组件

8.2.1. 简介

在基于表单的应用程序中,验证输入数据的有效性至关重要。在税费计算示例中,我们使用了以下表单:

Image

收到此表单的值后,我们必须验证所输入数据的有效性:子女数量和工资必须是正整数或零。如果不符合此条件,则将表单按输入状态原样返回给客户端,并附带错误消息。我们使用两个视图来处理此场景,一个用于上述表单,另一个用于显示错误:

Image

ASP.NET 提供了一组称为“验证组件”的组件,可用于检查以下内容:

组件
作用
RequiredFieldValidator
检查字段是否为空
CompareValidator
检查两个值是否相互匹配
范围验证器
检查值是否在两个限制之间
正则表达式验证器
检查字段是否符合正则表达式
CustomValidator
允许开发人员定义自己的验证规则——该组件可替代所有其他组件
ValidationSummary
允许您将前几个控件生成的错误消息汇总到页面上的单一位置

接下来我们将介绍这些组件。

8.2.2. RequiredFieldValidator

我们正在构建以下页面 [requiredfieldvalidator1.aspx]:

编号
name
类型
属性
角色
1
txtName
文本框
EnableViewState=true
输入字段
2
必填字段验证器1
必填字段验证器
EnableViewState=false
启用客户端脚本=true
ErrorMessage=[name] 字段为必填项
验证组件
3
btnSubmit
按钮
EnableViewState=false
ValidationReasons=true
[submit] 按钮

[RequiredFieldValidator1] 字段用于在 [txtName] 字段为空或包含一串空格时显示错误消息。其属性如下:

ControlToValidate
该组件必须对其值进行验证的字段。组件的“值”指什么?即对应 HTML 标签的 [value] 属性的值。对于 [TextBox],这是输入字段的内容;对于 [DropDownList],则是所选项的值。
EnableClientScript
布尔值 - 当为 true 时,表示前一个字段的内容也必须在客户端进行验证。在此情况下,只有当表单不包含任何错误时,浏览器才会提交表单。不过,如果客户端不是浏览器(例如),服务器端也会执行验证检查。
ErrorMessage
检测到错误时组件必须显示的错误消息

只有当触发页面 [POST] 的按钮或链接具有 [CausesValidation=true] 属性时,才会执行页面上的数据验证。此属性的默认值为 [true]。

现在运行此应用程序。首先,我们将使用 [Mozilla] 浏览器:

Image

[Mozilla] 接收到的 HTML 代码如下:

<html>
<head>
</head>
<body>
    <form name="_ctl0" method="post" action="requiredfieldvalidator1.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNDI1MDc1NTU1Ozs+SGtdZvVxefDCDxnsqbDnqCaROsk=" />

        <p>
            Demande du dossier de candidature au DESS 
        </p>
        <fieldset>
            <legend>[--Identité--]</legend>

            <table>
                <tbody>
                    <tr>
                        <td>
                            Nom*</td>
                        <td>
                            <input name="txtNom" type="text" id="txtNom" />
                            &nbsp;
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <input type="submit" name="btnEnvoyer" value="Envoyer" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="btnEnvoyer" />
        </p>
    </form>

</body>
</html>

我们可以看到,[btnEnvoyer] 按钮通过其 [onclick] 属性与一个 JavaScript 函数相关联。我们还可以看到,该页面中不包含任何 [JavaScript] 代码。测试 [typeof(Page_ClientValidate) == 'function'] 将失败,且 JavaScript 函数不会被调用。随后表单将提交至服务器,由服务器执行验证检查。 让我们不输入姓名直接点击 [Submit] 按钮。服务器的响应如下:

Image

发生了什么?表单已提交至服务器。服务器执行了页面上所有验证组件的代码。此处仅有一个验证组件。如果至少有一个验证组件检测到错误,服务器将返回表单的原始输入内容(实际上,对于 [EnableViewState=true] 的组件,还会包含每个检测到错误的验证组件的错误消息)。 该消息即为验证组件的 [ErrorMessage] 属性。这就是我们在上面看到的机制。如果我们在 [name] 字段中输入内容,提交表单时错误消息将不再显示:

Image

现在,让我们使用 Internet Explorer 访问 URL [http://localhost/requiredfieldvalidator1.aspx]。我们将看到以下页面:

Image

IE 接收到的 HTML 代码如下:

<html>
<head>
</head>
<body>
    <form name="_ctl0" method="post" action="requiredfieldvalidator1.aspx" language="javascript" onsubmit="ValidatorOnSubmit();" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNDI1MDc1NTU1Ozs+SGtdZvVxefDCDxnsqbDnqCaROsk=" />

<script language="javascript" src="/aspnet_client/system_web/1_1_4322/WebUIValidation.js"></script>


        <p>
            Demande du dossier de candidature au DESS 
        </p>
        <fieldset>
            <legend>[--Identité--]</legend>
            <table>
                <tbody>
                    <tr>
                        <td>
                            Nom*</td>
                        <td>
                            <input name="txtNom" type="text" id="txtNom" />
                            <span id="RequiredFieldValidator1" controltovalidate="txtNom" errormessage="Le champ [nom] est obligatoire" evaluationfunction="RequiredFieldValidatorEvaluateIsValid" initialvalue="" style="color:Red;visibility:hidden;">Le champ [nom] est obligatoire</span>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <input type="submit" name="btnEnvoyer" value="Envoyer" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="btnEnvoyer" />
        </p>

<script language="javascript">
<!--
    var Page_Validators =  new Array(document.all["RequiredFieldValidator1"]);
        // -->
</script>


<script language="javascript">
<!--
var Page_ValidationActive = false;
if (typeof(clientInformation) != "undefined" && clientInformation.appName.indexOf("Explorer") != -1) {
    if (typeof(Page_ValidationVer) == "undefined")
        alert("Impossible de trouver la bibliothèque de scripts /aspnet_client/system_web/1_1_4322/WebUIValidation.js. Essayez de placer ce fichier manuellement ou effectuez une réinstallation en exécutant 'aspnet_regiis -c'.");
    else if (Page_ValidationVer != "125")
        alert("Cette page utilise une version incorrecte de WebUIValidation.js. La page requiert la version 125. La bibliothèque de scripts est " + Page_ValidationVer + ".");
    else
        ValidatorOnLoad();
}

function ValidatorOnSubmit() {
    if (Page_ValidationActive) {
        ValidatorCommonOnSubmit();
    }
}
// -->
</script>


        </form>
</body>
</html>

我们可以看到,这段代码比 [Mozilla] 收到的那段要长得多。我们就不深入探讨细节了。这种差异源于服务器在发送的 HTML 文档中包含了 JavaScript 函数。既然两个浏览器请求的 URL 相同,为什么会有两段不同的 HTML 代码呢?我们之前已经提到,ASP.NET 技术会导致服务器根据客户端的具体环境来调整发送给客户端的 HTML 文档。 市场上的浏览器种类繁多,且功能各异。 例如,Microsoft 和 Netscape 浏览器在处理接收到的文档时,所采用的对象模型并不相同。因此,用于在客户端操作该文档的 JavaScript 代码在两款浏览器中也存在差异。同样地,浏览器厂商不断发布新版浏览器,持续提升其功能。因此,IE5 能解析的 HTML 文档,IE3 可能无法识别。上述情况正是服务器端为客户端进行适配的一个实例。 出于本文未深入探讨的原因,Web 服务器判定客户端 [Mozilla] 不具备处理验证 JavaScript 代码的能力。因此,发送给它的 HTML 文档中未包含该代码,验证操作在服务器端进行。对于 [IE6],如我们所见,该 JavaScript 代码被包含在发送的 HTML 文档中。为了观察其实际效果,让我们尝试以下实验:

  • 停止 Web 服务器
  • 在不填写 [Name] 字段的情况下点击 [Submit] 按钮

我们将看到以下新页面:

Image

确实是浏览器显示了错误信息。这是因为服务器已被停止。我们可以通过在 [name] 字段中输入内容并提交来验证这一点。这次,响应如下:

Image

发生了什么?浏览器执行了 JavaScript 验证代码。该代码判定页面有效。随后,浏览器将表单提交给了已停止的 Web 服务器。浏览器意识到这一点,并显示了上方的页面。如果我们重启服务器,一切将恢复正常。

我们能从这个例子中学到什么?

  • 验证组件概念的价值,它允许将任何格式错误的表单发回客户端
  • [VIEWSTATE] 的价值,它能确保表单以用户输入时的原样返回
  • 服务器适应客户端的能力。客户端通过发送给服务器的 HTTP 标头 [User-Agent:] 进行身份识别。因此,并非由服务器“猜测”其正在处理的对象是谁。这种适应性对开发人员极具价值,因为他们无需担心应用程序所面对的客户端类型。

8.2.3. CompareValidator

我们正在构建以下页面 [comparevalidator1.aspx]:

Image

编号
姓名
类型
属性
角色
1
cmbChoix1
下拉列表
EnableViewState=true
下拉列表
2
cmbChoix2
下拉列表
EnableViewState=true
下拉列表
3
CompareValidator1
CompareValidator
EnableViewState=false
启用客户端脚本=true
ErrorMessage=选项 1 无效
ValueToCompare=?
运算符=不等于
Type=string
验证组件
4
CompareValidator2
CompareValidator
启用视图状态=false
启用客户端脚本=true
ErrorMessage=选项 2 无效
待比较控件=?
运算符=不等于
Type=string
验证组件
5
btnSend
按钮
EnableViewState=false
ValidationReasons=true
[提交] 按钮

[CompareValidator] 组件用于比较两个值。可用的运算符包括 [Equal、NotEqual、LessThan、LessThanEqual、GreaterThan、GreaterThanEqual]。 第一个值由 [ControlToValidate] 属性设定,第二个值若需与常量比较则由 [ValueToCompare] 设定,若需与另一个组件的值比较则由 [ControlToCompare] 设定。[CompareValidator] 组件的重要属性如下:

ControlToValidate
必须由该组件验证其内容的字段
EnableClientScript
布尔值 - 设为 true 时,表示前面的字段内容也必须在客户端进行验证
ErrorMessage
如果检测到错误,组件应显示的错误消息
ValueToCompare
[ControlToValidate] 字段的值必须与之进行比较的值
ControlToCompare
[ControlToValidate] 字段的值必须与其进行比较的组件
运算符
两个值之间的比较运算符
类型
待比较值的类型

此页面的 HTML 代码如下:

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Demande du dossier de candidature au DESS 
        </p>
        <fieldset>
            <legend>[--Options du DESS pour lesquelles vous candidatez--]</legend>
            <table>
                <tbody>
                    <tr>
                        <td>
                            Option1*</td>
                        <td>
                            Option2</td>
                    </tr>
                    <tr>
                        <td>
                            <asp:DropDownList id="cmbChoix1" runat="server">
                                <asp:ListItem Value="?" Selected="True">?</asp:ListItem>
                                <asp:ListItem Value="Automatique">Automatique</asp:ListItem>
                                <asp:ListItem Value="Informatique">Informatique</asp:ListItem>
                            </asp:DropDownList>
                        </td>
                        <td>
                            <asp:DropDownList id="cmbChoix2" runat="server">
                                <asp:ListItem Value="?">?</asp:ListItem>
                                <asp:ListItem Value="Automatique">Automatique</asp:ListItem>
                                <asp:ListItem Value="Informatique">Informatique</asp:ListItem>
                            </asp:DropDownList>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <asp:CompareValidator id="CompareValidator1" runat="server" ErrorMessage="Choix 1 invalide" ControlToValidate="cmbChoix1" ValueToCompare="?" Operator="NotEqual"></asp:CompareValidator>
                        </td>
                        <td>
                            <asp:CompareValidator id="CompareValidator2" runat="server" ErrorMessage="Choix 2 invalide" ControlToValidate="cmbChoix2" Operator="NotEqual" ControlToCompare="cmbChoix1"></asp:CompareValidator>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
        </p>
    </form>
</body>
</html>

该页面的验证约束如下:

  • [cmbChoix1] 中选定的值必须与字符串“?”不同。这意味着 [CompareValidator1] 组件具有以下属性:Operator=NotEqual, ValueToCompare=?, Type=string
  • [cmbChoix2] 中选择的值必须与 [cmbChoix1] 中选择的值不同。这意味着 [CompareValidator2] 组件具有以下属性:运算符=不等于,比较控件=cmbChoix1,类型=字符串

我们运行此应用程序。以下是在客户端与服务器交互过程中收到的页面示例:

Image

如果通过 Internet Explorer 6 请求同一页面,其中包含的 JavaScript 代码会导致行为略有不同。用户在其中一个下拉列表中更改值时,系统会立即报告任何错误。这提升了用户体验。

8.2.4. CustomValidator 、RangeValidator

我们正在构建以下页面 [customvalidator1.aspx]:

Image

编号
姓名
类型
属性
角色
1
cmbDiplomas
下拉列表
EnableViewState=true
下拉列表
2
CompareValidator2
CompareValidator
启用视图状态=false
启用客户端脚本=true
ErrorMessage=文凭无效
运算符=不等于
ValueToCompare=?
Type=String
控件 [1] 的验证组件
3
txtOtherDegree
文本框
EnableViewState=false
输入字段
4
CustomValidator1
CustomValidator
启用视图状态=false
启用客户端脚本=true
ErrorMessage=学位类型指定无效
客户端验证函数=chkOtherDegree
用于字段 [3] 的验证组件
5
txtAnDiploma
文本框
EnableViewState=false
输入字段
6
RangeValidator1
RangeValidator
EnableViewState=false
启用客户端脚本=true
ErrorMessage=毕业年份必须在 [1990,2004] 范围内
MinValue=1990
MaxValue=2004
Type=Integer
ControlToValidate=txtAnDiplome
字段验证组件 [5]
7
必填字段验证器2
必填字段验证器
启用视图状态=false
启用客户端脚本=true
ErrorMessage=请填写毕业年份
待验证控件=txtAnDiplome
字段验证组件 [5]
8
btnSubmit
按钮
EnableViewState=false
ValidationReasons=true
[提交] 按钮

[RangeValidator] 字段用于验证控件的值是否在 [MinValue] 和 [MaxValue] 两个限制之间。其属性如下:

ControlToValidate
需要由该组件验证其值的字段
EnableClientScript
布尔值 - 设为 true 时,表示前一个字段的内容也必须在客户端进行验证
ErrorMessage
如果检测到错误,组件应显示的错误消息
MinValue
待验证字段的最小值
MaxValue
待检查字段的最大值
Type
待验证字段值的类型

[CustomValidator] 字段允许您执行 ASP.NET 提供的验证组件无法完成的验证。此验证由开发人员编写的函数执行。该函数在服务器端执行。由于验证也可以在客户端进行,因此开发人员可能需要编写一个 JavaScript 函数并将其包含在 HTML 文档中。其属性如下:

ControlToValidate
必须由该组件验证其值的字段
EnableClientScript
布尔值 - 设为 true 时,表示前一个字段的内容也必须在客户端进行验证
ErrorMessage
如果检测到错误,组件应显示的错误消息
ClientValidationFunction
要在客户端执行的函数

页面模板代码如下:

<%@ Page Language="VB" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p align="left">
            Demande du dossier de candidature au DESS 
        </p>
        <fieldset>
            <legend>[--Votre dernier diplôme--]</legend>
            <table>
                <tbody>
                    <tr>
                        <td>
                            Diplôme*</td>
                        <td>
                            <asp:DropDownList id="cmbDiplomes" runat="server">
                                <asp:ListItem Value="?">?</asp:ListItem>
                                <asp:ListItem Value="[Autre]">[Autre]</asp:ListItem>
                                <asp:ListItem Value="Ma&#238;trise">Ma&#238;trise</asp:ListItem>
                                <asp:ListItem Value="DESS">DESS</asp:ListItem>
                                <asp:ListItem Value="DEA">DEA</asp:ListItem>
                            </asp:DropDownList>
                        </td>
                        <td>
                            Si [Autre], précisez</td>
                        <td>
                            <asp:TextBox id="txtAutreDiplome" runat="server" EnableViewState="False"></asp:TextBox>
                        </td>
                    </tr>
                    <tr>
                        <td>
                        </td>
                        <td>
                            <p>
                                <asp:CompareValidator id="CompareValidator2" runat="server" ErrorMessage="Diplôme invalide" ControlToValidate="cmbDiplomes" ValueToCompare="?" Operator="NotEqual" ></asp:CompareValidator>
                            </p>
                        </td>
                        <td>
                        </td>
                        <td>
                            <asp:CustomValidator id="CustomValidator1" runat="server" ErrorMessage="Précision diplôme invalide" OnServerValidate="CustomValidator1_ServerValidate_1" EnableViewState="False" ClientValidationFunction="chckAutreDiplome"></asp:CustomValidator>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            Année d'obtention*</td>
                        <td colspan="3">
                            <asp:TextBox id="txtAnDiplome" runat="server" Columns="4"></asp:TextBox>
                            <asp:RangeValidator id="RangeValidator1" runat="server" ErrorMessage="Année diplôme doit être dans l'intervalle [1990,2004]" ControlToValidate="txtAnDiplome" MinimumValue="1990" MaximumValue="2004" Type="Integer"></asp:RangeValidator>
                            <asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" ErrorMessage="Année diplôme requise" ControlToValidate="txtAnDiplome"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
        </p>
    </form>
</body>
</html>

[CustomValidator] 组件的 [OnServerValidate] 属性允许您指定负责服务器端验证的函数。在上例中,[CustomValidator1] 组件调用了以下 [CustomValidator1_ServerValidate_1] 过程:

<%@ Page Language="VB" %>
<script runat="server">

    Sub CustomValidator1_ServerValidate_1(sender As Object, e As ServerValidateEventArgs)
        ' field [txtAutreDiplome] must be non-empty if [cmbDiplomes]=[other]
        e.isvalid=not (cmbDiplomes.selecteditem.text="[Autre]" and txtAutreDiplome.text.trim="") _
            and not (cmbDiplomes.selecteditem.text<>"[Autre]" and cmbDiplomes.selecteditem.text<>"?" and txtAutreDiplome.text.trim<>"")
    End Sub

</script>
....

与 [CustomValidator] 组件关联的验证函数接收两个参数:

  • sender:触发该事件的对象
  • e:事件。如果验证的数据正确,该过程必须将 [e.IsValid] 属性设置为 true;否则,将其设置为 false。

此处执行以下检查:

  • [cmbDiplomes] 下拉列表的值不能为 [?]。此项由 [CompareValidator2] 组件进行验证
  • 用户需从 [cmbDiplomes] 下拉列表中选择一个学位。如果列表中不存在该学位,用户可从列表中选择 [Other] 选项,随后必须在 [txtAutreDiplome] 输入框中输入其学位名称。若在 [cmbDiplomes] 中选择了 [Other],则 [txtAutreDiplome] 字段不得为空。 如果 [cmbDiplomes] 中既未选择 [Other] 也未选择 [?],则 [txtAutreDiplome] 字段必须为空。这由与 [CustomValidator1] 组件关联的函数进行验证。
  • 学位获得年份必须在 [1900-2004] 范围内。此条件由 [RangeValidator1] 进行验证。但是,如果用户将该字段留空,则不会使用 [RangeValidator1] 的验证函数。 因此,我们在 [ ] 中添加 [RequiredFieldValidator2] 组件以检查字段是否包含内容。这是一条通用规则:如果字段为空,则不检查其内容。唯一会进行检查的情况是该字段关联了 [RequiredFieldValidator] 组件。

以下是在 [Mozilla] 浏览器中的执行示例:

Image

如果使用 [IE6] 浏览器,我们可以为 [CustomValidator1] 组件添加客户端验证函数。为此,该组件具有以下属性:

  • EnableClientScript=true
  • ClientValidationFunction=chkAutreDiplome

[chkAutreDiplome] 函数如下:

<head>
    <meta http-equiv="pragma" content="no-cache" />
    <script language="javascript">
        function chkAutreDiplome(source,args){
            // vérifie la validité du champ txtAutreDiplome
            with(document.frmCandidature){
                diplome=cmbDiplomes.options[cmbDiplomes.selectedIndex].text;
                    args.IsValid= !(diplome=="[Autre]" && txtAutreDiplome.value=="")
                        && ! (diplome!="[Autre]" && diplome!="?" && txtAutreDiplome.value!="");
            }
        }
        </script>
</head>

[chkAutreDiplome] 函数接收与服务器函数 [CustomValidator1_ServerValidate_1] 相同的两个参数:

  • source:触发该事件的对象
  • args:事件。该事件具有 [IsValid] 属性,如果验证的数据正确,函数必须将其设置为 true。如果值为 false,将在验证组件的位置显示相关的错误消息,且表单不会提交至服务器。

8.2.5. 正则表达式验证器

我们正在构建以下页面 [regularexpressionvalidator1.aspx]:

Image

编号
姓名
类型
属性
角色
1
txtMel
文本框
EnableViewState=true
输入字段
2
必填字段验证器3
必填字段验证器
ControlToValidate=txtMel
显示=动态
控件验证组件 [1]
3
正则表达式验证器1
正则表达式验证器
待验证控件=txtMel
显示=动态
控件验证组件 [1]
4
btnSend
按钮
启用视图状态=false
CausesValidation=true
[提交] 按钮

在此,我们需要验证电子邮件地址的格式。我们使用 [RegularExpressionValidator] 组件来实现,该组件允许我们使用正则表达式对字段进行验证。 请记住,正则表达式是一种字符模式。因此,我们将字段内容与该模式进行比对。由于字段为空时不会进行内容检查,我们还需要一个 [RequiredFieldValidator] 组件来验证电子邮件地址是否为空。[RegularExpressionValidator] 类具有与之前介绍过的组件类类似的属性:

ControlToValidate
需要由该组件验证其值的字段
EnableClientScript
布尔值 - 设为 true 时,表示前一个字段的内容也必须在客户端进行验证
ErrorMessage
如果检测到错误,组件应显示的错误消息
正则表达式
用于与 [ControlToValidate] 内容进行比对的正则表达式
显示
显示模式:静态:即使未显示错误消息,该字段也始终存在;动态:仅在存在错误消息时显示该字段;:不显示错误消息。该字段在其他组件中也存在,但此前未被使用。

电子邮件地址的正则表达式模式可以如下所示:\s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*

电子邮件地址的格式为 [champ1.champ2....@champA.champB...]。@符号前必须至少有一个字段,@符号后必须至少有两个字段。这些字段由字母数字字符和 - 符号组成。 字母数字字符由符号 \w 表示。序列 [\w-] 表示字符 \w 或字符 -。序列 S 后的 + 号表示该序列可以重复 1 次或多次;* 号表示可以重复 0 次或多次;? 号表示可以重复 0 次或 1 次。

电子邮件地址中的一个字段对应于正则表达式 [\w-]+。@符号前必须至少有一个字段,其后必须至少有两个字段(由 . 符号分隔)这一事实,对应于正则表达式:[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+。我们可以允许用户在地址前后添加空格。这些空格将在处理过程中被移除。 一串空格(可能为空)由正则表达式 \s* 表示。因此,电子邮件地址的正则表达式为:\s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*。

页面布局代码如下:

<html>
<head>
</head>
<body>
    <form runat="server">
        <p align="left">
            Demande du dossier de candidature au DESS
        </p>
        <fieldset>
            <legend>[--Votre adresse électronique où sera envoyé le dossier de candidature--]</legend>
            <table>
                <tbody>
                    <tr>
                        <td>
                            <asp:TextBox id="txtMel" runat="server" Columns="60"></asp:TextBox>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <p>
                                <asp:RequiredFieldValidator id="RequiredFieldValidator3" runat="server" Display="Dynamic" ControlToValidate="txtMel" ErrorMessage="Votre adresse électronique est requise"></asp:RequiredFieldValidator>
                            </p>
                            <p>
                                <asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" Display="Dynamic" ControlToValidate="txtMel" ErrorMessage="Votre adresse électronique n'a pas un format valide" ValidationExpression="\s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*"></asp:RegularExpressionValidator>
                            </p>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
        </p>
    </form>
</body>
</html>

8.2.6. ValidationSummary

出于美观考虑,您可能希望将错误消息集中显示在同一位置,如下例 [summaryvalidator1.aspx] 所示,其中我们将所有先前示例整合到了一页中:

Image

所有验证控件都具有以下属性:

  • [Display=Dynamic],确保当控件的错误消息为空时,控件不会占用页面空间
  • [EnableClientScript=false],用于禁用所有客户端验证
  • [Text=*]。此属性将作为错误时的显示消息,而 [ErrorMessage] 属性的内容则由下文展示的 [ValidationSummary] 控件显示。

这组控件被放置在一个名为 [vueFormulaire] 的 [Panel] 组件中。在另一个名为 [vueErreurs] 的 [Panel] 组件中,我们放置了一个 [ValidationSummary] 控件:

Image

编号
姓名
类型
属性
角色
1
验证摘要1
验证摘要
标题文本=发生以下错误,启用客户端脚本=false,显示摘要=true
显示页面上所有验证控件的错误
2
lnkErrorsToForm
链接按钮
ValidationCauses=false
链接回表单

对于 [lnkErrorsToForm] 链接,无需启用验证,因为该链接不会提交任何需要检查的值。

当所有数据均有效时,将向用户显示以下 [info] 视图:

Image

1

编号
姓名
类型
属性
角色
1
lblInfo
标签
 
给用户的提示信息

此页面的 HTML 代码如下:

<html>
<head>
</head>
<body>
    <form runat="server">
        <p align="left">
            Demande du dossier de candidature au DESS 
        </p>
        <p>
            <hr />
            <asp:panel id="vueErreurs" runat="server">
                <p align="left">
                    <asp:ValidationSummary id="ValidationSummary1" runat="server" ShowMessageBox="True" BorderColor="#C04000" BorderWidth="1px" BackColor="#FFFFC0" HeaderText="Les erreurs suivantes se sont produites"></asp:ValidationSummary>
                </p>
                <p>
                    <asp:LinkButton id="lnkErreursToFormulaire" onclick="lnkErreursToFormulaire_Click" runat="server" CausesValidation="False">Retour au formulaire</asp:LinkButton>
                </p>
            </asp:panel>
            <asp:panel id="vueFormulaire" runat="server">
....
                    <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
                </p>
            </asp:panel>
        <asp:panel id="vueInfos" runat="server">
            <asp:Label id="lblInfo" runat="server"></asp:Label>
        </asp:panel>
    </form>
</body>
</html>

以下是一些获得的结果示例。我们在未输入任何值的情况下提交[表单]视图:

Image

[提交]按钮返回以下响应:

Image

[errors]视图已显示。如果我们使用链接返回表单,会发现表单处于以下状态:

Image

这是 [form] 视图。请注意错误数据旁边的 [*] 字符。这是验证控件中显示的 [Text] 字段。如果我们正确填写字段,将获得 [info] 视图:

Image

页面控件代码如下:

<%@ Page Language="VB" %>
<script runat="server">

    ' procedure executed when the page is loaded
    Sub page_Load(sender As Object, e As EventArgs)
        ' on the 1st request, we present the [form] view
        if not ispostback then
            afficheVues(true,false,false)
        end if
    end sub

    sub afficheVues(byval formulaireVisible as boolean, _
        erreursVisible as boolean, infosVisible as boolean)
        ' set of views
        vueFormulaire.visible=formulaireVisible
        vueErreurs.visible=erreursVisible
        vueInfos.visible=infosVisible
    end sub

    Sub CustomValidator1_ServerValidate(sender As Object, e As ServerValidateEventArgs)
...
    End Sub

    Sub CustomValidator2_ServerValidate(sender As Object, e As ServerValidateEventArgs)
...
    End Sub

    Sub CustomValidator1_ServerValidate_1(sender As Object, e As ServerValidateEventArgs)
...
    End Sub

    Sub lnkErreursToFormulaire_Click(sender As Object, e As EventArgs)
        ' displays the form view
        afficheVues(true,false,false)
        ' redo validity checks
        Page.validate
    End Sub

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
        ' is the page valid?
        if not Page.IsValid then
            ' the [errors] view is displayed
            afficheVues(false,true,false)
        else
            ' otherwise the view [infos]
            lblInfo.Text="Le dossier de candidature au DESS IAIE a été envoyé à l'adresse ["+ _
                        txtMel.Text + "]. Nous vous en souhaitons bonne réception.<br><br>Le secrétariat du DESS."
            afficheVues(false,false,true)
        end if
    end sub

</script>
<html>
...
</html>
  • 在 [Page_Load] 过程(该过程在每次客户端请求时运行)中,我们显示 [form] 视图,同时隐藏其他视图。此操作仅在首次请求时执行。该过程调用一个辅助过程 [displayViews],并向其传递三个布尔值,以确定是否显示这三个视图。
  • 当调用 [btnEnvoyer_Click] 过程时,数据验证已经完成。[btnEnvoyer] 按钮具有 [CausesValidation=true] 属性,该属性会触发此数据验证。所有验证控件均已执行,且其 [IsValid] 属性已设置。该属性指示控件验证的数据是否有效。 此外,页面本身的 [IsValid] 属性也已设置。只有当页面上所有验证控件的 [IsValid] 属性均为 [true] 时,该属性才为 [true]。因此,[btnEnvoyer_Click] 过程首先会检查页面是否有效。 如果页面无效,则显示 [errors] 视图。该视图包含 [ValidationSummary] 控件,其中列出了所有控件的 [ErrorMessage] 属性(参见上图)。如果页面有效,则显示 [info] 视图并显示一条信息提示。
  • [lnkErrorsToForm_Click] 过程负责以已验证的状态显示 [form] 视图。由于 [form] 视图中的所有输入字段都具有 [EnableViewState=true] 属性,因此它们的状态会自动重新生成。值得注意的是,验证组件的状态并未被恢复。 人们可能会预期,在返回 [errors] 视图时,无效控件会显示其 [Text] 字段。但实际情况并非如此。 因此,我们通过调用页面的 [Page.Validate] 方法强制执行了数据验证。此操作必须在 [formView] 面板可见后进行。因此总共进行了两次验证。在实际应用中应避免这种情况。在此示例中,我们借此机会介绍了关于页面验证的新概念。

8.3. 列表控件与数据绑定

我们已介绍的若干服务器组件允许您显示值列表(如 DropDownList、ListBox)。此外,还有一些尚未涉及的组件可让您在 HTML 表格中显示多个值列表。 对于所有这些组件,均可通过编程方式将列表中的值逐一与对应组件关联。此外,还可将更复杂的对象(如 [Array]、[ArrayList]、[DataSet]、[HashTable] 等类型的对象)与这些组件关联,从而简化数据与组件的关联代码。这种关联称为数据绑定。

所有从 [ListControl] 类派生的组件均可与数据列表关联。这些组件包括 [DropDownList]、[ListBox]、[CheckButtonList] 和 [RadioButtonList]。每个组件均可绑定到数据源。 数据源可以采用多种形式:[Array]、[ArrayList]、[DataTable]、[DataSet]、[HashTable]……通常是实现了 IEnumerable、ICollection 或 IListSource 接口之一的对象。本文仅介绍其中几种。[DataSet] 对象是关系型数据库的表示形式,因此它是一组通过关系链接的表。 [DataTable] 对象即表示这样的表。数据源会设置 [ListControl] 对象中每个 [Item] 的 [Text] 和 [Value] 属性。若 T 为 [Text] 的值,V 为 [Value] 的值,则为 [ListControl] 的每个元素生成的 HTML 标签如下:

下拉列表、列表框
<option value="V">T</option>
复选框列表
<input type="checkbox" value="V">T
单选按钮列表
<input type="radio" value="V">T

[ListControl] 组件通过以下属性与数据源相关联:

DataSource
数据源 [Array]、[ArrayList]、[DataTable]、[DataSet]、[HashTable]、...
DataMember
如果数据源是 [DataSet],则表示将用作数据源的表的名称。此时,实际的数据源即为该表。
DataTextField
如果数据源是一个表([DataTable]、[DataSet]),则表示将为 [ListControl] 项的 [Text] 字段提供值的表列名称
DataValueField
如果数据源是表([DataTable]、[DataSet]),则表示将为 [ListControl] 元素的 [Value] 字段提供值的表列名称

将 [ListControl] 组件绑定到数据源并不会使用数据源中的值初始化该组件。这需要通过 [ListControl].DataBind 操作来实现。

根据数据源的性质,将其绑定到 [ListControl] 组件的方式会有所不同:

数组 A
[ListControl].DataSource=A
[ListControl] 元素的 [Text] 和 [Value] 字段将取 A 中元素的值
数组列表 AL
[ListControl].DataSource=AL
[ListControl] 元素的 [Text] 和 [Value] 字段将取 AL 中元素的值
DataTable DT
[ListControl].DataSource = DT, [ListControl].DataTextField = "col1", [ListControl].DataValueField = "col2"
其中 col1 和 col2 是 DT 表中的两列。[ListControl] 项的 [Text] 和 [Value] 字段将分别显示 DT 表中 col1 和 col2 列的值
DataSet DS
[ListControl].DataSource=DS, [ListControl].DataSource="table" 其中 "table" 是 DS 中某张表的名称。
[ListControl].DataTextField="col1", [ListControl].DatavalueField ="col2" 其中 col1 和 col2 是 "table" 表中的两列。[ListControl] 元素的 [Text] 和 [Value] 字段将分别显示 "table" 表中 col1 和 col2 列的值
哈希表 HT
[ListControl].DataSource = HT, [ListControl].DataTextField = "key", [ListControl].DataValueField = "value",其中 [key] 和 [value] 分别是 HT 的键和值。

我们将此信息应用到以下示例 [databind1.aspx] 中:

这里有五个数据绑定,每个绑定包含四个控件,类型分别为 [DropDownList]、[ListBox]、[CheckBoxList] 和 [RadioButtonList]。这五个绑定在数据源方面有所不同:

绑定
数据源
1
数组
2
数组列表
3
DataTable
4
数据集
5
哈希表

8.3.1. 控件呈现代码

绑定 1 中控件的呈现代码如下:

<td>
<asp:DropDownList id="DropDownList1" runat="server"></asp:DropDownList>
</td>
<td>
<asp:ListBox id="ListBox1" runat="server" SelectionMode="Multiple"></asp:ListBox>
</td>
<td>
<asp:CheckBoxList id="CheckBoxList1" runat="server"></asp:CheckBoxList>
</td>
<td>
<asp:RadioButtonList id="RadioButtonList1" runat="server"></asp:RadioButtonList>
</td>

绑定 2 至 5 的绑定配置完全相同,仅绑定编号不同。

8.3.2. 绑定到数组数据源

上述四个 [ListBox] 控件的绑定在控件代码的 [Page_Load] 过程 中如下所示:

' global data
dim textes() as string={"un","deux","trois","quatre"}
dim valeurs() as string={"1","2","3","4"}
dim myDataListe as new ArrayList
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
dim myHashTable as new HashTable

' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' create the data sources to be linked to the components
      createDataSources
      ' link to an array [Array]
      bindToArray
      ' link to a list [ArrayList]
      bindToArrayList
      ' link to a table [DataTable]
      bindToDataTable
      ' link to a data group [DataSet]
      bindToDataSet
      ' link to a dictionary [HashTable]
      bindToHashTable
    end if
End Sub

sub createDataSources
  ' creates data sources to be linked to components
  ' arraylist
  dim i as integer
  for i=0 to textes.length-1
    myDataListe.add(textes(i))
  next
  ' datatable
  ' we define its two columns
  myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
  myDataTable.Columns.Add("texte",Type.GetType("System.String"))
  ' fill the table
  dim ligne as DataRow
  for i=0 to textes.length-1
    ligne=myDataTable.NewRow
    ligne("id")=i
    ligne("texte")=textes(i)
    myDataTable.Rows.Add(ligne)
  next
  ' dataset - a single table
  myDataSet.Tables.Add(myDataTable)
  ' hashtable
  for i=0 to textes.length-1
    myHashTable.add(valeurs(i),textes(i))
  next
end sub

' panel connection
sub bindToArray
        ' association with components
        with DropDownList1
            .DataSource=textes
            .DataBind
        end with
        with ListBox1
            .DataSource=textes
            .DataBind
        end with
        with CheckBoxList1
            .DataSource=textes
            .DataBind
        end with
        with RadioButtonList1
            .DataSource=textes
            .DataBind
        end with
        ' item selection
        ListBox1.Items(1).Selected=true
        ListBox1.Items(3).Selected=true
        CheckBoxList1.Items(0).Selected=true
        CheckBoxList1.Items(3).Selected=true
        DropDownList1.SelectedIndex=2
        RadioButtonList1.SelectedIndex=1
end sub

sub bindToArrayList
....
end sub

sub bindToDataTable
...
end sub

sub bindToDataSet
...
end sub

控件仅在首次请求时在此处与数据绑定。此后,控件将通过 [VIEWSTATE] 机制保留其元素。绑定操作在 [bindToArray] 过程 中执行。由于数据源的类型为 [Array],因此仅初始化 [ListControl] 组件的 [DataSource] 字段。 通过 [ListControl].DataBind 方法,控件会从关联的数据源中获取值并填充。只有此时,[ListControl] 对象才拥有元素。随后,您可以选择其中部分元素。

8.3.3. 绑定到 ArrayList 数据源

数据源 [myDataList] 在 [createDataSources] 过程 中初始化:

' global data
dim textes() as string={"un","deux","trois","quatre"}
dim myDataListe as new ArrayList
..

' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' create the data sources to be linked to the components
      createDataSources
...
    end if
End Sub

sub createDataSources
  ' creates data sources to be linked to components
  ' arraylist
  dim i as integer
  for i=0 to textes.length-1
    myDataListe.add(textes(i))
  next
...
end sub

在控制代码的 [bindToArrayList] 过程 中,绑定 2 中四个 [ListBox] 控件的绑定如下所示:

' liaison arraylist
sub bindToArrayList
        ' l'association aux composants
        with DropDownList2
            .DataSource=myDataListe
            .DataBind
        end with
        with ListBox2
            .DataSource=myDataListe
            .DataBind
        end with
        with CheckBoxList2
            .DataSource=myDataListe
            .DataBind
        end with
        with RadioButtonList2
            .DataSource=myDataListe
            .DataBind
        end with
        ' la sélection des éléments
        ListBox2.Items(1).Selected=true
        ListBox2.Items(3).Selected=true
        CheckBoxList2.Items(0).Selected=true
        CheckBoxList2.Items(3).Selected=true
        DropDownList2.SelectedIndex=2
        RadioButtonList2.SelectedIndex=1
end sub

由于数据源的类型为 [ArrayList],因此仅初始化了 [ListControl] 组件的 [DataSource] 字段。

8.3.4. DataTable 类型的数据源

数据源 [myDataTable] 在 [createDataSources] 过程(

' global data
dim textes() as string={"un","deux","trois","quatre"}
dim myDataTable as new DataTable("table1")
...

' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' create the data sources to be linked to the components
      createDataSources
...
    end if
End Sub

sub createDataSources
  ' creates data sources to be linked to components
...
  ' datatable
  ' we define its two columns
  myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
  myDataTable.Columns.Add("texte",Type.GetType("System.String"))
  ' fill the table
  dim ligne as DataRow
  for i=0 to textes.length-1
    ligne=myDataTable.NewRow
    ligne("id")=i
    ligne("texte")=textes(i)
    myDataTable.Rows.Add(ligne)
  next
...
end sub

我们首先创建一个包含两列的 [DataTable] 对象:[id] 和 [text]。其中 [id] 列将填充 [ListControl] 项的 [Value] 字段,而 [text] 列将填充其 [Text] 字段。包含两列的 [DataTable] 创建方式如下:

  myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
  myDataTable.Columns.Add("texte",Type.GetType("System.String"))

因此,我们创建了一个包含两列的表格:

  • 第一列名为“id”,类型为整数
  • 第二列名为“text”,类型为字符串

现在表格结构已创建完成,我们可以使用以下代码向其中填充数据:

  dim ligne as DataRow
  for i=0 to textes.length-1
    ligne=myDataTable.NewRow
    ligne("id")=i
    ligne("texte")=textes(i)
    myDataTable.Rows.Add(ligne)
  next

[id] 列将包含整数 [0,1,..,n],而 [text] 列将包含 [data] 数组中的值。完成此操作后,[dataList] 表格即被填充。绑定 3 中四个 [ListBox] 控件的绑定在控件代码的 [bindToDataTable] 过程 中按以下方式执行:

sub bindToDataTable
        ' l'association aux composants
        with DropDownList3
            .DataSource=myDataTable
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with ListBox3
            .DataSource=myDataTable
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with CheckBoxList3
            .DataSource=myDataTable
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with RadioButtonList3
            .DataSource=myDataTable
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        ' la sélection des éléments
        ListBox3.Items(1).Selected=true
        ListBox3.Items(3).Selected=true
        CheckBoxList3.Items(0).Selected=true
        CheckBoxList3.Items(3).Selected=true
        DropDownList3.SelectedIndex=2
        RadioButtonList3.SelectedIndex=1
end sub

每个 [ListControl] 组件均通过以下设置与 [myDataTable] 数据源绑定:

            .DataSource= myDataTable
            .DataValueField="id"
            .DataTextField="texte"

[myDataTable] 表是数据源。该表的 [id] 列将填充组件元素的 [Value] 字段,而 [text] 列将填充其 [Text] 字段。

8.3.5. DataSet 类型的数据源

数据源 [myDataSet] 在 [createDataSources] 过程 中初始化:

' global data
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
...


' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' create the data sources to be linked to the components
      createDataSources
...
    end if
End Sub

sub createDataSources
  ' creates data sources to be linked to components
  ' dataset - a single table
  myDataSet.Tables.Add(myDataTable)
...
end sub

一个 [DataSet] 对象代表一组 [DataTable] 表。我们将之前创建的 [myDataTable] 添加到 [DataSet] 中。绑定 4 中四个 [ListBox] 控件的绑定是在控件代码的 [bindToDataSet] 过程 中按以下方式完成的:

sub bindToDataSet
        ' l'association aux composants
        with DropDownList4
            .DataSource=myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with ListBox4
            .DataSource=myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with CheckBoxList4
            .DataSource=myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with RadioButtonList4
            .DataSource=myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        ' la sélection des éléments
        ListBox4.Items(1).Selected=true
        ListBox4.Items(3).Selected=true
        CheckBoxList4.Items(0).Selected=true
        CheckBoxList4.Items(3).Selected=true
        DropDownList4.SelectedIndex=2
        RadioButtonList4.SelectedIndex=1
end sub

每个 [ListControl] 组件与数据源的关联如下:

            .DataSource= myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"

数据集 [myDataSet] 即为数据源。由于它可能包含多个表,因此我们在 [DataMember] 中指定要使用的表名。该表的 [id] 列将填充组件元素的 [Value] 字段,而 [text] 列将填充其 [Text] 字段。

8.3.6. 哈希表数据源

数据源 [myHashTable] 在 [createDataSources] 过程 中初始化:

' global data
dim textes() as string={"un","deux","trois","quatre"}
dim valeurs() as string={"1","2","3","4"}
dim myHashTable as new HashTable
...

' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' create the data sources to be linked to the components
      createDataSources
...
    end if
End Sub

sub createDataSources
  ' creates data sources to be linked to components
...
  ' hashtable
  for i=0 to textes.length-1
    myHashTable.add(valeurs(i),textes(i))
  next
end sub

字典 [myHashTable] 可以视为一个包含两列的表格,分别称为“键”和“值”。 [key] 列代表字典的键,[value] 列代表与其关联的值。在此,[key] 列由 [values] 数组的内容组成,[value] 列由 [texts] 数组的内容组成。此源与控件的绑定是在 [bindToHashTable] 过程 中完成的:

sub bindToHashTable
        ' l'association aux composants
        with DropDownList5
            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"
            .DataBind
        end with
        with ListBox5
            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"
            .DataBind
        end with
        with CheckBoxList5
            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"
            .DataBind
        end with
        with RadioButtonList5
            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"
            .DataBind
        end with
        ' la sélection des éléments
        ListBox5.Items(1).Selected=true
        ListBox5.Items(3).Selected=true
        CheckBoxList5.Items(0).Selected=true
        CheckBoxList5.Items(3).Selected=true
        DropDownList5.SelectedIndex=2
        RadioButtonList5.SelectedIndex=1
end sub

对于每个组件,均使用以下语句建立绑定:

            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"

数据源是字典 [myHashTable]。控件的数值由字典的 [key] 列提供,文本由 [value] 列提供。字典元素按键的顺序插入到控件中,初始顺序是随机的。

8.3.7. 命名空间导入指令

许多命名空间会自动导入到 ASP.NET 页面中。但包含 [DataTable] 和 [DataSet] 类的 "System.Data" 命名空间并非如此。因此,必须显式导入该命名空间。操作方法如下:

<%@ Page Language="VB" %>
<%@ import Namespace="System.Data" %>
<script runat="server">
...
</script>
<html>
...
</html>

8.4. DataGrid 组件与数据绑定

[DataGrid] 组件允许您以表格格式显示数据,但它的功能远不止于简单的显示:

  • 它提供了精确配置表格“视觉呈现”的能力
  • 它允许您更新数据源

[DataGrid] 组件功能强大且结构复杂。我们将分步为您介绍。

8.4.1. 显示 Array、ArrayList、DataTable 或 DataSet 数据源

[DataGrid] 组件支持将 [Array]、[ArrayList]、[DataTable] 和 [DataSet] 类型的数据源以 HTML 表格形式展示。对于这四种数据类型,只需将数据源与 [DataGrid] 组件的 [DataSource] 属性关联即可:

DataSource
数据源 [Array]、[ArrayList]、[DataTable]、[DataSet] 等
DataMember
若数据源为 [DataSet],则表示用作数据源的表的名称。此时实际的数据源即为该表。若此字段留空,则显示 [DataSet] 中的所有表。

现在我们展示 [datagrid1.aspx] 页面,该页面展示了一个关联了四个不同数据源的 [DataGrid]:

Image

该页面包含四个使用 [WebMatrix] 构建的 [DataGrid] 组件,具体如下。我们将组件拖放到 [设计] 选项卡中的相应位置:

Image

随后将绘制一个通用 HTML 表格。[DataGrid] 的属性可在设计时进行定义。这就是我们在此对其格式属性所做的操作。为此,我们选择要配置的 [DataGrid]。其属性将显示在右下角的窗口中:

Image

我们将使用上面的两个链接。[属性生成器] 链接可访问 [DataGrid] 的主要属性:

Image

我们取消勾选四个 [DataGrid] 组件的 [显示标题] 选项,并保存页面。另一个链接 [自动格式] 允许您为将要显示的 HTML 表格选择多种样式:

Image

我们为 [DataGrid] #i 选择 [color i]。这些设计选择会反映在页面的呈现代码中:

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Liaison de données avec un DataGrid 
        </p>
        <hr />
        <p>
            <table>
                <tbody>
                </tbody>
            </table>
            <table border="1">
                <tbody>
                    <tr>
                        <td>
                            Array</td>
                        <td>
                            ArrayList</td>
                        <td>
                            DataTable</td>
                        <td>
                            DataSet</td>
                    </tr>
                    <tr>
                        <td>
                            <asp:DataGrid id="DataGrid1" runat="server" ShowHeader="False" CellPadding="4" BackColor="White" BorderColor="#CC9966" BorderWidth="1px" BorderStyle="None">
                                <FooterStyle forecolor="#330099" backcolor="#FFFFCC"></FooterStyle>
                                <HeaderStyle font-bold="True" forecolor="#FFFFCC" backcolor="#990000"></HeaderStyle>
                                <PagerStyle horizontalalign="Center" forecolor="#330099" backcolor="#FFFFCC"></PagerStyle>
                                <SelectedItemStyle font-bold="True" forecolor="#663399" backcolor="#FFCC66"></SelectedItemStyle>
                                <ItemStyle forecolor="#330099" backcolor="White"></ItemStyle>
                            </asp:DataGrid>
                        </td>
                        <td>
                            <asp:DataGrid id="DataGrid2" runat="server" ShowHeader="False" CellPadding="4" BackColor="White" BorderColor="#3366CC" BorderWidth="1px" BorderStyle="None">
                                <FooterStyle forecolor="#003399" backcolor="#99CCCC"></FooterStyle>
                                <HeaderStyle font-bold="True" forecolor="#CCCCFF" backcolor="#003399"></HeaderStyle>
                                <PagerStyle horizontalalign="Left" forecolor="#003399" backcolor="#99CCCC" mode="NumericPages"></PagerStyle>
                                <SelectedItemStyle font-bold="True" forecolor="#CCFF99" backcolor="#009999"></SelectedItemStyle>
                                <ItemStyle forecolor="#003399" backcolor="White"></ItemStyle>
                            </asp:DataGrid>
                        </td>
                        <td>
                            <asp:DataGrid id="DataGrid3" runat="server" ShowHeader="False" CellPadding="3" BackColor="#DEBA84" BorderColor="#DEBA84" BorderWidth="1px" BorderStyle="None" CellSpacing="2">
                                <FooterStyle forecolor="#8C4510" backcolor="#F7DFB5"></FooterStyle>
                                <HeaderStyle font-bold="True" forecolor="White" backcolor="#A55129"></HeaderStyle>
                                <PagerStyle horizontalalign="Center" forecolor="#8C4510" mode="NumericPages"></PagerStyle>
                                <SelectedItemStyle font-bold="True" forecolor="White" backcolor="#738A9C"></SelectedItemStyle>
                                <ItemStyle forecolor="#8C4510" backcolor="#FFF7E7"></ItemStyle>
                            </asp:DataGrid>
                        </td>
                        <td>
                            <asp:DataGrid id="DataGrid4" runat="server" ShowHeader="False" CellPadding="3" BackColor="White" BorderColor="#E7E7FF" BorderWidth="1px" BorderStyle="None" GridLines="Horizontal">
                                <FooterStyle forecolor="#4A3C8C" backcolor="#B5C7DE"></FooterStyle>
                                <HeaderStyle font-bold="True" forecolor="#F7F7F7" backcolor="#4A3C8C"></HeaderStyle>
                                <PagerStyle horizontalalign="Right" forecolor="#4A3C8C" backcolor="#E7E7FF" mode="NumericPages"></PagerStyle>
                                <SelectedItemStyle font-bold="True" forecolor="#F7F7F7" backcolor="#738A9C"></SelectedItemStyle>
                                <AlternatingItemStyle backcolor="#F7F7F7"></AlternatingItemStyle>
                                <ItemStyle forecolor="#4A3C8C" backcolor="#E7E7FF"></ItemStyle>
                            </asp:DataGrid>
                        </td>
                    </tr>
                </tbody>
            </table>
        </p>
        <asp:Button id="Button1" runat="server" Text="Envoyer"></asp:Button>
    </form>
</body>
</html>

在设计模式下,我们仅设置了格式属性。需要在控件代码中将数据与这四个组件关联起来:

<%@ Page Language="VB" %>
<%@ import Namespace="system.data" %>
<script runat="server">

    ' global data
        dim textes1() as string={"un","deux","trois","quatre"}
        dim textes2() as string={"one","two","three","for"}
        dim valeurs() as string={"1","2","3","4"}
        dim myDataListe as new ArrayList
        dim myDataTable as new DataTable("table1")
        dim myDataSet as new DataSet

        ' procedure executed when the page is loaded
        Sub page_Load(sender As Object, e As EventArgs)
            if not IsPostBack then
              ' create the data sources to be linked to the components
              createDataSources
              ' link to an array [Array]
              bindToArray
              ' link to a list [ArrayList]
              bindToArrayList
              ' link to a table [DataTable]
              bindToDataTable
              ' link to a data group [DataSet]
              bindToDataSet
            end if
        End Sub

        sub createDataSources
          ' creates data sources to be linked to components
          ' arraylist
          dim i as integer
          for i=0 to textes1.length-1
            myDataListe.add(textes1(i))
          next
          ' datatable
          ' we define its two columns
          myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
          myDataTable.Columns.Add("texte1",Type.GetType("System.String"))
          myDataTable.Columns.Add("texte2",Type.GetType("System.String"))
          ' fill the table
          dim ligne as DataRow
          for i=0 to textes1.length-1
            ligne=myDataTable.NewRow
            ligne("id")=valeurs(i)
            ligne("texte1")=textes1(i)
            ligne("texte2")=textes2(i)
            myDataTable.Rows.Add(ligne)
          next
          ' dataset - a single table
          myDataSet.Tables.Add(myDataTable)
        end sub

        ' panel connection
        sub bindToArray
          with DataGrid1
            .DataSource=textes1
            .DataBind
          end with
        end sub

        ' arraylist link
        sub bindToArrayList
          with DataGrid2
            .DataSource=myDataListe
            .DataBind
          end with
        end sub

        ' datatable link
        sub bindToDataTable
          with DataGrid3
            .DataSource=myDataTable
            .DataBind
          end with
        end sub

        ' dataset link
        sub bindToDataSet
          with DataGrid4
            .DataSource=myDataSet
            .DataBind
          end with
        end sub

</script>
<html>
...
</html>

该代码与前一个示例非常相似,因此我们不再赘述。但请注意数据绑定的实现方式。对于这四个控件中的每一个,只需执行以下步骤即可:

          with [DataGrid]
            .DataSource=[source de données]
            .DataBind
          end with

其中 [data source] 的类型为 [Array]、[ArrayList]、[DataTable] 或 [DataSet]。因此,我们可以看到,这是一个用于在表格中显示数据的强大工具:

  • 布局是在设计阶段使用 [WebMatrix] 或任何其他 IDE 创建的
  • 数据绑定在代码中完成。使用 [WebMatrix] 时,如果数据源是 SQL Server 数据库或 Access 数据库,则可在设计时完成。在这种情况下,需在表单上放置一个 [SqlDataSource] 或 [AccessDataSource] 组件。该组件可在设计时根据实际情况链接到物理数据源(即 SQL Server 数据库或 Access 数据库)。 若将已链接至物理数据源的 [SqlDataSource] 或 [AccessDataSource] 对象赋值给 [DataGrid] 组件的 [DataSource] 属性,则实际数据将在设计模式下显示于 [DataGrid] 组件中。

8.5. 数据列表控件的 ViewState

在此,我们将重点介绍数据列表控件的 [VIEWSTATE] 机制。在两个客户端请求之间维护数据列表控件的状态时,您可能会在两种方法之间犹豫不决:

  • 将其 [VIEWSTATE] 属性设置为 true
  • 将其 [VIEWSTATE] 属性设置为 false,并将数据源存储在会话中,以便在下次请求时将组件与该数据源关联。

以下示例演示了数据容器 [VIEWSTATE] 机制的某些方面。在客户端的首次请求中,应用程序显示以下视图:

编号
编号
类型
属性
角色
1
DataGrid1
DataGrid
EnableViewState=true
显示数据源 S
2
DataGrid2
DataGrid
EnableViewState=true
显示与 [DataGrid1] 相同的数据源 S
3
Button1
Button
EnableViewState=false
[提交] 按钮

[DataGrid1] 组件由 [VIEWSTATE] 机制维护。 我们需要确定该机制——它会在每次请求时重新生成 [DataGrid1] 的显示内容——是否也会重新生成其数据源。为此,我们将数据源与 [DataGrid2] 组件关联起来。每次请求时,[DataGrid2] 通过显式绑定到 [DataGrid1] 的数据源来完成生成。同时,其 [EnableViewState] 属性也被设置为 [true]。

应用程序的 [main.aspx] 呈现代码如下:


<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>
<HTML>
    <HEAD>
        <title></title>
    </HEAD>
    <body>
        <form runat="server">
            <table>
                <tr>
                    <td align="center">DataGrid 1</td>
                    <td align="center">
                        DataGrid 2</td>
                </tr>
                <tr>
                    <td>
                      <asp:DataGrid id="DataGrid1" runat="server" ...>
                            <SelectedItemStyle ...></SelectedItemStyle>
....
                        </asp:DataGrid></td>
                    <td>
                      <asp:DataGrid id="Datagrid2" runat="server" ...>
....
                        </asp:DataGrid></td>
                </tr>
            </table>
            <asp:Button id="Button1" runat="server" Text="Envoyer"></asp:Button>
        </form>
    </body>
</HTML>

控制器 [main.aspx.vb] 如下所示:


Imports System.Data
 
Public Class main
    Inherits System.Web.UI.Page
 
    Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents Datagrid2 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents Button1 As System.Web.UI.WebControls.Button
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' on the 1st query, the data source is defined and linked to the 1st datagrid
        If Not IsPostBack Then
            'define data source
            With DataGrid1
                .DataSource = createDataSource()
                .DataBind()
            End With
        End If
        ' for each query, datagrid2 is linked to the source of the 1st datagrid
        With Datagrid2
            .DataSource = DataGrid1.DataSource
            .DataBind()
        End With
    End Sub
 
    Private Function createDataSource() As DataTable
        ' initialize the data source
        Dim thèmes As New DataTable
        ' columns
        With thèmes.Columns
            .Add("id", GetType(System.Int32))
            .Add("thème", GetType(System.String))
            .Add("description", GetType(System.String))
        End With
        ' column id will be primary key
        thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
        ' lines
        Dim ligne As DataRow
        For i As Integer = 0 To 4
            ligne = thèmes.NewRow
            ligne.Item("id") = i.ToString
            ligne.Item("thème") = "thème" + i.ToString
            ligne.Item("description") = "description du thème " + i.ToString
            thèmes.Rows.Add(ligne)
        Next
        Return thèmes
    End Function
 
    Private Sub InitializeComponent()
 
    End Sub
End Class

[createDataSource] 方法创建了一个类型为 [DataTable] 的数据源 S。我们不会过多讨论其代码,因为这并非本示例的重点。这是用于构建我们感兴趣的两个 [DataGrid] 组件的方法:

  • [DataGrid1] 组件在首次查询时与 S 表绑定一次,此后不再绑定。
  • [DataGrid2] 组件会在每次新查询时绑定到 [DataGrid1.DataSource] 数据源。

在首次查询时,我们得到如下视图:

Image

合乎逻辑的是,两个组件都显示了它们所关联的数据源。我们使用 [Submit] 按钮触发向服务器的 [PostBack]。随后显示的视图如下:

Image

我们观察到 [DataGrid1] 组件保留了其值,但 [DataGrid2] 组件并未保留。解释:

  • 即使在 [Page_Load] 过程开始之前,由于 [viewstate] 机制的作用,[DataGrid1] 和 [DataGrid2] 对象已检索到了上一次请求时的值。事实上,两者的 [EnableViewState] 属性均设置为 [true]。
  • [Page_Load] 过程开始运行。由于这是一次 [PostBack] 操作,[DataGrid1] 组件不会被 [Page_Load] 修改(参见代码)。因此,它保留了通过 [viewstate] 检索到的值。这就是上图所示的情况。
  • 另一方面,[DataGrid2] 组件通过 [DataBind] 绑定到了 [DataGrid1.DataSource] 数据源。因此,它会被重建,而它刚刚通过 [viewstate] 检索到的值也会丢失。因此,在此处将它的 [EnableViewState] 属性设置为 [false] 会更有利,以避免不必要的状态管理。 上图显示 [DataGrid2] 已被绑定到一个空数据源。由于该数据源是 [DataGrid1.DataSource],我们可以得出结论:虽然 [viewstate] 机制成功恢复了 [DataGrid1] 组件的显示,但并未恢复其 [DataSource] 等属性。

从这个示例中我们可以得出什么结论?如果数据容器需要在每次请求时都与数据源绑定(DataBind),则应避免将其 [EnableViewState] 属性设置为 [true]。然而,在某些情况下,即使在此情境下,容器的 [EnableViewState] 属性也必须保持为 [true];否则,您希望处理的事件将不会被触发。 我们稍后将遇到一个此类示例。

通常,数据容器的数据源会在请求过程中发生变化。因此,容器必须在每次请求时都与数据源绑定。数据源通常被限定在会话范围内,以便请求能够访问它。我们的第二个示例演示了这一机制。该应用程序仅提供一个视图:

No.
name
type
属性
角色
1
DataGrid1
DataGrid
EnableViewState=true
显示数据源 S
2
DataGrid2
DataGrid
EnableViewState=false
显示与 [DataGrid1] 相同的数据源 S
3
Button1
Button
EnableViewState=false
[提交] 按钮
4
lblInfo1
lblInfo2
lblInfo3
标签
EnableViewState=false
信息文本

[DataGrid1] 组件仅在首次请求时与数据绑定。得益于 [viewstate] 机制,它将在多次请求中保留其值。而 [DataGrid2] 组件在每次请求时都会与数据源绑定,且每次新请求都会向该数据源添加一项。因此,[DataGrid2] 组件必须在每次请求时进行绑定(DataBind)。 因此,我们已按先前建议将其 [EnableViewState] 属性设置为 [false]。这样,在第二次请求(使用 [Submit] 按钮)时,我们会得到以下响应:

Image

[DataGrid1] 组件保留了其初始值。[DataGrid2] 组件多了一个项目。三个值 [1,2,2] 代表查询编号。我们可以看到其中一个值是错误的。我们将尝试理解原因。

应用程序的呈现代码 [main.aspx] 如下:


<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <title></title>
    </HEAD>
    <body>
        <form runat="server">
            <table>
                <tr>
                    <td align="center" bgColor="#ccffcc">DataGrid 1</td>
                    <td align="center" bgColor="#ffff99">DataGrid 2</td>
                </tr>
                <tr>
                    <td vAlign="top">
                        <asp:DataGrid id="DataGrid1" runat="server" ...>
...
                        </asp:DataGrid>
                    </td>
                    <td vAlign="top">
                        <asp:DataGrid id="Datagrid2" runat="server" ... EnableViewState="False">
....
                        </asp:DataGrid>
                    </td>
                </tr>
            </table>
            <P>Numéro de requête&nbsp;:
                <asp:Label id="lblInfo1" runat="server" EnableViewState="False"></asp:Label>,
                <asp:Label id="lblInfo2" runat="server" EnableViewState="False"></asp:Label>,
                <asp:Label id="lblInfo3" runat="server" EnableViewState="False"></asp:Label></P>
            <P>
                <asp:Button id="Button1" runat="server" Text="Envoyer" EnableViewState="False"></asp:Button></P>
        </form>
    </body>
</HTML>

控制器代码 [main.aspx.vb] 如下:


Imports System.Data
Imports System
 
Public Class main
    Inherits System.Web.UI.Page
 
    Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents Datagrid2 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents Button1 As System.Web.UI.WebControls.Button
    Protected WithEvents lblInfo1 As System.Web.UI.WebControls.Label
    Protected WithEvents lblInfo2 As System.Web.UI.WebControls.Label
    Protected WithEvents lblInfo3 As System.Web.UI.WebControls.Label
 
    Dim dtThèmes As DataTable
    Dim numRequête1 As Integer
    Dim numRequête2 As Integer
    Dim numRequête3 As New entier
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' on the 1st query, the data source is defined and linked to the 1st datagrid
        If Not IsPostBack Then
            'define data source
            dtThèmes = createDataSource()
            With DataGrid1
                .DataSource = dtThèmes
                .DataBind()
            End With
            ' store information in the session
            Session("source") = dtThèmes
            numRequête1 = 0 : Session("numRequête1") = numRequête1
            numRequête2 = 0 : Session("numRequête2") = numRequête2
            numRequête3.valeur = 0 : Session("numRequête3") = numRequête3
        End If
        ' a new theme is added to each query
        dtThèmes = CType(Session("source"), DataTable)
        Dim nbThèmes = dtThèmes.Rows.Count
        Dim ligne As DataRow = dtThèmes.NewRow
        With ligne
            .Item("id") = nbThèmes.ToString
            .Item("thème") = "thème" + nbThèmes.ToString
            .Item("description") = "description du thème " + nbThèmes.ToString
        End With
        dtThèmes.Rows.Add(ligne)
        'links datagrid2 with the data source
        With Datagrid2
            .DataSource = dtThèmes
            .DataBind()
        End With
        ' info no. of requests
        numRequête1 = CType(Session("numRequête1"), Integer)
        numRequête1 += 1
        lblInfo1.Text = numRequête1.ToString
        numRequête2 = CType(Session("numRequête2"), Integer)
        numRequête2 += 1
        lblInfo2.Text = numRequête2.ToString
        numRequête3 = CType(Session("numRequête3"), entier)
        numRequête3.valeur += 1
        lblInfo3.Text = numRequête3.valeur.ToString
        ' store some information in the session
        Session("numRequête2") = numRequête2
    End Sub
 
    Private Function createDataSource() As DataTable
....
    End Function
End Class
 
Public Class entier
    Private _valeur As Integer
    Public Property valeur() As Integer
        Get
            Return _valeur
        End Get
        Set(ByVal Value As Integer)
            _valeur = Value
        End Set
    End Property
End Class

我们没有重写 [createDataSource] 方法的代码。它与前一个应用程序中的代码相同,只是源代码中只包含三行。让我们先看看数据源和两个容器是如何管理的:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' on the 1st query, the data source is defined and linked to the 1st datagrid
        If Not IsPostBack Then
            'define data source
            dtThèmes = createDataSource()
            With DataGrid1
                .DataSource = dtThèmes
                .DataBind()
            End With
            ' store information in the session
            Session("source") = dtThèmes
...
        End If
        ' a new theme is added to each query
        dtThèmes = CType(Session("source"), DataTable)
        Dim nbThèmes = dtThèmes.Rows.Count
        Dim ligne As DataRow = dtThèmes.NewRow
        With ligne
            .Item("id") = nbThèmes.ToString
            .Item("thème") = "thème" + nbThèmes.ToString
            .Item("description") = "description du thème " + nbThèmes.ToString
        End With
        dtThèmes.Rows.Add(ligne)
        'links datagrid2 with the data source
        With Datagrid2
            .DataSource = dtThèmes
            .DataBind()
        End With
...
    End Sub

[DataGrid1] 组件仅在首次查询时(非 IsPostBack 状态)与数据源 S 建立绑定。随后该组件会被放入会话中,此后将不再被放入会话。每次请求都会检索会话中的数据源 S,并向其中添加一行。 [DataGrid2] 组件在每次请求时都会显式地绑定到数据源 S。这就是为什么它的内容在每次请求时都会增加一行。请注意,在修改数据源 S 的内容后,它不会通过操作显式地放回会话中:

            Session("source") = S

为什么?当查询开始时,会话中包含一个类型为 [DataTable] 的 Session("source") 对象,该对象代表上次查询时的数据源。我们将该 Session("source") 对象称为 S。当我们编写:


        dtThèmes = CType(Session("source"), DataTable)

[dtThèmes] 和 [S] 是指向同一个 [DataTable] 对象的两个引用。因此,当 [Page_Load] 中的代码向 [dtThèmes] 引用的表中添加一个元素时,它同时也会将其添加到 [S] 引用的表中。 在页面执行结束时,会话中的所有对象都将被保存,包括 Session("source") 对象,即 S,即 [dtThemes]。因此,被保存的确实是数据源的新内容。没有必要编写:

            Session("source") = dtThèmes

来执行此保存操作,因为 Session("source") 已经等于 [dtThèmes]。当会话中放置的数据不是对象时(例如 [Integer、Float、...] 结构),情况就不再如此。查询计数器管理就证明了这一点:


    Dim numRequête1 As Integer
    Dim numRequête2 As Integer
    Dim numRequête3 As New entier
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' on the 1st query, the data source is defined and linked to the 1st datagrid
....
            ' store information in the session
            Session("source") = dtThèmes
            numRequête1 = 0 : Session("numRequête1") = numRequête1
            numRequête2 = 0 : Session("numRequête2") = numRequête2
            numRequête3.valeur = 0 : Session("numRequête3") = numRequête3
        End If
        ' a new theme is added to each query
....
        ' info no. of requests
        numRequête1 = CType(Session("numRequête1"), Integer)
        numRequête1 += 1
        lblInfo1.Text = numRequête1.ToString
        numRequête2 = CType(Session("numRequête2"), Integer)
        numRequête2 += 1
        lblInfo2.Text = numRequête2.ToString
        numRequête3 = CType(Session("numRequête3"), entier)
        numRequête3.valeur += 1
        lblInfo3.Text = numRequête3.valeur.ToString
        ' store some information in the session
        Session("numRequête2") = numRequête2
    End Sub
 
    Private Function createDataSource() As DataTable
....
    End Function
 
    Private Sub InitializeComponent()
 
    End Sub
End Class
 
Public Class entier
    Private _valeur As Integer
    Public Property valeur() As Integer
        Get
            Return _valeur
        End Get
        Set(ByVal Value As Integer)
            _valeur = Value
        End Set
    End Property

我们将请求计数器存储在三个元素中:

  • numRequest1 和 numRequest2 的类型为 [Integer] —— [Integer] 不是类,而是一种结构
  • numRequest3 的类型为 [integer] —— [integer] 是为此目的定义的类

当我们编写:


        numRequête1 = CType(Session("numRequête1"), Integer)
..
        numRequête2 = CType(Session("numRequête2"), Integer)
..
        numRequête3 = CType(Session("numRequête3"), entier)
..
  • 结构 [Session("numRequest1")] 被复制到 [numRequest1] 中。因此,当元素 [numRequest1] 被修改时,元素 [Session("numRequest1")] 本身并不会被修改
  • [Session("numRequest2")] 和 [numRequest2] 也是如此
  • 元素 [Session("numRequest3")] 和 [numRequest3] 都是指向同一 [integer] 类型对象的引用。被引用的对象可以通过任一引用进行修改。

由此我们可以得出结论,没有必要编写:

        Session("numRequête3") = numRequête3

来将 [numRequest3] 的新值存储在会话中。相反,您应该写:

        Session("numRequête1") = numRequête1
        Session("numRequête2") = numRequête2

来存储结构体 [numRequête1] 和 [numRequête2] 的新值。我们仅对 [numRequête2] 进行了此操作,这解释了为何在第二次查询后获取的屏幕截图中,计数器 [numRequête1] 的数值不正确。

因此需注意:一旦数据被添加到会话中,若其由对象表示,则无需重复添加。在其他情况下,若数据已发生修改,则必须重新添加。

8.6. 使用分页且已排序的 DataGrid 显示数据列表

[DataGrid] 组件允许您显示 [DataSet] 的内容。到目前为止,我们一直都是“手动”构建 [DataSet]。这次,我们将使用来自数据库的 [DataSet]。我们将构建以下 MVC 应用程序:

这三个视图将作为容器被整合到 [main.aspx] 控制器中的呈现代码中。因此,该应用程序仅包含一个页面:[main.aspx]。

8.6.1. 业务类

[products] 类提供了对以下 ACCESS 数据库的访问:

Image

[products]类的代码如下:


Imports System
Imports System.Data
Imports System.Data.OleDb
Imports System.Xml
 
Namespace st.istia.univangers.fr
 
    Public Class produits
        Private chaineConnexionOLEDB As String
 
        Public Sub New(ByVal chaineConnexionOLEDB As String)
            ' save the connection string
            Me.chaineConnexionOLEDB = chaineConnexionOLEDB
        End Sub
 
        Public Function getDataSet(ByVal commande As String) As DataSet
            ' create a DataAdapter object to read data from source OLEDB
            Dim adaptateur As New OleDbDataAdapter(commande, chaineConnexionOLEDB)
            ' create a memory image of the select result
            Dim contenu As New DataSet
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' we return the result
            Return contenu
        End Function
    End Class
End Namespace

ACCESS 数据库将通过 OLEDB 驱动程序进行管理。我们向 [products] 类的构造函数提供连接字符串、OLEDB 驱动程序以及待管理的数据库。 [products] 类有一个 [getDataSet] 方法,该方法返回一个 [DataSet],该 [DataSet] 通过执行一个 SQL [select] 查询获得,其查询文本作为参数传递。在该方法执行期间,会进行多项操作:建立与数据库的连接、执行 [select] 查询以及关闭连接。所有这些操作都可能引发异常,这里对此进行了处理。一个测试程序可能如下所示:

Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports System.Data
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

    ' test pg
    Module testproduits
        Sub Main(ByVal arguments() As String)
            ' displays the contents of a product table
            ' the table is in a ACCESS database whose pg receives the file name
            Const syntaxe1 As String = "pg bdACCESS"

            ' checking program parameters
            If arguments.Length <> 1 Then
                ' error msg
                Console.Error.WriteLine(syntaxe1)
                ' end
                Environment.Exit(1)
            End If

            ' prepare the connection chain
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)

            ' creation of a product object
            Dim objProduits As produits = New produits(chaineConnexion)

            ' retrieve the product table from a dataset
            Dim contenu As DataSet
            Try
                contenu = objProduits.getDataSet("select id,nom,prix from liste")
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

            ' display its contents
            Dim lignes As DataRowCollection = contenu.Tables(0).Rows
            For i As Integer = 0 To lignes.Count - 1
                ' table line i
                Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
                "," + lignes(i).Item("prix").ToString)
            Next
        End Sub
    End Module
End Namespace

此应用程序的各个组件编译如下:

dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb

dos>vbc /r:produits.dll /r:system.data.dll /r:system.xml.dll /r:system.dll testproduits.vb

dos>dir
06/05/2004  16:52              118 784 produits.mdb
07/05/2004  11:07                  902 produits.vb
07/04/2004  07:01                1 532 testproduits.vb
07/05/2004  14:21                3 584 produits.dll
07/05/2004  14:22                4 608 testproduits.exe

dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30

8.6.2. 视图

既然我们已经有了数据访问类,接下来将为这个 Web 应用程序编写控制器和视图。让我们先看看它是如何工作的。第一个视图如下:

编号
名称
类型
属性
角色
1
txtSelect
文本框
EnableViewState=true
SELECT 查询输入字段
2
必填字段验证器1
必填字段验证器
EnableViewState=false
检查是否存在 1
3
txtPages
文本框
EnableViewState=true
输入字段 - 指定每页结果中要显示的数据行数
4
必填字段验证器2
RequiredFieldValidator
EnableViewState=false
检查是否存在 3
5
RangeValidator1
RangeValidator
EnableViewState=false
检查 (3) 是否在 [1,30] 范围内
6
btnExecute
按钮
EnableViewState=false
[提交] 按钮

我们将此视图称为 [form] 视图。它允许用户对 [products.mdb] 数据库执行 SQL SELECT 查询。执行该查询将创建一个新视图:

名称
类型
属性
角色
1
lblSelect
标签
EnableViewState=false
信息字段
2
rdAscending
rdDescending
单选按钮
启用视图状态=false
组名=rdSort
允许您选择排序顺序
3
DataGrid1
DataGrid
EnableViewState=true
允许分页=true
允许排序=true
显示查询结果的表格
4
lnkResults
链接按钮
EnableViewState=false
[提交] 按钮

我们将此视图称为 [results] 视图。它包含一个 [DataGrid],用于显示 SQL SELECT 语句的结果。用户在查询时可能会出错。借助验证控件,部分错误将在 [errors] 视图中向用户报告。

Image

其他错误将通过 [errors] 视图向用户报告:

Image

显示 [errors] 视图:

名称
类型
属性
角色
1
HTMLErrors
变量
 
显示错误所需的 HTML 代码
3
lnkForm2
LinkButton
EnableViewState=false
[提交] 按钮

该应用程序的三个视图是同一页面 [main.aspx] 内的三个不同容器(面板)。其呈现代码如下:


<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
    </HEAD>
    <body>
        <P>Liaison de données avec un DataGrid</P>
        <HR width="100%" SIZE="1">
        <form runat="server">
            <asp:panel id="vueFormulaire" runat="server">
                <P>Commande [select] à exécuter sur la table LISTE</P>
                <P>&nbsp;Exemple : select id, nom, prix from LISTE
                </P>
                <P>
                    <asp:TextBox id="txtSelect" runat="server" Columns="60"></asp:TextBox></P>
                <P>
                    <asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server" EnableViewState="False" EnableClientScript="False"
                        ErrorMessage="Indiquez la requête [select] à exécuter" ControlToValidate="txtSelect" Display="Dynamic"></asp:RequiredFieldValidator></P>
                <P>Nombre de lignes par page :
                    <asp:TextBox id="txtPages" runat="server" Columns="3"></asp:TextBox></P>
                <P>
                    <asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" EnableViewState="False" EnableClientScript="False"
                        ErrorMessage="Indiquez le nombre de lignes par page désirées" ControlToValidate="txtPages" Display="Dynamic"></asp:RequiredFieldValidator>
                    <asp:RangeValidator id="RangeValidator1" runat="server" EnableViewState="False" EnableClientScript="False"
                        ErrorMessage="Vous devez indiquer un nombre entre 1 et 30" ControlToValidate="txtPages" Display="Dynamic"
                        Type="Integer" MinimumValue="1" MaximumValue="30"></asp:RangeValidator></P>
                <P>
                    <asp:Button id="btnExécuter" runat="server" EnableViewState="False" Text="Exécuter"></asp:Button></P>
            </asp:panel>
            <asp:Panel id="vueRésultats" runat="server">
                <P>Résultats de la requête
                    <asp:Label id="lblSelect" runat="server"></asp:Label></P>
                <P>Tri
                    <asp:RadioButton id="rdCroissant" runat="server" Text="croissant" Checked="True" GroupName="rdTri"></asp:RadioButton>
                    <asp:RadioButton id="rdDécroissant" runat="server" Text="décroissant" GroupName="rdTri"></asp:RadioButton></P>
                <P>
                    <asp:DataGrid id="DataGrid1" runat="server" BorderColor="#CC9966" BorderStyle="None" BorderWidth="1px"
                        BackColor="White" CellPadding="4" AllowPaging="True" PageSize="4" AllowSorting="True">
                        <SelectedItemStyle Font-Bold="True" ForeColor="#663399" BackColor="#FFCC66"></SelectedItemStyle>
                        <ItemStyle ForeColor="#330099" BackColor="White"></ItemStyle>
                        <HeaderStyle Font-Bold="True" ForeColor="#FFFFCC" BackColor="#990000"></HeaderStyle>
                        <FooterStyle ForeColor="#330099" BackColor="#FFFFCC"></FooterStyle>
                        <PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;dent" HorizontalAlign="Center"
                            ForeColor="#330099" BackColor="#FFFFCC"></PagerStyle>
                    </asp:DataGrid></P>
                <P>
                    <asp:LinkButton id="lnkRésultats" runat="server" EnableViewState="False">Retour au formulaire</asp:LinkButton></P>
            </asp:Panel>
            <asp:Panel id="vueErreurs" runat="server">
                <P>Les erreurs suivantes se sont produites :</P>
                <% =erreursHTML %>
                <P>
                    <asp:LinkButton id="lnkErreurs" runat="server" EnableViewState="False">Retour vers le formulaire</asp:LinkButton></P>
            </asp:Panel>
        </form>
    </body>
</HTML>

8.6.3. 配置 DataGrid

让我们仔细看看 [DataGrid] 组件的分页功能,这是我们第一次接触到这一特性。在上面的代码中,分页由以下属性控制:


                    <asp:DataGrid id="DataGrid1" runat="server" PageSize="4" AllowPaging="True" ...>
...
                        <PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;dent" ...></PagerStyle>
                    </asp:DataGrid></P>
AllowPaging="true"
启用分页
PageSize="4"
每页显示四行数据
NextPageText="下一页"
跳转到数据源下一页的链接文本
PrevPageText="上一页"
跳转至数据源上一页的链接文本

这些信息可直接输入到 <asp:datagrid> 标签的属性中。您也可以使用 [WebMatrix]。在 [DataGrid] 属性窗口中,单击 [属性生成器] 链接:

Image

将出现以下向导:

Image

选择 [分页] 选项:

Image

上文展示了呈现代码中 [DataGrid] 的分页属性值。

此外,我们还启用了对 [DataGrid] 某列数据的排序功能。实现此功能有多种方法。一种是在 [DataGrid] 属性窗口中设置 [AllowSorting=true] 属性。您也可以使用属性生成器。无论采用哪种方法,最终都会在组件的 <asp:DataGrid> 标签中生成 [AllowSorting=true] 属性:


                    <asp:DataGrid id="DataGrid1" runat="server" ... AllowPaging="True" PageSize="4" AllowSorting="True">

8.6.4. 控制器

[global.asax, global.asax.vb] 控制器如下所示:

[global.asax]

<%@ Application src="global.asax.vb" inherits="Global" %>

[global.asax.vb]


Imports st.istia.univangers.fr
Imports System.Configuration
 
Public Class Global
    Inherits System.Web.HttpApplication
 
    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' create a product object
        Dim objProduits As produits
        Try
            objProduits = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection"))
            ' put the object in the application
            Application("objProduits") = objProduits
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

当应用程序启动(Application_Start)时,我们会创建一个 [products] 对象并将其存储在应用程序中,以便所有客户端的请求都能使用它。如果在此创建过程中发生异常,则会将其记录在应用程序日志中。[Application_Start] 过程仅执行一次。之后,[global.asax] 控制器将不再参与处理。随后由 [main.aspx.vb] 控制器处理剩余部分:


Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
 
Public Class main
    Inherits System.Web.UI.Page
 
    ' components page
    Protected WithEvents txtSelect As System.Web.UI.WebControls.TextBox
    Protected WithEvents RequiredFieldValidator1 As System.Web.UI.WebControls.RequiredFieldValidator
    Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
    Protected WithEvents RequiredFieldValidator2 As System.Web.UI.WebControls.RequiredFieldValidator
    Protected WithEvents RangeValidator1 As System.Web.UI.WebControls.RangeValidator
    Protected WithEvents btnExécuter As System.Web.UI.WebControls.Button
    Protected WithEvents vueFormulaire As System.Web.UI.WebControls.Panel
    Protected WithEvents lblSelect As System.Web.UI.WebControls.Label
    Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
    Protected WithEvents lnkRésultats As System.Web.UI.WebControls.LinkButton
    Protected WithEvents vueRésultats As System.Web.UI.WebControls.Panel
    Protected WithEvents lnkErreurs As System.Web.UI.WebControls.LinkButton
    Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
    Protected WithEvents rdCroissant As System.Web.UI.WebControls.RadioButton
    Protected WithEvents rdDécroissant As System.Web.UI.WebControls.RadioButton
    ' data page
    Protected erreursHTML As String
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' check for application errors
        If CType(Application("erreur"), Boolean) Then
            ' the application has not initialized correctly
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs, False)
            Exit Sub
        End If
        '1st request
        If Not IsPostBack Then
            ' the empty form is displayed
            afficheFormulaire()
        End If
    End Sub
 
    Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
        ' displays the error view
        erreursHTML = ""
        For i As Integer = 0 To erreurs.Count - 1
            erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
        lnkErreurs.Visible = afficheLien
        ' the [errors] view is displayed
        vueErreurs.Visible = True
        vueFormulaire.Visible = False
        vueRésultats.Visible = False
    End Sub
 
    Private Sub afficheFormulaire()
        ' the [form] view is displayed
        vueFormulaire.Visible = True
        vueErreurs.Visible = False
        vueRésultats.Visible = False
    End Sub
 
    Private Sub afficheRésultats(ByVal sqlTexte As String, ByVal données As DataView)
        ' initialize controls
        lblSelect.Text = sqlTexte
        With DataGrid1
            .DataSource = données
            .PageSize = CType(txtPages.Text, Integer)
            .CurrentPageIndex = 0
            .DataBind()
        End With
        ' the [results] view is displayed
        vueRésultats.Visible = True
        vueFormulaire.Visible = False
        vueErreurs.Visible = False
    End Sub
 
    Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
        ' valid page?
        If Not Page.IsValid Then
            afficheFormulaire()
            Exit Sub
        End If
 
        ' execute query SELECT customer
        Dim données As DataView
        Try
            données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0).DefaultView
        Catch ex As Exception
            Dim erreurs As New ArrayList
            erreurs.Add("erreur d'accès à la base de données (" + ex.Message + ")")
            afficheErreurs(erreurs, True)
            Exit Sub
        End Try
        ' all's well - the results are in
        afficheRésultats(txtSelect.Text.Trim, données)
        ' put the data in the session
        Session("données") = données
    End Sub
 
    Private Sub retourFormulaire(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkErreurs.Click, lnkRésultats.Click
        ' set of views
        vueErreurs.Visible = False
        vueFormulaire.Visible = True
        vueRésultats.Visible = False
    End Sub
 
    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
        ' change page
        With DataGrid1
            .CurrentPageIndex = e.NewPageIndex
            .DataSource = CType(Session("données"), DataView)
            .DataBind()
        End With
    End Sub
 
    Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
        ' sort the dataview
        Dim données As DataView = CType(Session("données"), DataView)
        données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
        ' we display it
        With DataGrid1
            .CurrentPageIndex = 0
            .DataSource = données
            .DataBind()
        End With
    End Sub
End Class

当页面加载时 [Page_Load],我们首先检查应用程序是否能够正确初始化。如果不能,我们将显示 [errors] 视图,且不包含返回表单的链接,因为此时该链接已无必要。实际上,只有当应用程序无法正确初始化时,才能显示 [errors] 视图。否则,如果这是客户端的首次请求,我们将显示 [form] 视图。 其余部分,我们留给读者自行理解代码。我们将仅关注三个过程:[btnExecute_Click] 过程(在用户请求执行 [form] 视图中输入的 SQL 查询时运行)、[DataGrid1_PageIndexChanged] 过程(在用户使用 [DataGrid] 中的 [Next] 和 [Previous] 链接时运行), 以及 [DataGrid1_SortCommand] 过程,该过程在用户点击列标题按该顺序排序数据时运行。排序顺序(升序或降序)由两个排序单选按钮决定。

因此,在 [btnExécuter_Click] 过程开始时,我们会首先检查页面是否有效。当 [btnExécuter_Click] 过程运行时,与页面中各个验证控件相关的检查已经完成。对于每个验证控件,已设置了两个属性:

IsValid
若验证数据有效则设为 true,否则设为 false
ErrorMessage
若验证数据无效,则显示此错误消息

对于页面本身,已设置了一个 [IsValid] 属性。只有当所有验证控件的 [IsValid] 属性均设置为 true 时,该属性才为 true。 若非如此,则必须显示 [form] 视图。该视图包含将显示其 [errorMessage] 属性的验证控件。如果页面有效,我们将使用由 [Application_Start] 创建的 [products] 对象来获取与执行 SQL SELECT 查询对应的 [DataSet]。我们将此转换为 [DataView] 对象:


        Dim données As DataView
        Try
            données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0).DefaultView
...

我们本可以直接操作 [DataSet] 并编写如下代码:


        Dim données As DataSet
        Try
            données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim)
...

[DataSet] 对象本质上是由关系链接起来的表集合。在我们的具体应用中,从 [products] 类获取的 [DataSet] 仅包含一个表,即由 [select] 语句生成的那个表。表可以进行排序,而 [DataSet] 则无法排序;然而,我们需要对检索到的数据进行排序。为了处理 [select] 语句生成的结果表,我们可以这样编写:


        Dim données As DataTable
        Try
            données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0)
...

尽管 [DataTable] 对象代表数据库表,但它本身不具备排序方法。要实现排序,您需要创建该表的视图。 视图是 [DataView] 类型的对象。您可以通过过滤器对同一张表创建不同的视图。一张表有一个默认视图,即未定义任何过滤器的视图。因此,它代表了整个表。可以通过 [DataTable.DefaultView] 获取此默认视图。您可以使用其 [sort] 属性对视图进行排序,我们稍后将对此进行讨论。

如果从 [products] 类成功检索 [DataSet],则显示 [results] 视图;否则,显示 [errors] 视图。通过 [displayResults] 过程显示 [results] 视图,该过程接收两个参数:

  • 要放置在 [lblSelect] 标签中的文本
  • 要绑定到 [DataGrid1] 的 [DataView]

此示例展示了 [DataGrid] 组件的强大灵活性。它能够识别所绑定的 [DataView] 的结构并适应其变化。最后,[btnExécuter_Click] 过程将刚刚获取的 [DataView] 存储在用户的会话中,以便当用户请求同一 [DataView] 的其他页面时,该 [DataView] 仍可使用。

当用户点击 [DataGrid] 中的 [Next] 和 [Previous] 链接时,将执行 [DataGrid1_PageIndexChanged] 过程。该过程接收两个参数:


    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.) Handles DataGrid1.PageIndexChanged
source
触发该事件的对象——在此情况下,即 [Next] 或 [Previous] 链接之一
e
有关该事件的信息。e.NewPageIndex 属性表示响应客户端请求后应显示的页码

该事件处理程序的完整代码如下:


    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
        ' change page
        With DataGrid1
            .CurrentPageIndex = e.NewPageIndex
            .DataSource = CType(Session("données"), DataView)
            .DataBind()
        End With
    End Sub

[DataGrid] 组件具有一个 [CurrentPageIndex] 属性,用于指示其当前显示或即将显示的页码。我们将 [e] 参数的 [NewPageIndex] 值赋给该属性。随后,[DataGrid] 便与 [btnExécuter_Click] 过程保存在会话中的 [DataView] 建立了绑定。

有人可能会疑惑,既然 [DataGrid] 的内容是在每次页面重新加载时由代码计算生成的,它是否还需要 [EnableViewState=true] 属性。乍看之下似乎不需要。然而,如果 [DataGrid] 设置了 [EnableViewState=false] 属性,我们会发现 [DataGrid1.PageIndexChanged] 事件永远不会被触发。这就是我们保留 [EnableViewState=true] 的原因。 我们知道,这会导致 [DataGrid] 的内容被存储在页面的隐藏字段 [__VIEWSTATE] 中。如果 [DataGrid] 规模较大,这可能会显著降低页面加载速度。如果这构成问题,您可以自行管理分页,而不使用 [DataGrid] 的自动分页功能。

当用户点击 [DataGrid] 显示的某列标题以请求按该列顺序对数据进行排序时,将执行 [DataGrid1_SortCommand] 过程。该过程接收两个参数:


    Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
source
触发该事件的对象——在此情况下,是 [Next] 或 [Previous] 链接之一
e
有关该事件的信息。[e.SortExpression] 属性即为被点击用于排序的列名

该事件处理程序的完整代码如下:


    Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
        ' sort the dataview
        Dim données As DataView = CType(Session("données"), DataView)
        données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
        ' we display it
        With DataGrid1
            .CurrentPageIndex = 0
            .DataSource = données
            .DataBind()
        End With
    End Sub

我们检索当前会话中由 [DataGrid] 显示的 [DataView]。该 [DataView] 是由 [btnExécuter_Click] 过程放置在那里的。[DataView] 组件有一个 [Sort] 属性,我们将排序表达式赋值给该属性。 其语法为 [select ... order by expr1, expr2, ...],其中每个 [expr] 后面可以跟关键字 [asc] 表示升序排序,或 [desc] 表示降序排序。此处使用的 [order by] 表达式即为 [order by column asc/desc]。 [e.SortExpression] 属性返回被点击用于排序的 [DataGrid] 列的名称。[asc/desc] 字符串根据 [rdTri] 组中单选按钮的值进行设置。一旦 [DataView] 的排序表达式设置完成,[DataGrid] 便与其绑定。我们将 [DataGrid] 定位在其第一页。

8.7. DataList 组件与数据绑定

接下来我们将重点关注 [DataList] 组件。它提供的格式化选项比 [DataGrid] 更多,但灵活性较低。因此,它无法自动适应所关联的数据源。若需适应,必须通过代码实现。如果预先已知数据源的结构,那么该组件提供的格式化选项可能使其比 [DataGrid] 更受青睐。

8.7.1. 应用

为演示 [DataList] 的用法,我们将构建一个与前一个类似的 MVC 应用程序:

这三个视图将作为容器嵌入到 [main.aspx] 控制器中的呈现代码中。因此,该应用程序仅包含一个页面 [main.aspx]。

8.7.2. 业务类

[products] 类与之前相同。

8.7.3. 视图

当用户首次向应用程序发送请求时,将看到以下 [results1] 视图:

编号
名称
类型
属性
角色
1
单选按钮1
单选按钮2
单选按钮
EnableViewState=false
允许您从两种 [DataList] 样式中选择一种
2
btnChanger
按钮
EnableViewState=false
[提交] 按钮
3
DataList1
DataList
EnableViewState=true
数据列表显示字段

如果用户选择样式 #2,他们将看到以下 [results2] 视图:

编号
名称
类型
属性
角色
1
DataList2
DataList
EnableViewState=true
数据列表显示字段

[errors] 视图表明访问数据源时出现问题:

编号
名称
类型
属性
角色
1
HTMLErrors
变量
 
用于显示错误的 HTML 代码

该应用程序的三个视图是同一页面 [main.aspx] 内的三个不同容器(面板)。其呈现代码如下:


<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
    </HEAD>
    <body>
        <P>Liaison de données avec un DataList</P>
        <HR width="100%" SIZE="1">
        <form runat="server" ID="Form1">
            <asp:Panel Runat="server" ID="bandeau">
                <P>Choisissez votre style :
                    <asp:RadioButton id="RadioButton1" runat="server" EnableViewState="False" Text="1" GroupName="rdstyle"
                        Checked="True"></asp:RadioButton>
                    <asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" Text="2" GroupName="rdstyle"></asp:RadioButton>
                    <asp:Button id="btnChanger" runat="server" EnableViewState="False" Text="Changer"></asp:Button></P>
                <HR width="100%" SIZE="1">
            </asp:Panel>
            <asp:Panel id="vueRésultats1" runat="server">
                <P>
                    <asp:DataList id="DataList1" runat="server" BorderColor="Tan" BorderWidth="1px" BackColor="LightGoldenrodYellow"
                        CellPadding="2" RepeatDirection="Horizontal" RepeatColumns="4" ForeColor="Black">
                        <SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
                        <HeaderTemplate>
                            Contenu de la table [liste] de la base [produits]
                        </HeaderTemplate>
                        <AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
                        <ItemTemplate>
                            nom :
                            <%# Container.DataItem("nom") %>
                            <br>
                            prix :
                            <%# databinder.eval(Container.DataItem,"prix","{0:C}") %>
                            <br>
                        </ItemTemplate>
                        <FooterStyle BackColor="Tan"></FooterStyle>
                        <HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
                    </asp:DataList></P>
            </asp:Panel>
            <asp:Panel id="vueRésultats2" runat="server">
                <P>
                    <asp:DataList id="DataList2" runat="server">
                        <HeaderTemplate>
                            Contenu de la table [liste] de la base [produits]
                            <HR width="100%" SIZE="1">
                        </HeaderTemplate>
                        <AlternatingItemStyle BackColor="Teal"></AlternatingItemStyle>
                        <SeparatorStyle BackColor="LightSkyBlue"></SeparatorStyle>
                        <ItemStyle BackColor="#C0C000"></ItemStyle>
                        <ItemTemplate>
                            nom :
                            <%# Container.DataItem("nom") %>
                            , prix :
                            <%# databinder.eval(Container.DataItem,"prix","{0:C}") %>
                            <BR>
                        </ItemTemplate>
                        <SeparatorTemplate>
                            <HR width="100%" SIZE="1">
                        </SeparatorTemplate>
                        <HeaderStyle BackColor="#C0C0FF"></HeaderStyle>
                    </asp:DataList></P>
            </asp:Panel>
            <asp:Panel id="vueErreurs" runat="server">
                <P>Les erreurs suivantes se sont produites :</P>
                <%= erreursHTML %>
            </asp:Panel>
        </form>
    </body>
</HTML>

8.7.4. 配置 [DataList] 组件

让我们来看看 [DataList] 组件的各种属性。这些属性数量众多,本文仅选取其中一小部分进行介绍。您可以在 [DataList] 中定义多达七个显示模板:

HeaderTemplate
[DataList] 标题模板
ItemTemplate
用于显示关联数据列表中各项的行模板。仅此模板为必填项。
AlternatingItemTemplate
为了在视觉上区分连续显示的项目,可以使用两个模板:ItemTemplate 用于第 n 个项目,AlternatingItemTemplate 用于第 n+1 个项目
SelectedItemTemplate
用于 [DataList] 中所选项的模板
SeparatorTemplate
用于 [DataList] 中两个元素之间分隔符的模板
EditItemTemplate
[DataList] 允许您编辑其显示的值。[EditItemTemplate] 是 [DataList] 中当前处于“编辑”模式的项目的模板
FooterTemplate
[DataList] 页脚的模板

[DataList1] 组件使用 [WebMatrix] 构建。在其属性窗口中,已选中 [AutoFormat] 链接:

1234567

上文中的 [Color 5] 架构将生成一个 [DataList],并为以下模板应用样式:HeaderTemplate (1)、ItemTemplate (2, 6)、AlternatingTemplate (3, 5)、SelectedItemTemplate (4)、FooterTemplate (7)。生成的代码如下:


                    <asp:DataList id="DataList1" runat="server" BorderColor="Tan" BorderWidth="1px" BackColor="LightGoldenrodYellow"
                        CellPadding="2" ForeColor="Black">
                        <SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
                        <AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
                        <FooterStyle BackColor="Tan"></FooterStyle>
                        <HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
                    </asp:DataList></P>

尚未定义任何模板。这需要我们来完成。我们定义以下模板:

HeaderTemplate

                        <HeaderTemplate>
                            [products] 数据库中 [list] 表的内容
                        </HeaderTemplate>
ItemTemplate

                        <ItemTemplate>
                            名称:
                            <%# Container.DataItem("name") %>
                            
                            价格:
                            <%# DataBinder.Eval(Container.DataItem,"price","{0:C}") %>
                            
                        </ItemTemplate>

请记住,[ItemTemplate] 模板用于显示与 [DataList] 关联的数据源中的项目。该数据源是一组数据行,每行包含一个或多个值。数据源中的当前行由 [Container.DataItem] 对象表示。此类行包含列。 [Container.DataItem("col1")] 是当前行中“col1”列的值。要在呈现代码中包含此值,我们编写 <%# Container.DataItem("col") %>。有时,我们希望以特殊格式显示当前行中的某个元素。在此,我们希望以欧元显示当前行的“price”列。 我们使用 [DataBinder.Eval] 函数,该函数接受三个参数:

  • 当前行 [Container.DataItem]
  • 要格式化的列名
  • 格式化字符串采用 {0:format} 形式,其中 [format] 是 [string.format] 方法支持的格式之一。

因此,代码 <%# DataBinder.Eval(Container.DataItem,"price",{0:C}) %> 将以货币格式(格式 C=货币)显示当前行中的 [price] 列。

因此,我们将得到一个如下所示的 [DataList]:

Image

上文中的数据已按每行四个数据项进行排列。这是通过以下 [DataList] 属性实现的:

RepeatDirection
水平
RepeatColumns
所需列数

最终,[DataList1] 的代码即为上文代码片段中所示。关于 [DataList2] 的呈现代码,我们留待读者自行研读。与 [DataGrid] 组件类似,[DataList] 的大多数属性均可通过 [WebMatrix] 向导进行设置。要执行此操作,请使用 [DataList] 属性窗口中的 [属性生成器] 链接:

8.7.5. 控制器

[global.asax, global.asax.vb] 控制器如下所示:

[global.asax]

<%@ Application src="global.asax.vb" inherits="Global" %>

[global.asax.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports System.Collections
 
Public Class Global
    Inherits System.Web.HttpApplication
 
    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' create a product object
        Try
            Dim données As DataSet = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection")).getDataSet("select * from LISTE")
            ' put the object in the application
            Application("données") = données
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

当应用程序启动(Application_Start)时,我们会从 [products] 业务类创建一个 [dataset],并将其添加到应用程序中,以便所有客户端的所有请求都能使用它。如果在此创建过程中发生异常,则会在应用程序中记录该异常。[Application_Start] 过程仅运行一次。此后,[global.asax] 控制器将不再参与。 随后,[main.aspx.vb] 控制器将处理相关工作:


Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
 
Public Class main
    Inherits System.Web.UI.Page
 
    ' components page
    Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
    Protected WithEvents DataList1 As System.Web.UI.WebControls.DataList
    Protected WithEvents DataList2 As System.Web.UI.WebControls.DataList
    Protected WithEvents vueRésultats1 As System.Web.UI.WebControls.Panel
    Protected WithEvents vueRésultats2 As System.Web.UI.WebControls.Panel
    Protected WithEvents RadioButton1 As System.Web.UI.WebControls.RadioButton
    Protected WithEvents RadioButton2 As System.Web.UI.WebControls.RadioButton
    Protected WithEvents btnChanger As System.Web.UI.WebControls.Button
    Protected WithEvents bandeau As System.Web.UI.WebControls.Panel
    ' data page
    Protected erreursHTML As String
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' check for application errors
        If CType(Application("erreur"), Boolean) Then
            ' the application has not initialized correctly
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs, False)
            Exit Sub
        End If
        '1st request
        If Not IsPostBack Then
            ' initialize controls
            With DataList1
                .DataSource = CType(Application("données"), DataSet)
                .DataBind()
            End With
            With DataList2
                .DataSource = CType(Application("données"), DataSet)
                .DataBind()
            End With
            ' the empty form is displayed
            afficheRésultats(True, False)
        End If
    End Sub
 
    Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
        ' displays the error view
        erreursHTML = ""
        For i As Integer = 0 To erreurs.Count - 1
            erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
        ' the [errors] view is displayed
        vueErreurs.Visible = True
        vueRésultats1.Visible = False
        vueRésultats2.Visible = False
        bandeau.Visible = False
    End Sub
 
    Private Sub afficheRésultats(ByVal visible1 As Boolean, ByVal visible2 As Boolean)
        ' the [results] view is displayed
        vueRésultats1.Visible = visible1
        vueRésultats2.Visible = visible2
        vueErreurs.Visible = False
    End Sub
 
    Private Sub btnChanger_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnChanger.Click
        ' change the style
        afficheRésultats(RadioButton1.Checked, RadioButton2.Checked)
    End Sub
End Class

8.8. Repeater 组件与数据绑定

[Repeater] 组件允许您针对数据列表中的每个项目重复 HTML 代码。假设我们希望以以下格式显示错误列表:

Image

我们之前曾遇到过这个问题,并通过在呈现代码中使用 <% =erreursHTML %> 这种形式来解决,其中 erreursHTML 的值由控制器计算得出。该值包含 HTML 代码,具体来说是一个列表的 HTML 代码。其缺点是,若要修改此 HTML 列表的呈现效果,就必须进入控制器部分,这违背了控制器与呈现的分离原则。[Repeater] 组件提供了一个解决方案。 与 [DataList] 类似,我们可以定义 <HeaderTemplate> 用于页眉、<ItemTemplate> 用于数据列表中的当前项,以及 <FooterTemplate> 用于数据结尾。在此,我们可以为 [Repeater] 组件定义如下:

            <asp:Repeater id="Repeater1" runat="server" EnableViewState="False">
                <HeaderTemplate>
                    Les erreurs suivantes se sont produites :
                    <ul>
                </HeaderTemplate>
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
                <FooterTemplate>
                    </ul>
                </FooterTemplate>
            </asp:Repeater>

请注意,如果数据源包含多列,[Container.DataItem] 代表一行数据;如果数据源仅有一列,则代表单个数据点。本例中即属于后者。例如,我们正在构建以下应用程序:

Image

页面布局代码如下:

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Liaison de données avec un composant [Repeater]
        </p>
        <hr />
        <p>
            <asp:Repeater id="Repeater1" runat="server" EnableViewState="False">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
                <HeaderTemplate>
                    Les erreurs suivantes se sont produites :
                    <ul>
                </HeaderTemplate>
                <FooterTemplate>
                    </ul>
                </FooterTemplate>
            </asp:Repeater>
        </p>
    </form>
</body>
</html>

控件代码如下:

<%@ Page Language="VB" %>
<script runat="server">

    ' procedure executed when the page is loaded
    Sub page_Load(sender As Object, e As EventArgs)
        if not IsPostBack then
          ' create a data source
          with Repeater1
              .DataSource=createDataSource
              .DataBind
          end with
        end if
    End Sub

    function createDataSource as ArrayList
      ' create an arraylist
      dim erreurs as new ArrayList
      dim i as integer
      for i=0 to 5
        erreurs.add("erreur-"+i.ToString)
      next
      return erreurs
    end function

</script>
<html>
...
</html>

在客户端的首次请求中,我们将一个 [ArrayList] 对象与 [Repeater] 组件关联,该组件用于显示错误列表。

8.9. 应用程序

在此,我们将重新审视一个先前使用服务器端组件实现的应用程序。该应用程序允许用户模拟税费计算。它依赖于一个 [impot] 类,此处不再赘述。该类需要从 OLEDB 数据源中检索数据。在本示例中,我们将使用 ACCESS 数据源。在此版本中,我们引入了以下新功能:

  • 使用验证组件检查数据有效性
  • 使用与数据源关联的服务器组件来显示结果

8.9.1. 应用程序的 MVC 结构

该应用程序的 MVC 结构如下:

三个视图将作为容器嵌入到控制器 [main.aspx] 的呈现代码中。因此,该应用程序仅包含一个页面 [main.aspx]。

8.9.2. 应用程序的视图

[form]视图是用于输入计算用户税款所需信息的表单:

Image

用户填写表单:

Image

他们使用 [Submit] 按钮请求税额计算。随后将看到以下 [simulations] 视图:

Image

用户通过上方的链接返回表单。表单将保持其填写时的状态。用户可能在输入数据时出错:

Image

这些错误会在 [表单] 视图中被标记出来:

Image

随后用户更正错误。他们可以运行新的模拟:

Image

这将显示包含一项新增模拟的 [模拟] 视图:

Image

最后,如果数据源不可用,系统将在[错误]视图中通知用户:

Image

8.9.2.1. 呈现代码

请记住,[main.aspx] 页面整合了所有视图。它是一个包含三个容器的单一表单:

  • 用于 [form] 视图的 [panelform]
  • [panelerrors] 用于 [errors] 视图
  • [panelsimulations] 用于 [simulations] 视图

接下来我们将详细说明这三个容器的组成。 [panelform] 容器的视觉呈现如下:

编号
名称
类型
属性
角色
0
面板表单
面板
 
表单视图
1
rd是
rdNo
单选按钮
组名=rdmarie
单选按钮
2
cvMarie
CustomValidator
ErrorMessage=您未指定婚姻状况
EnableClientScript=false
检查客户端程序是否发送了预期的婚姻状况
3
txtChildren
文本框
 
子女数量
4
rfvChildren
必填字段验证器
ErrorMessage=请输入子女数量
ControlToValidate=txtChildren
检查 [txtChildren] 字段是否为空
5
rvChildren
范围验证器
ErrorMessage=请输入 1 到 30 之间的数字
ControlToValidate=txtChildren
检查 [txtChildren] 字段是否在 [1,30] 范围内
6
txtSalary
文本框
EnableViewState=true
年薪
7
rfvSalary
必填字段验证器
ControlToValidate=txtSalary
ErrorMessage=请输入您的年薪
检查 [txtSalary] 字段是否为空
8
revSalary
正则表达式验证器
ControlToValidate=txtSalary
ErrorMessage=薪资无效
正则表达式=\s*\d+\s*
检查 [txtSalary] 字段是否为一串数字
9
btnCalculate
按钮
ValidationTriggers=true
表单上的 [submit] 按钮 - 开始计算税款
10
btnClear
按钮
Validation=false
表单 [提交] 按钮 - 清空表单

您可能会对 [cvMarié] 检查感到惊讶,它用于验证用户是否选择了两个单选按钮中的一个。这是必要的,因为无法确定用户是否正在使用服务器发送的表单。由于无法保证这一点,我们必须验证所有提交的参数。 另请注意 [btnEffacer] 按钮的 [CausesValidation=false] 属性。当用户点击此按钮时,提交的数据不应被检查,因为它将被忽略。

容器中的所有组件均设置了 [EnableViewState=false] 属性,但 [panelForm, txtEnfants, txtSalaire, rdOui, rdNon] 除外。[panelerreurs] 容器的视觉呈现如下:

否。
名称
类型
属性
角色
0
错误面板
面板
EnableViewState=false
错误视图
1
rptErrors
重复器
EnableViewState=false
显示错误列表

[rptErrors] 组件定义如下:


                <asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
                    <ItemTemplate>
                        <li>
                            <%# Container.Dataitem%>
                        </li>
                    </ItemTemplate>
                    <HeaderTemplate>
                        Les erreurs suivantes se sont produites
                        <ul>
                    </HeaderTemplate>
                    <FooterTemplate>
                        </ul>
                    </FooterTemplate>
                </asp:Repeater>

与 [rptErrors] 组件关联的数据列表将是一个 [ArrayList] 对象,其中包含一组错误消息。因此,如上所示,<%# Container.Dataitem%> 指代当前的错误消息。[panelsimulations] 容器的可视化表示如下:

编号
名称
类型
属性
角色
0
面板模拟
面板
EnableViewState=false
模拟视图
2
lnkForm2
链接按钮
EnableViewState=false
链接到表单
1
dgSimulations
DataGrid
EnableViewState=false
负责显示放置在 [DataTable] 对象中的模拟数据

在 [Webmatrix] 中,[dgSimulations] 组件的配置如下。在 [dgSimulations] 属性窗口中,我们选择 [AutoFormat] 链接并选择 [Color 3] 配色方案:

接着,仍在 [dgSimulations] 属性窗口中,我们点击 [Property Generator] 链接。我们要求显示列标题:

Image

我们选择 [Columns] 选项来定义 [DataGrid] 的四个列:

Image

取消勾选 [自动创建列...] 选项。我们将自行定义 [DataGrid] 的四个列。这将关联一个包含四个列的 [DataTable] 对象,其列名分别为“已婚”、“子女”、“薪资”和“税款”。 要在 [DataGrid] 中创建列,我们选择 [相关列] 选项,并使用如上图所示的创建按钮。列创建完成后,我们可以定义其属性。模拟表的第一列将包含来自 [DataTable] 对象的“已婚”列,该对象将与 [DataGrid] 相关联。在向导中,[DataGrid] 的第一列定义如下:

Image

标题文本
列标题,此处为“已婚”
数据字段
数据源中该列的名称,该名称将由 [DataGrid] 的此列显示。此处即 [DataTable] 中的“married”列。

第二列的定义如下:

表头文本
子女
数据字段
children

第三列定义如下:

表头文本
年薪
数据字段
salary
格式化表达式
{0:C} - 货币显示格式,用于获取欧元符号

第四列定义如下:

标题文本
税额
数据字段
格式化表达式
{0:C} - 货币显示格式,用于显示欧元符号

我们将呈现代码和控件代码分别放在两个独立的文件中。第一个文件位于 [main.aspx],第二个位于 [main.aspx.vb]。 [main.aspx] 的代码如下:


<%@ page src="main.aspx.vb" inherits="main" AutoEventWireUp="false" %>
<HTML>
    <HEAD>
        <title>Calculer votre impôt</title>
    </HEAD>
    <body>
        <P>Calculer votre impôt</P>
        <HR width="100%" SIZE="1">
        <FORM id="Form1" runat="server">
 
            <asp:panel id="panelform" Runat="server">
                <TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
                    <TR>
                        <TD height="19">Etes-vous marié(e)</TD>
                        <TD height="19">
                            <asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
                            <asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non
                            <asp:CustomValidator id="cvMarie" runat="server" ErrorMessage="Vous n'avez pas indiqué votre état marital"
                                Display="Dynamic" EnableClientScript="False" Visible="False" EnableViewState="False"></asp:CustomValidator></TD>
                    </TR>
                    <TR>
                        <TD>Nombre d'enfants</TD>
                        <TD>
                            <asp:TextBox id="txtEnfants" runat="server" Columns="3" MaxLength="3"></asp:TextBox>
                            <asp:RequiredFieldValidator id="rfvEnfants" runat="server" ErrorMessage="Indiquez le nombre d'enfants" Display="Dynamic"
                                ControlToValidate="txtEnfants" EnableViewState="False"></asp:RequiredFieldValidator>
                            <asp:RangeValidator id="rvEnfants" runat="server" ErrorMessage="Tapez un nombre entre 1 et 30" Display="Dynamic"
                                ControlToValidate="txtEnfants" Type="Integer" MaximumValue="30" MinimumValue="0" EnableViewState="False"></asp:RangeValidator></TD>
                    </TR>
                    <TR>
                        <TD>Salaire annuel (euro)</TD>
                        <TD>
                            <asp:TextBox id="txtSalaire" runat="server" Columns="10" MaxLength="10"></asp:TextBox>
                            <asp:RequiredFieldValidator id="rfvSalaire" runat="server" ErrorMessage="Indiquez le montant de votre salaire"
                                Display="Dynamic" ControlToValidate="txtSalaire" EnableViewState="False"></asp:RequiredFieldValidator>
                            <asp:RegularExpressionValidator id="revSalaire" runat="server" ErrorMessage="Salaire invalide" Display="Dynamic"
                                ControlToValidate="txtSalaire" ValidationExpression="\s*\d+\s*" EnableViewState="False"></asp:RegularExpressionValidator></TD>
                    </TR>
                </TABLE>
                <P>
                    <asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
                    <asp:Button id="btnEffacer" runat="server" Text="Effacer" CausesValidation="False"></asp:Button></P>
            </asp:panel>
 
             <asp:panel id="panelerreurs" runat="server" EnableViewState="False">
                <asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
                    <ItemTemplate>
                        <li>
                            <%# Container.Dataitem%>
                        </li>
                    </ItemTemplate>
                    <HeaderTemplate>
                        Les erreurs suivantes se sont produites
                        <ul>
                    </HeaderTemplate>
                    <FooterTemplate>
                        </ul>
                    </FooterTemplate>
                </asp:Repeater>
            </asp:panel>
 
          <asp:panel id="panelsimulations" runat="server" EnableViewState="False">
                <P>
                    <asp:DataGrid id="dgSimulations" runat="server" BorderColor="#DEBA84" BorderStyle="None" CellSpacing="2"
                        BorderWidth="1px" BackColor="#DEBA84" CellPadding="3" AutoGenerateColumns="False" EnableViewState="False">
                        <SelectedItemStyle Font-Bold="True" ForeColor="White" BackColor="#738A9C"></SelectedItemStyle>
                        <ItemStyle ForeColor="#8C4510" BackColor="#FFF7E7"></ItemStyle>
                        <HeaderStyle Font-Bold="True" ForeColor="White" BackColor="#A55129"></HeaderStyle>
                        <FooterStyle ForeColor="#8C4510" BackColor="#F7DFB5"></FooterStyle>
                        <Columns>
                            <asp:BoundColumn DataField="mari&#233;" HeaderText="Mari&#233;"></asp:BoundColumn>
                            <asp:BoundColumn DataField="enfants" HeaderText="Enfants"></asp:BoundColumn>
                            <asp:BoundColumn DataField="salaire" HeaderText="Salaire annuel" DataFormatString="{0:C}"></asp:BoundColumn>
                            <asp:BoundColumn DataField="imp&#244;t" HeaderText="Montant de l'imp&#244;t" DataFormatString="{0:C}"></asp:BoundColumn>
                        </Columns>
                        <PagerStyle HorizontalAlign="Center" ForeColor="#8C4510" Mode="NumericPages"></PagerStyle>
                    </asp:DataGrid></P>
                <P>
                    <asp:LinkButton id="lnkForm2" runat="server" EnableViewState="False">Retour au formulaire</asp:LinkButton></P>
            </asp:panel>
 
      </FORM>
    </body>
</HTML>

8.9.3. 应用程序的控件代码

应用程序的控件代码分布在 [global.asax.vb] 和 [main.aspx.vb] 文件中。[global.asax] 文件的定义如下:

<%@ Application src="Global.asax.vb" Inherits="Global" %>

[global.asax.vb] 文件内容如下:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
Imports System.Data
 
Public Class Global
    Inherits System.Web.HttpApplication
 
    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
 
    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' start of session - create a list of empty simulations
        Dim simulations As New DataTable("simulations")
        simulations.Columns.Add("marié", Type.GetType("System.String"))
        simulations.Columns.Add("enfants", Type.GetType("System.String"))
        simulations.Columns.Add("salaire", Type.GetType("System.Int32"))
        simulations.Columns.Add("impôt", Type.GetType("System.Int32"))
        Session.Item("simulations") = simulations
    End Sub
End Class

当应用程序启动(即收到对应用程序的首次请求)时,将执行 [Application_Start] 过程。该过程尝试通过从 OLEDB 数据源检索数据来创建一个 [tax] 类型的对象。若读者已忘记该类定义,建议查阅第 5 章。如果数据源不可用,[tax] 对象的创建可能会失败。 在此情况下,错误信息将存储在应用程序中,以便后续所有请求知晓该对象未能正确初始化。若创建成功,生成的 [impot] 对象也会存储在应用程序中,供所有税费计算请求使用。当客户端发出首次请求时,[Application_Start] 过程会为其创建一个会话。 该会话旨在存储用户将执行的各种税费计算模拟。这些数据将存储在与“simulations”会话键关联的 [DataTable] 对象中。会话启动时,该键将与一个结构已定义的空 [DataTable] 对象相关联。应用程序所需的信息保存在其配置文件 [wenConfig] 中:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\devel\aspnet\poly\webforms2\vs\impots6\impots.mdb" />
    </appSettings>
</configuration>

[connectionString] 键指定了连接到 OLEDB 数据源的连接字符串。其余的控件代码位于 [main.aspx.vb] 中:


Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System
Imports System.Web.UI.Control
Imports System.Data
 
Public Class main
    Inherits System.Web.UI.Page
 
    Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
    Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
    Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
    Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
    Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
    Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
    Protected WithEvents panelform As System.Web.UI.WebControls.Panel
    Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
    Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
    Protected WithEvents rfvEnfants As System.Web.UI.WebControls.RequiredFieldValidator
    Protected WithEvents rvEnfants As System.Web.UI.WebControls.RangeValidator
    Protected WithEvents rfvSalaire As System.Web.UI.WebControls.RequiredFieldValidator
    Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
    Protected WithEvents dgSimulations As System.Web.UI.WebControls.DataGrid
    Protected WithEvents revSalaire As System.Web.UI.WebControls.RegularExpressionValidator
    Protected WithEvents cvMarie As System.Web.UI.WebControls.CustomValidator
    Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
 
    ' local variables
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
    End Sub
 
    Private Sub afficheErreurs(ByRef erreurs As ArrayList)
...
    End Sub
 
    Private Sub afficheFormulaire()
...
    End Sub
 
    Private Sub afficheSimulations(ByRef simulations As DataTable, ByRef lien As String)
...
    End Sub
 
    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
....
    End Sub
 
    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
...
    End Sub
 
    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
    End Sub
 
    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
    End Sub
 
    Private Sub razForm()
...
    End Sub
 
    Private Sub cvMarie_ServerValidate(ByVal source As System.Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs)
...
    End Sub
End Class

该代码处理的第一个事件是 [Page_Load]:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' first, we look at the state of the application
        If CType(Application("erreur"), Boolean) Then
            ' the application failed to initialize
            ' the error view is displayed
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs)
            Exit Sub
        End If
        ' no errors - on the 1st request, the form is presented
        If Not IsPostBack Then afficheFormulaire()
    End Sub

在处理请求之前,我们会验证应用程序是否已正确初始化。如果未正确初始化,我们将使用 [displayErrors] 过程显示 [errors] 视图。如果这是第一次请求(IsPostBack=false),我们将使用 [displayForm] 显示 [form] 视图。

显示 [errors] 视图的存储过程如下:


    Private Sub afficheErreurs(ByRef erreurs As ArrayList)
        ' builds a list of errors
        With rptErreurs
            .DataSource = erreurs
            .DataBind()
        End With
        ' container display 
        panelerreurs.Visible = True
        panelform.Visible = False
        panelsimulations.Visible = False
        Exit Sub
    End Sub

该过程接收一个参数,即 [errors] 中类型为 [ArrayList] 的错误消息列表。我们只需将此数据源绑定到负责显示它的 [rptErrors] 组件即可。

显示 [form] 视图的存储过程如下:


    Private Sub afficheFormulaire()
        ' displays the form
        panelform.Visible = True
        ' the other containers are hidden
        panelerreurs.Visible = False
        panelsimulations.Visible = False
    End Sub

此过程仅将 [panelform] 容器设为可见。组件将显示其提交后的值或上一次的值(VIEWSTATE)。

当用户在 [form] 视图中单击 [Calculate] 按钮时,会向 [main.aspx] 发送一个 POST 请求。系统将执行 [Page_Load] 过程,随后执行 [btnCalculate_Click] 过程:


    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
        ' we check the validity of the data entered; if there are any errors, we report them
        If Not Page.IsValid Then
            afficheFormulaire()
            Exit Sub
        End If
        ' no errors - tax is calculated
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
        ' the result is added to existing simulations
        Dim simulations As DataTable = CType(Session.Item("simulations"), DataTable)
        Dim simulation As DataRow = simulations.NewRow
        simulation("marié") = CType(IIf(rdOui.Checked, "oui", "non"), String)
        simulation("enfants") = txtEnfants.Text.Trim
        simulation("salaire") = CType(txtSalaire.Text.Trim, Long)
        simulation("impôt") = impot
        simulations.Rows.Add(simulation)
        ' put the simulations in the session
        Session.Item("simulations") = simulations
        ' the simulations page is displayed
        afficheSimulations(simulations, "Retour au formulaire")
    End Sub

该过程首先检查页面的有效性。请注意,当 [btnCalculate_Click] 过程运行时,验证控件已完成其工作,且页面的 [IsValid] 属性已被设置。如果页面无效,则再次显示 [form] 视图,并结束该过程。 对于 [form] 视图中 [IsValid] 属性设置为 [false] 的验证组件,将显示其 [ErrorMessage] 属性。如果页面有效,则使用启动时存储在应用程序中的 [tax] 对象计算税额。此新的模拟将添加到已执行的模拟列表中,并存储在会话中。

最后,通过以下 [displaySimulations] 过程显示 [simulations] 视图:


    Private Sub afficheSimulations(ByRef simulations As DataTable, ByRef lien As String)
        ' we link the datagrid to the simulations source
        With dgSimulations
            .DataSource = simulations
            .DataBind()
        End With
        ' link
        lnkForm2.Text = lien
        ' the views
        panelsimulations.Visible = True
        panelerreurs.Visible = False
        panelform.Visible = False
    End Sub

该过程有两个参数:

  • [simulations] 中的模拟列表,类型为 [DataTable]
  • [link] 中的链接文本

[simulations] 数据源已绑定至负责显示它的组件。链接文本放置在视图中 [LinkButton] 对象的 [Text] 属性中。

当用户点击 [form] 视图中的 [Delete] 按钮时,将执行 [btnDelete_click] 过程(始终在 [Page_Load] 之后):


    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
        ' displays the empty form
        razForm()
        afficheFormulaire()
    End Sub
 
    Private Sub razForm()
        ' empty the form
        rdOui.Checked = False
        rdNon.Checked = True
        txtEnfants.Text = ""
        txtSalaire.Text = ""
    End Sub

上面的代码非常简单,无需任何注释。我们还需要处理对 [errors] 和 [simulations] 视图链接的点击:


    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
        ' displays the form
        afficheFormulaire()
    End Sub
 
    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
        ' displays the form
        afficheFormulaire()
    End Sub

这两个过程仅用于显示 [表单] 视图。我们知道,该视图中的字段将接收其提交值或其上一次的值。由于在此情况下,客户端的 POST 请求未发送任何表单字段值,因此这些字段将恢复为上一次的值。因此,表单将显示用户输入的值。

8.9.4. 测试

应用程序所需的所有文件都放置在名为 <application-path> 的文件夹中:
[bin] 文件夹中包含应用程序所需的 [import]、[importData] 和 [importOLEDB] 类所在的 DLL 文件:

如有需要,读者可参阅第 5 章,其中解释了如何创建上述提到的 [impot.dll] 文件。完成此操作后,使用参数 (<application-path>,/impots6) 启动 Cassini 服务器。然后,我们使用浏览器访问 URL [http://impots6/main.aspx]:

Image

若将 ACCESS 文件 [impots.mdb] 重命名为 [impots1.mdb],您将看到以下页面:

Image

8.9.5. 结论

我们构建了一个仅使用服务器端组件的 MVC 应用程序。借助这些组件,我们成功将应用程序的展示层与控制层完全分离。此前这尚不可行,因为展示代码中包含由控制代码计算的变量,其值中包含 HTML 代码。如今这种情况已不复存在。