Skip to content

8. 服务器组件 ASP - 2

8.1. Introduction

我们将继续研究用户界面,重点关注:

  • 数据验证组件
  • 如何将数据与服务器组件关联
  • 服务器组件 HTML

8.2. 数据验证组件

8.2.1. 简介

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

Image

收到该表单的值后,我们需要验证所输入数据的有效性:子女数量和工资必须为正整数或零。如果不符合条件,则将表单按用户输入的原样返回给客户端,并附带错误提示信息。我们通过两个视图处理了这种情况,一个用于上述表单,另一个用于错误提示:

Image

ASP.NET 提供了称为“验证组件”的功能,可用于检查以下情况:

组件
作用
RequiredFieldValidator
验证字段是否为空
CompareValidator
验证两个值是否相等
RangeValidator
验证一个值是否在两个边界之间
RegularExpressionValidator
验证字段是否符合正则表达式
CustomValidator
允许开发人员自定义验证规则——该组件可替代所有其他组件
ValidationSummary
将前面的控件发出的错误消息汇总到页面上的一个位置

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

8.2.2. RequiredFieldValidator

我们构建以下页面:

编号
名称
类型
属性
角色
1
txtNom
TextBox
EnableViewState=true
输入框
2
RequiredFieldValidator1
RequiredFieldValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=字段 [名称] 为必填项
验证组件
3
btnEnvoyer
Button
EnableViewState=false
验证原因=true
按钮 [submit]

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

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 函数。随后表单将提交至服务器,由服务器进行有效性验证。现在我们不指定名称,直接使用 [Envoyer] 按钮。服务器的响应如下:

Image

发生了什么?表单已提交至服务器。服务器执行了页面上所有验证组件的代码。 此处仅有一个组件。如果至少有一个验证组件检测到错误,服务器将按原样返回表单(实际上,对于带有 [EnableViewState=true] 的组件,还会为每个检测到错误的验证组件附加一条错误消息)。 该消息即为验证组件的 [ErrorMessage] 属性。上文所示正是该机制的运作过程。若我们在 [nom] 字段中填入内容,则在提交表单时错误消息将不再显示:

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文档进行适配。 市场上存在多种浏览器,它们的功能各不相同。例如,微软和网景的浏览器在处理接收到的文档时,采用的对象建模方式并不相同。因此,用于在浏览器端操作该文档的JavaScript代码在两者之间也存在差异。同样,浏览器厂商不断推出新版浏览器,持续提升其功能。 因此,IE5能够解析的HTML文档,IE3可能无法解析。上文即为服务器在客户端层面进行这种适配的一个示例。 出于本文未深入探讨的原因,Web 服务器判断客户端 [Mozilla] 不具备处理验证 JavaScript 代码的能力。因此,该代码未被放入发送给它的文档 HTML 中,验证操作在服务器端完成。 对于 [IE6],如我们所见,该 JavaScript 代码已被包含在发送的 HTML 文档中。为了观察其运行效果,让我们进行以下实验:

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

我们将获得以下新页面:

Image

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

Image

发生了什么?浏览器执行了验证的JavaScript代码。该代码判定页面有效。因此,浏览器将表单提交给了已停止运行的Web服务器。浏览器察觉到这一情况后,便显示了上图所示的页面。如果重启服务器,一切将恢复正常。

从这个例子中能得到什么启示?

  • 验证组件的概念具有重要意义,它能将所有填写错误的表单退回给客户端
  • [VIEWSTATE] 的优势,它能将表单按用户输入的原样返回
  • 服务器能够根据客户端进行自适应调整。客户端通过发送给服务器的 HTTP [User-Agent:] 头部标识自身。因此,并非由服务器“猜测”其面对的是何种客户端。 这种自适应能力对开发人员极具价值,因为他们无需关注应用程序所面对的客户端类型。

8.2.3. CompareValidator

我们构建以下页面:

Image

编号
名称
类型
属性
角色
1
cmbChoix1
DropDownList
EnableViewState=true
下拉列表
2
cmbChoix2
DropDownList
EnableViewState=true
下拉列表
3
CompareValidator1
CompareValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=选项 1 无效
比较值=?
运算符=不等于
类型=字符串
验证组件
4
CompareValidator2
CompareValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=选项 2 无效
比较控件=?
运算符=不等于
Type=string
验证组件
5
btnEnvoyer
Button
启用视图状态=false
验证原因=true
按钮 [submit]

[CompareValidator] 组件用于比较两个值。可用的运算符为 [Equal, NotEqual, LessThan, LessThanEqual, GreaterThan, GreaterThanEqual]。 第一个值由属性 [ControlToValidate] 确定,第二个值由 [ValueToCompare] 确定(如果前一个值需与常量比较),或由 [ControlToCompare] 确定(如果需与其他组件的值比较)。 组件 [CompareValidator] 的重要属性如下:

ControlToValidate
该字段的内容需由该组件进行验证
EnableClientScript
布尔值——设为真表示前一个字段的内容也需在客户端进行验证
ErrorMessage
检测到错误时组件应显示的错误消息
ValeurToCompare
用于与字段值进行比较的值[ControlToValidate]
ControlToCompare
用于与字段值进行比较的组件 [ControlToValidate]
Operator
两个值之间的比较运算符
Type
待比较值的类型

此页面的呈现代码如下:

<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] 具有以下属性:Operator=NotEqual, ControlToCompare=cmbChoix1, Type=string

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

Image

如果通过Internet Explorer 6访问同一页面,该页面中包含的JavaScript代码会导致其运行方式略有不同。一旦用户在下拉列表中更改了选项,系统会立即提示可能出现的错误。这提升了用户的操作体验。

8.2.4. CustomValidator, RangeValidator

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

Image

编号
名称
类型
属性
角色
1
cmbDiplomes
DropDownList
EnableViewState=true
下拉列表
2
CompareValidator2
CompareValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=证书无效
运算符=不等于
ValueToCompare=?
Type=String
控件 [1] 的验证组件
3
txtAutreDiplome
TextBox
EnableViewState=false
输入字段
4
CustomValidator1
CustomValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=学历精确度无效
客户端验证函数=chkAutreDiplome
字段验证组件 [3]
5
txtAnDiplome
TextBox
EnableViewState=false
输入字段
6
RangeValidator1
RangeValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=毕业年份必须在 [1990,2004] 范围内
MinValue=1990
MaxValue=2004
类型=整数
待验证控件=txtAnDiplome
字段验证组件 [5]
7
RequiredFieldValidator2
RequiredFieldValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=请填写毕业年份
待验证控件=txtAnDiplome
字段验证组件 [5]
8
btnEnvoyer
Button
启用视图状态=false
CausesValidation=true
按钮 [submit]

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

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

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

ControlToValidate
需由组件验证其值的字段
EnableClientScript
布尔值——为真时表示前一个字段的内容也需在客户端进行验证
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;处理</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)
        ' 如果 [cmbDiplomes]=QZXW2HTMLBW2F1dHJlXQZQ,则字段 [txtAutreDiplome] 必须不为空X
        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] 下拉列表中选择一个学历。如果该学历在列表中不存在,用户可以选择列表中的 [Autre] 选项。 此时,用户需在输入字段 [txtAutreDiplome] 中填写其学历。若在 [cmbDiplomes] 中选择了 [Autre],则字段 [txtAutreDiplome] 必须不为空。 如果在 [cmbDiplomes] 中既未选择 [Autre] 也未选择 [?],则字段 [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){
             // 验证字段 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. RegularExpressionValidator

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

Image

编号
名称
类型
属性
角色
1
txtMel
TextBox
EnableViewState=true
输入框
2
RequiredFieldValidator3
RequiredFieldValidator
ControlToValidate=txtMel
Display=Dynamic
控件验证组件 [1]
3
RegularExpressionValidator1
RegularExpressionValidator
待验证控件=txtMel
Display=Dynamic
控件的验证组件 [1]
4
btnEnvoyer
Button
启用视图状态=false
CausesValidation=true
按钮 [submit]

在此,我们需要验证电子邮件地址的格式。我们使用 [RegularExpressionValidator] 组件来实现,该组件可通过正则表达式验证字段的有效性。需要说明的是,正则表达式是一种字符串模式。因此,我们是根据该模式来验证字段的内容。 由于字段为空时其内容不会被验证,因此我们还需要一个 [RequiredFieldValidator] 组件来验证电子邮件地址是否为空。[RegularExpressionValidator] 类的属性与之前已研究的组件类类似:

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

电子邮件地址的正则表达式模板可能如下:\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] 控件显示。

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

Image

编号
名称
类型
属性
角色
1
ValidationSummary1
ValidationSummary
HeaderText=发生了以下错误,EnableClientScript=false,ShowSummary=true
显示页面中所有验证控件的错误
2
lnkErreursToFormulaire
LinkButton
CausesValidation=false
返回表单的链接

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

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

Image

1

编号
姓名
类型
属性
作用
1
lblInfo
标签
 
面向用户的提示信息

本页面的呈现代码如下:

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

以下是一些示例结果。我们验证视图 [formulaire] 而不输入任何值:

Image

点击按钮 [Envoyer] 得到以下结果:

Image

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

Image

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

Image

该页面的控制代码如下:

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

     ' 页面加载时执行的流程
    Sub page_Load(sender As Object, e As EventArgs)
        ' 在第一次请求时,显示视图 [formulaire]
        if not ispostback then
            afficheVues(true,false,false)
        end if
    end sub

    sub afficheVues(byval formulaireVisible as boolean, _
        erreursVisible as boolean, infosVisible as boolean)
         ' 视图集
        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)
        ' 显示表单视图
        afficheVues(true,false,false)
         ' 重新执行有效性检查
        Page.validate
    End Sub

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
        ' 页面是否有效?
        if not Page.IsValid then
            ' 显示视图 [erreurs]
            afficheVues(false,true,false)
        else
             ' 否则显示视图 [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] 过程 中,我们显示视图 [formulaire],其余视图则被隐藏。此操作仅在首次请求时执行。 该过程调用了一个名为 [afficheVues] 的实用程序过程,并向其传递三个布尔值,以决定是否显示这三个视图。
  • 当调用 [btnEnvoyer_Click] 过程时,数据验证已完成。[btnEnvoyer] 按钮通过 [CausesValidation=true] 属性强制执行此数据验证。 所有验证控件均已执行,且其 [IsValid] 属性已设置。该属性指示控件验证的数据是否有效。此外,页面本身的 [IsValid] 属性也已设置。 该属性仅在页面所有验证控件的 [IsValid] 属性均取值为 [vrai] 时,才设置为 [vrai]。 因此,[btnEnvoyer_Click] 过程首先会检查页面是否有效。如果无效,则显示视图 [erreurs]。 该视图包含控件 [ValidationSummary],该控件继承了所有控件的 [ErrorMessage] 属性(参见上文屏幕截图)。如果页面有效,则显示视图 [infos] 并附带一条信息提示。
  • 过程 [lnkErreursToFormulaire_Click] 负责在已验证的状态下显示视图 [formulaire]。 视图 [formulaire] 中所有具有属性 [EnableViewState=true] 的输入字段,其状态都会自动重新生成。奇怪的是,验证组件的状态却未被恢复。 实际上,当视图 [erreurs] 返回时,本应看到出错的控件显示其字段 [Text]。但实际情况并非如此。因此,我们通过调用页面上的方法 [Page.Validate] 强制执行了数据验证。 此操作必须在面板 [vueFormulaire] 显示后进行。因此总共进行了两次验证。在实际应用中应尽量避免这种情况。在此示例中,我们借此机会介绍了页面验证的新概念。

8.3. ListControl 组件与数据绑定

我们研究过的若干服务器组件支持显示值列表(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 标签如下:

DropDownList, ListBox
<option value="V">T</option>
CheckButtonList
<input type="checkbox" value="V">T
RadioButtonList
<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] 组件关联的方式会有所不同:

Array A
[ListControl].DataSource=A
[ListControl] 元素中的 [Text] 和 [Value] 字段将取 A 中的元素作为值
ArrayList 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
HashTable HT
[ListControl]。DataSource=HT , [ListControl].DataTextField="key", [ListControl]. DatavalueField ="value",其中 [key] 和 [value] 分别是 HT 的键和值。

我们通过以下 [databind1.aspx] 示例来实践这些信息:

这里有五个数据关联,每个关联包含四个控件,类型分别为 [DropDownList]、[ListBox]、[CheckBoxList] 和 [RadioButtonList]。这五个关联的区别在于其数据源:

连接
数据源
1
数组
2
ArrayList
3
DataTable
4
DataSet
5
HashTable

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] 过程中的实现如下:

' 全局数据
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

' 页面加载时执行的程序
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' 创建数据源,用于将数据与组件关联
      createDataSources
       ' 与数组建立关联 [Array]
      bindToArray
       ' 与列表建立关联 [ArrayList]
      bindToArrayList
       ' 链接到表 [DataTable]
      bindToDataTable
       ' 链接到数据组 [DataSet]
      bindToDataSet
       ' 与字典关联 [HashTable]
      bindToHashTable
    end if
End Sub

sub createDataSources
   ' 创建将与组件关联的数据源
  ' 数组列表
  dim i as integer
  for i=0 to textes.length-1
    myDataListe.add(textes(i))
  next
   ' 数据表
   ' 定义其两个列
  myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
  myDataTable.Columns.Add("texte",Type.GetType("System.String"))
  ' 填充表格
  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
   ' 数据集 - 仅包含一张表
  myDataSet.Tables.Add(myDataTable)
  ' 哈希表
  for i=0 to textes.length-1
    myHashTable.add(valeurs(i),textes(i))
  next
end sub

' 数组关联
sub bindToArray
         ' 与组件的关联
        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
        ' 元素选择
        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 类型的数据源

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

' 全局数据
dim textes() as string={"un","deux","trois","quatre"}
dim myDataListe as new ArrayList
..

' 页面加载时执行的程序
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' 创建将与组件关联的数据源
      createDataSources
...
    end if
End Sub

sub createDataSources
   ' 创建将与组件关联的数据源
  ' 数组列表
  dim i as integer
  for i=0 to textes.length-1
    myDataListe.add(textes(i))
  next
...
end sub

在控制代码的[bindToArrayList]过程 中,连接2中的四个控件[ListBox]的连接方式如下:

' 绑定数组列表
sub bindToArrayList
         ' 与组件的关联
        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
        ' 元素选择
        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] 中被初始化:

' 全局数据
dim textes() as string={"un","deux","trois","quatre"}
dim myDataTable as new DataTable("table1")
...

' 页面加载时执行的程序
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' 创建将与组件关联的数据源
      createDataSources
...
    end if
End Sub

sub createDataSources
   ' 创建将与组件关联的数据源
...
   ' 数据表
   ' 定义其两个列
  myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
  myDataTable.Columns.Add("texte",Type.GetType("System.String"))
  ' 填充数据表
  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] 和 [texte]。 列 [id] 将为 [ListControl] 元素的字段 [Value] 提供数据,而列 [texte] 则为其字段 [Text] 提供数据。 包含两列的 [DataTable] 的构建方式如下:

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

因此创建一个两列的表:

  • 第一个名为“id”的字段为整数型
  • 第二个名为“文本”,类型为字符串

表结构创建完成后,我们可以使用以下代码填充数据:

  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],而 [texte] 列将包含数组 [data] 的值。 完成上述操作后,表 [dataListe] 即已填充完毕。在控制代码的 [bindToDataTable] 过程 中,连接 3 中的四个控件 [ListBox] 的连接方式如下:

sub bindToDataTable
         ' 与组件关联
        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
        ' 选择元素
        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] 字段,而 [texte] 列将填充其 [Text] 字段。

8.3.5. 数据源类型为 DataSet

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

' 全局数据
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
...


' 页面加载时执行的程序
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' 创建将与组件关联的数据源
      createDataSources
...
    end if
End Sub

sub createDataSources
   ' 创建将与组件关联的数据源
   ' 数据集 - 仅包含一张表
  myDataSet.Tables.Add(myDataTable)
...
end sub

一个 [DataSet] 对象代表一组 [DataTable] 类型的表。 将之前构建的表 [myDataTable] 添加到 [DataSet] 中。在控件代码的 [bindToDataSet] 过程 中,连接 4 中的四个控件 [ListBox] 的连接方式如下:

sub bindToDataSet
         ' 与组件的关联
        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
        ' 元素选择
        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] 字段,而 [texte] 列将填充其 [Text] 字段。

8.3.6. 数据源类型为 HashTable

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

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

' 页面加载时执行的程序
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' 创建将与组件关联的数据源
      createDataSources
...
    end if
End Sub

sub createDataSources
   ' 创建将与组件关联的数据源
...
  ' 哈希表
  for i=0 to textes.length-1
    myHashTable.add(valeurs(i),textes(i))
  next
end sub

字典 [myHashTable] 可以视为一个包含两列的表格,分别称为“键”和“值”。[key] 列代表字典的键,[value] 列则代表与这些键关联的值。 此处的 [key] 列由数组 [valeurs] 的内容组成,而 [value] 列由数组 [textes] 的内容组成。 该数据源与控件的关联是在过程 [bindToHashTable] 中完成的:

sub bindToHashTable
         ' 与组件的关联
        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
        ' 元素选择
        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 中。但“System.Data”(其中包含类 [DataTable] 和 [DataSet])则不在此列。 因此需要导入该类。操作方法如下:

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

8.4. DataGrid 组件与数据绑定

[DataGrid]组件不仅能以表格形式显示数据,其功能远不止于此:

  • 它支持对表格的“视觉呈现”进行精细化配置
  • 支持更新数据源

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

8.4.1. 显示数组数据源:ArrayList、DataTable、DataSet

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

DataSource
数据源 [Array]、[ArrayList]、[DataTable]、[DataSet]、...
DataMember
当数据源为 [DataSet] 时,表示用作数据源的表名。此时,实际的数据源即为该表。若此字段未填写,则显示 [DataSet] 中的所有表。

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

Image

该页面包含四个 [DataGrid] 组件,这些组件是基于 [WebMatrix] 按以下方式构建的。将组件拖放到 [Design] 选项卡中的指定位置:

Image

随后将绘制一个通用表格HTML。 [DataGrid] 的属性可在设计阶段进行定义。此处我们将对其格式属性进行设置。为此,请选中待配置的 [DataGrid]。其属性将显示在右下角的窗口中:

Image

我们将使用上述两个链接。链接 [Générateur de propriétés] 可访问 [DataGrid] 的主要属性:

Image

对于四个 [DataGrid] 组件,我们取消勾选 [Afficher l'en-tête] 选项,并提交页面。另一个链接 [Mise en forme automatique] 允许为将要显示的 HTML 表格从多种样式中进行选择:

Image

我们为编号为 i 的 [DataGrid] 选择 [couleur 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">

     ' 总体数据
        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

        ' 页面加载时执行的程序
        Sub page_Load(sender As Object, e As EventArgs)
            if not IsPostBack then
              ' 创建将与组件关联的数据源
              createDataSources
               ' 与数组建立关联 [Array]
              bindToArray
               ' 与列表建立关联 [ArrayList]
              bindToArrayList
               ' 链接到一张表 [DataTable]
              bindToDataTable
               ' 链接到数据组 [DataSet]
              bindToDataSet
            end if
        End Sub

        sub createDataSources
           ' 创建将与组件关联的数据源
          ' 数组列表
          dim i as integer
          for i=0 to textes1.length-1
            myDataListe.add(textes1(i))
          next
           ' 数据表
           ' 定义其两个列
          myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
          myDataTable.Columns.Add("texte1",Type.GetType("System.String"))
          myDataTable.Columns.Add("texte2",Type.GetType("System.String"))
          ' 填充表格
          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
           ' 数据集 - 仅包含一张表
          myDataSet.Tables.Add(myDataTable)
        end sub

        ' 数组关联
        sub bindToArray
          with DataGrid1
            .DataSource=textes1
            .DataBind
          end with
        end sub

        ' 数组列表关联
        sub bindToArrayList
          with DataGrid2
            .DataSource=myDataListe
            .DataBind
          end with
        end sub

        ' 数据表关联
        sub bindToDataTable
          with DataGrid3
            .DataSource=myDataTable
            .DataBind
          end with
        end sub

        ' 数据集关联
        sub bindToDataSet
          with DataGrid4
            .DataSource=myDataSet
            .DataBind
          end with
        end sub

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

该代码与前面的示例非常相似,因此我们不再特别说明。不过,请注意数据绑定的实现方式。对于这四个控件中的每一个,只需使用以下代码序列即可:

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

其中 [source de données] 的类型为 [Array]、[ArrayList]、[DataTable] 或 [DataSet]。 由此可见,这是一个强大的表格数据展示工具:

  • 在设计阶段,使用 [WebMatrix] 或任何其他 IDE 进行格式设置
  • 数据绑定在代码中完成。若数据源为 SQL 服务器数据库或 ACCESS 数据库,则可通过 [WebMatrix] 在设计阶段完成数据绑定。 在这种情况下,需在工作表中放置一个类型为 [SqlDataSource] 或 [AccessDataSource] 的组件。 该组件可在设计时根据具体情况连接到物理数据源,即 SQL 服务器数据库或 ACCESS 数据库。 如果将一个已连接到物理源的 [SqlDataSource] 或 [AccessDataSource] 对象分配给 [DataGrid] 组件的 [DataSource] 属性,那么在设计模式下, [DataGrid] 组件中将显示实际数据。

8.5. ViewState 数据列表组件

本文旨在阐明数据列表组件的 [VIEWSATE] 机制。在两个客户端请求之间维持数据列表组件的状态时,可选择以下两种方法之一:

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

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

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

[DataGrid1] 组件由 [VIEWSTATE] 机制维护。 我们想了解的是,该机制在每次请求时都会重新生成 [DataGrid1] 的显示内容,是否也会重新生成其数据源。为此,该数据源与组件 [DataGrid2] 相关联。 该组件在每次请求时通过与 [DataGrid1] 数据源的显式关联来生成。它还拥有指向 [EnableViewState] 的属性,该属性又关联到 [vrai]。

该应用程序的呈现代码 [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
        ' 在首次查询时定义数据源并将其关联到第一个数据网格
        If Not IsPostBack Then
            '定义数据源
            With DataGrid1
                .DataSource = createDataSource()
                .DataBind()
            End With
        End If
        ' 在每次查询时,将数据网格2与第一个数据网格的数据源关联
        With Datagrid2
            .DataSource = DataGrid1.DataSource
            .DataBind()
        End With
    End Sub

    Private Function createDataSource() As DataTable
        ' 初始化数据源
        Dim thèmes As New DataTable
        ' 列
        With thèmes.Columns
            .Add("id", GetType(System.Int32))
            .Add("thème", GetType(System.String))
            .Add("description", GetType(System.String))
        End With
        ' id 列将作为主键
        thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
        ' 行
        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

合乎逻辑的是,这两个组件都显示了它们所关联的数据源。我们使用按钮 [Envoyer] 向服务器发起 [PostBack] 请求。此时显示的视图如下:

Image

我们注意到,[DataGrid1]组件保留了其值,但[DataGrid2]组件则未保留。原因如下:

  • 在 [Page_Load] 过程启动之前,由于 [viewstate] 的机制,[DataGrid1] 和 [DataGrid2] 这两个对象已恢复到上一次请求时的值。 事实上,它们的 [EnableViewState] 属性均已设置为 [vrai]。
  • 此时,[Page_Load] 过程开始执行。由于这是一项 [PostBack] 操作,因此 [DataGrid1] 组件不会被 [Page_Load] 修改(参见代码)。 因此,它保留了通过 [viewstate] 检索到的值。上图即展示了这一点。
  • 而组件 [DataGrid2] 通过 [DataBind] 连接到了数据源 [DataGrid1.DataSource]。因此它被重建,而它刚刚通过 [viewstate] 获取的值则丢失了。 因此,若将其属性从 [EnableViewState] 改为 [faux],将有助于避免对其状态进行不必要的管理。 上图显示 [DataGrid2] 被关联到一个空源。由于该源是 [DataGrid1.DataSource], 因此可以推断:虽然 [viewstate] 的机制确实恢复了组件 [DataGrid1] 的显示,但并未恢复其诸如 [DataSource] 之类的属性。

从这个例子中能得出什么结论?如果数据容器在每次请求时都需要与数据源建立关联(DataBind),则应避免将其属性[EnableViewState]设置为[vrai]。 然而,在某些情况下,即使在此情境下,也必须将容器的属性 [EnableViewState] 保持为 [vrai],否则希望处理的事件将无法触发。我们稍后将遇到一个相关示例。

通常,数据容器的数据源会随着请求的变化而变化。因此,每次请求时都需要将容器与数据源建立关联。通常,数据源会被置于会话中,以便请求能够访问它。我们的第二个示例展示了这一机制。该应用程序仅提供一个视图:

编号
名称
类型
属性
角色
1
DataGrid1
DataGrid
EnableViewState=true
显示数据源 S
2
DataGrid2
DataGrid
EnableViewState=false
显示与 [DataGrid1] 相同的数据源
3
Button1
Button
EnableViewState=false
按钮 [submit]
4
lblInfo1
lblInfo2
lblInfo3
标签
EnableViewState=false
信息文本

组件 [DataGrid1] 仅在首次查询时与数据相关联。借助 [viewstate] 的机制,它将在后续查询中保持其值。 组件 [DataGrid2] 与每次请求关联,其数据源会在每次新请求时添加一个元素。因此,必须将组件 [DataGrid2] 与每次请求关联(DataBind)。 因此,我们按照之前的建议,将其属性 [EnableViewState] 设置为 [faux]。这样,在第二次查询时(使用按钮 [Envoyer]),得到以下响应:

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
        ' 在首次查询时,数据源已定义并关联到第一个数据网格
        If Not IsPostBack Then
            '定义数据源
            dtThèmes = createDataSource()
            With DataGrid1
                .DataSource = dtThèmes
                .DataBind()
            End With
            ' 在会话中保存信息
            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
        ' 每次查询时,添加一个新主题
        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)
        '将数据网格2与数据源关联
        With Datagrid2
            .DataSource = dtThèmes
            .DataBind()
        End With
        ' 查询次数信息
        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
        ' 在会话中保存部分信息
        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
        ' 在首次请求时定义数据源并将其与第一个数据网格关联
        If Not IsPostBack Then
            '定义数据源
            dtThèmes = createDataSource()
            With DataGrid1
                .DataSource = dtThèmes
                .DataBind()
            End With
            ' 将信息存储在会话中
            Session("source") = dtThèmes
...
        End If
        ' 每次请求时,都会添加一个新主题
        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)
        '将数据网格2与数据源关联
        With Datagrid2
            .DataSource = dtThèmes
            .DataBind()
        End With
...
    End Sub

组件 [DataGrid1] 仅在首次查询时与数据源 S 建立关联(而非 IsPostBack)。此时该关联会被放入会话中,此后将不再建立。每次查询都会检索已放入会话的数据源 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")、c.a.d. S、c.a.d、[dtThèmes]。因此,保存的确实是数据源的新内容。无需编写:

            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
        ' 在首次请求时定义数据源并将其与第一个数据网格关联
....
            ' 在会话中保存信息
            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
        ' 每次查询时,都会添加一个新主题
....
        ' 查询次数信息
        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
        ' 在会话中保存部分信息
        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

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

  • numRequête1 和 numRequête2 属于 [Integer] 类型——[Integer] 不是类而是结构体
  • numRequête3 属于 [entier] 类型 - [entier] 是为此专门定义的类

当编写:


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

由此可推断,无需编写:

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

来将 [numRequête3] 的新值存储在会话中。相反,应编写:

        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. 业务类

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

Image

类 [produits] 的代码如下:


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)
            ' 保存连接字符串
            Me.chaineConnexionOLEDB = chaineConnexionOLEDB
        End Sub

        Public Function getDataSet(ByVal commande As String) As DataSet
            ' 创建对象 DataAdapter 以读取源数据 OLEDB
            Dim adaptateur As New OleDbDataAdapter(commande, chaineConnexionOLEDB)
            ' 创建 select 结果的内存映像
            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
            ' 返回结果
            Return contenu
        End Function
    End Class
End Namespace

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

Option Explicit On 
Option Strict On

' 命名空间
Imports System
Imports System.Data
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' 测试页面
    Module testproduits
        Sub Main(ByVal arguments() As String)
             ' 显示产品表的内容
             ' 该表位于一个数据库中 ACCESS,其程序名称即为文件名
            Const syntaxe1 As String = "pg bdACCESS"

             ' 验证程序参数
            If arguments.Length <> 1 Then
                 ' 错误消息
                Console.Error.WriteLine(syntaxe1)
                 ' 结束
                Environment.Exit(1)
            End If

             ' 准备连接字符串
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)

             ' 创建产品对象
            Dim objProduits As produits = New produits(chaineConnexion)

             ' 将产品表加载到数据集
            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

             ' 显示其内容
            Dim lignes As DataRowCollection = contenu.Tables(0).Rows
            For i As Integer = 0 To lignes.Count - 1
                 ' 表中的第 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
TextBox
EnableViewState=true
SELECT 查询输入框
2
RequiredFieldValidator1
RequiredFieldValidator
EnableViewState=false
检查是否存在 1
3
txtPages
TextBox
EnableViewState=true
输入框 - 指定每页结果中显示的数据行数
4
RequiredFieldValidator2
RequiredFieldValidator
EnableViewState=false
检查是否包含 3
5
RangeValidator1
RangeValidator
EnableViewState=false
检查 (3) 是否在区间内 [1,30]
6
btnExécuter
Button
EnableViewState=false
按钮 [submit]

我们将此视图命名为 [formulaire]。它允许用户在数据库 [produits.mdb] 上执行一个 SQL select 查询。执行该查询将生成一个新视图:

编号
名称
类型
属性
角色
1
lblSelect
标签
EnableViewState=false
信息字段
2
rdCroissant
rdDécroissant
RadioButton
EnableViewState=false
GroupName=rdTri
用于选择排序方向
3
DataGrid1
DataGrid
EnableViewState=true
允许分页=true
允许排序=true
显示SELECT查询结果的表格
4
lnkRésultats
LinkButton
EnableViewState=false
链接按钮 [submit]

我们将此视图命名为 [résultats]。该视图包含 [DataGrid],后者将显示 SQL select 命令的结果。用户在查询时可能会出错。 通过验证检查,某些错误将在视图 [erreurs] 中向用户提示。

Image

其他错误将通过视图 [erreurs] 向用户提示:

Image

显示视图 [erreurs]:

编号
名称
类型
属性
角色
1
erreursHTML
变量
 
显示错误所需的代码 HTML
3
lnkForm2
LinkButton
EnableViewState=false
链接按钮 [submit]

该应用程序的三个视图是同一页面 [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="Suivant"
跳转至数据源下一页的链接文本
PrevPageText="Précédent"
跳转至数据源上一页的链接文本

这些信息可直接输入到 <asp:datagrid> 标签的属性中。也可以借助 [WebMatrix] 实现。在 [DataGrid] 的属性窗口中,点击链接 [Générateur de propriétés]:

Image

此时将显示以下向导:

Image

选择选项 [Pagination]:

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)
        ' 创建产品对象
        Dim objProduits As produits
        Try
            objProduits = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection"))
            ' 将对象放入应用程序
            Application("objProduits") = objProduits
            ' 无错误
            Application("erreur") = False
        Catch ex As Exception
            '发生错误,在应用程序中记录
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

当应用程序启动时(Application_Start),我们会构建一个 [produits] 对象,并将其放入应用程序中,以便所有客户端的所有请求都能使用它。如果在此构建过程中发生异常,该异常将被记录在应用程序中。 过程 [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

    ' 页面组件
    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
    ' 页面数据
    Protected erreursHTML As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' 检查应用程序是否出错
        If CType(Application("erreur"), Boolean) Then
            ' 应用程序未正确初始化
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs, False)
            Exit Sub
        End If
        '首次请求
        If Not IsPostBack Then
            ' 显示空表单
            afficheFormulaire()
        End If
    End Sub

    Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
        ' 显示错误视图
        erreursHTML = ""
        For i As Integer = 0 To erreurs.Count - 1
            erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
        lnkErreurs.Visible = afficheLien
        ' 显示视图 [erreurs]
        vueErreurs.Visible = True
        vueFormulaire.Visible = False
        vueRésultats.Visible = False
    End Sub

    Private Sub afficheFormulaire()
        ' 显示视图 [formulaire]
        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)
        ' 初始化控件
        lblSelect.Text = sqlTexte
        With DataGrid1
            .DataSource = données
            .PageSize = CType(txtPages.Text, Integer)
            .CurrentPageIndex = 0
            .DataBind()
        End With
        ' 显示视图 [résultats]
        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
        ' 页面有效吗?
        If Not Page.IsValid Then
            afficheFormulaire()
            Exit Sub
        End If

        ' 执行 SELECT 客户端请求
        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
        ' 一切正常 - 显示结果
        afficheRésultats(txtSelect.Text.Trim, données)
        ' 将数据放入会话
        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
        ' 视图集
        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
        ' 页面切换
        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
        ' 对数据视图进行排序
        Dim données As DataView = CType(Session("données"), DataView)
        données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
        ' 显示数据
        With DataGrid1
            .CurrentPageIndex = 0
            .DataSource = données
            .DataBind()
        End With
    End Sub
End Class

在加载页面 [Page_Load] 时,我们会首先检查应用程序是否已正确初始化。如果未成功初始化,则显示视图 [erreurs],且不包含返回表单的链接,因为此时该链接已无用武之地。 实际上,只有当应用程序未能正确初始化时,才会显示视图 [erreurs]。否则,若这是客户端的首次请求,则显示视图 [formulaire]。其余部分,我们留给读者自行理解代码。 我们仅重点探讨三个过程:当用户请求执行在视图 [formulaire] 中输入的查询 SQL 时,将执行过程 [btnExécuter_Click]; 以及当用户使用 [DataGrid] 视图中的链接 [Suivant] 和 [Précédent] 时,以及当用户点击某列标题按。排序方向(升序或降序)由两个排序单选按钮决定。

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

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

对于页面本身,已设置属性 [IsValid]。 只有当所有验证控件的 [IsValid] 属性均为真时,该属性才为真。否则,需显示视图 [formulaire]。该视图包含将显示其 [errorMessage] 属性的验证控件。 如果页面有效,则使用由 [Application_Start] 创建的 [produits] 类型对象,以获取与执行 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] 对象本质上是由关系连接起来的一组表。 在我们的特定应用中,由类 [produits] 生成的 [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] 属性对视图进行排序,我们稍后将详细讨论该属性。

若成功获取类 [produits] 的 [DataSet],则显示视图 [résultats];否则显示视图 [erreurs]。 视图 [résultats] 的显示由过程 [afficheRésultats] 负责,该过程接收两个参数:

  • 要放置在标签 [lblSelect] 中的文本
  • 需与 [DataGrid1] 建立关联的 [DataView]

这个示例展示了[DataGrid]组件的高度灵活性。它能够识别与其关联的[DataView]的结构,并适应该结构。 最后,[btnExécuter_Click] 过程将刚刚获取的 [DataView] 存储在用户会话中,以便当用户请求同一 [DataView] 下的其他页面时能够调用。

当用户使用 [DataGrid] 中的链接 [Suivant] 和 [Précédent] 时,将执行 [DataGrid1_PageIndexChanged] 过程。该过程接收两个参数:


    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.) Handles DataGrid1.PageIndexChanged
source
事件的源对象——此处为 [Suivant] 或 [Précédent] 中的一个
e
事件信息。属性 e.NewPageIndex 表示为响应客户端请求而需显示的页面编号

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


    Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
        ' 换页
        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
事件的源对象——此处为 [Suivant] 或 [Précédent] 中的某个链接
e
事件信息。属性 [e.SortExpression] 是用于排序的点击列的名称

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


    Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
        ' 对数据视图进行排序
        Dim données As DataView = CType(Session("données"), DataView)
        données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
        ' 显示
        With DataGrid1
            .CurrentPageIndex = 0
            .DataSource = données
            .DataBind()
        End With
    End Sub

从当前会话中检索由 [DataGrid] 显示的 [DataView]。是 [btnExécuter_Click] 过程将其放置在此处的。 组件 [DataView] 具有一个属性 [Sort],我们向该属性分配排序表达式。 该表达式遵循 [select ... order by expr1, expr2, ...] 语法,其中每个 [expri] 后面可以跟关键字 [asc] 表示升序排序,或 [desc] 表示降序排序。 此处使用的表达式 [order by] 即为 [order by colonne 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. 业务类

类 [produits] 与前文所述相同。

8.7.3. 视图

当用户首次向应用程序发起请求时,将获得以下视图 [résultats1]:

编号
名称
类型
属性
角色
1
RadioButton1
RadioButton2
RadioButton
EnableViewState=false
允许从两种样式中选择一种 [DataList]
2
btnChanger
Button
EnableViewState=false
按钮 [submit]
3
DataList1
DataList
EnableViewState=true
数据列表显示区域

如果用户选择样式 2,将看到以下视图 [résultats2]:

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

视图 [erreurs] 报告了数据源访问问题:

编号
名称
类型
属性
角色
1
erreursHTML
变量
 
显示错误所需的代码 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("名称") %>
                            <br>
                            prix :
                            <%# databinder.eval(Container.DataItem,"价格","{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("名称") %>
                            , prix :
                            <%# databinder.eval(Container.DataItem,"价格","{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] 构建的。在其属性窗口中,已选中链接 [Mise en forme automatique]:

1234567

如上所述,[Couleur 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>
                            Contenu de la table [liste] de la base [produits]
                        </HeaderTemplate>
ItemTemplate

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

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

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

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

因此,生成的 [DataList] 将如下所示:

Image

上文中的数据以每行四条数据的形式排列。这是通过 [DataList] 的以下属性实现的:

RepeatDirection
水平
RepeatColumns
所需列数

最终,[DataList1]的代码即为稍早前展示的呈现代码。[DataList2]的呈现代码请读者自行研读。 与组件 [DataGrid] 一样,[DataList] 的大多数属性均可通过 [WebMatrix] 的向导进行设置。 为此,请使用 [DataList] 属性窗口中的 [Générateur de propriétés] 链接:

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)
        ' 创建一个产品对象
        Try
            Dim données As DataSet = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection")).getDataSet("select * from LISTE")
            ' 将对象放入应用程序
            Application("données") = données
            ' 无错误
            Application("erreur") = False
        Catch ex As Exception
            '发生错误,在应用程序中记录
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

当应用程序启动时(Application_Start),我们会基于业务类 [produits] 构建一个 [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

    ' 页面组件
    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
    ' 页面数据
    Protected erreursHTML As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' 检查应用程序是否出错
        If CType(Application("erreur"), Boolean) Then
            ' 应用程序未正确初始化
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs, False)
            Exit Sub
        End If
        '首次请求
        If Not IsPostBack Then
            ' 初始化控件
            With DataList1
                .DataSource = CType(Application("données"), DataSet)
                .DataBind()
            End With
            With DataList2
                .DataSource = CType(Application("données"), DataSet)
                .DataBind()
            End With
            ' 显示空表单
            afficheRésultats(True, False)
        End If
    End Sub

    Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
        ' 显示错误视图
        erreursHTML = ""
        For i As Integer = 0 To erreurs.Count - 1
            erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
        ' 显示视图 [erreurs]
        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)
        ' 显示视图 [résultats]
        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
        ' 更改样式
        afficheRésultats(RadioButton1.Checked, RadioButton2.Checked)
    End Sub
End Class

8.8. 重复器组件与数据绑定

[Repeater] 组件可对数据列表中每个元素的 HTML 代码进行重复。假设我们要以以下形式显示一个错误列表:

Image

我们之前遇到过这个问题,并通过在展示代码中添加一个形式为 <ul><% =erreursHTML %></ul> 的变量来解决,其中 erreursHTML 的值由控制器计算得出。 该值包含代码 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">

     ' 页面加载时执行的程序
    Sub page_Load(sender As Object, e As EventArgs)
        if not IsPostBack then
          ' 创建数据源
          with Repeater1
              .DataSource=createDataSource
              .DataBind
          end with
        end if
    End Sub

    function createDataSource as 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. Application

这里我们重新审视一个已使用服务器组件处理过的应用程序。该应用程序用于进行税款计算模拟。它基于一个名为 [impot] 的类,此处不再赘述。该类需要从数据源 OLEDB 中获取数据。 本示例中将使用数据源 ACCESS。我们在新版本中引入了以下新功能:

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

8.9.1. 应用程序的 MVC 结构

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

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

8.9.2. 应用程序的视图

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

Image

用户填写表单:

Image

他使用按钮 [Envoyer] 请求计算其税款。随后将显示以下视图 [simulations]:

Image

用户通过上述链接返回表单。表单内容保持其输入时的状态。用户可能出现输入错误:

Image

系统会在视图 [formulaire] 上提示这些错误:

Image

用户随后修正错误。他可以进行新的模拟:

Image

随后,他将获得包含额外一次模拟的视图 [simulations]:

Image

最后,如果数据源不可用,系统会在视图 [erreurs] 中向用户发出提示:

Image

8.9.2.1. 展示代码

需要提醒的是,页面 [main.aspx] 汇集了所有视图。这是一个包含三个容器的单一表单:

  • [panelform] 用于视图 [formulaire]
  • [panelerreurs] 用于视图 [erreurs]
  • [panelsimulations] 用于视图 [simulations]

现在我们详细说明这三个容器的组成部分。容器 [panelform] 的可视化表示如下:

编号
名称
类型
属性
角色
0
panelform
面板
 
表单视图
1
rdOui
rdNon
RadioButton
GroupName=rdmarie
单选按钮
2
cvMarie
CustomValidator
ErrorMessage=您未填写婚姻状况
EnableClientScript=false
检查客户端程序是否已发送预期的婚姻状况
3
txtEnfants
TextBox
 
子女数量
4
rfvEnfants
RequiredFieldValidator
ErrorMessage=请输入子女人数
ControlToValidate=txtEnfants
检查字段 [txtEnfants] 是否为空
5
rvEnfants
RangeValidator
ErrorMessage=请输入 1 到 30 之间的数字
ControlToValidate=txtEnfants
验证字段 [txtEnfants] 是否在区间 [1,30] 内
6
txtSalaire
TextBox
EnableViewState=true
年薪
7
rfvSalaire
RequiredFieldValidator
待验证控件=txtSalaire
ErrorMessage=请输入您的薪资金额
检查字段 [txtSalaire] 是否为空
8
revSalaire
RegularExpressionValidator
ControlToValidate=txtSalaire
ErrorMessage=工资无效
正则表达式=\s*\d+\s*
验证字段 [txtSalaire] 是否为一串数字
9
btnCalculer
Button
CausesValidation=true
表单中的 [submit] 按钮——启动税款计算
10
btnEffacer
按钮
CausesValidation=false
表单中的 [submit] 按钮——清空表单

可能会对负责验证用户是否已选中其中一个单选按钮的控件 [cvMarié] 感到惊讶。实际上,如果用户确实使用了服务器发送的表单,情况就只能如此。由于无法确保这一点,我们不得不检查所有提交的参数。 此外,还需注意按钮 [btnEffacer] 的属性 [CausesValidation=false]。因为当用户使用该按钮时,无需验证提交的数据,因为这些数据将被忽略。

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

编号
名称
类型
属性
角色
0
panelerreurs
面板
EnableViewState=false
错误视图
1
rptErreurs
重复器
EnableViewState=false
显示错误列表

组件 [rptErreurs] 的定义如下:


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

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

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

在 [Webmatrix] 下,[dgSimulations] 组件的配置如下。 在 [dgSimulations] 的属性窗口中,我们选择链接 [Mise en forme automatique],并选择架构 [Couleur 3]:

接着,仍在 [dlgSimulations] 的属性窗口中,我们选择链接 [Générateur de propriétés]。我们要求显示列标题:

Image

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

Image

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

Image

Texte de l'en-tête
列标题,此处为“已婚”
Champ de données
数据源中将由 [DataGrid] 的此列显示的列名。此处为 [DataTable] 中的“已婚”列。

第二列定义如下:

Texte de l'en-tête
子女
Champ de données
子女

第三列定义如下:

Texte de l'en-tête
年薪
Champ de données
工资
Expression de mise en forme
{0:C} - 货币显示格式,用于显示欧元符号

第四列定义如下:

Texte de l'en-tête
税额
Champ de données
税款
Expression de mise en forme
{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)
        ' 创建一个 Impot 对象
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' 将对象放入应用程序
            Application("objImpot") = objImpot
            ' 无错误
            Application("erreur") = False
        Catch ex As Exception
            '发生错误,在应用程序中记录
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' 会话开始 - 创建一个空的模拟列表
        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]。该过程试图创建一个类型为 [impot] 的对象,其数据来源于源 OLEDB。 若读者已忘记该类,请参阅第 5 章,该类已在其中定义。如果数据源不可用,[impot] 对象的创建可能会失败。 在此情况下,错误信息将存储在应用程序中,以便后续所有查询都能知晓该对象未能正确初始化。若创建成功,生成的 [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>

密钥 [chaineConnexion] 指代源 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

    ' 局部变量

    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
        ' 首先,检查应用程序的状态
        If CType(Application("erreur"), Boolean) Then
            ' 应用程序无法初始化
            ' 显示错误视图
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs)
            Exit Sub
        End If
        ' 无错误——在首次请求时,显示表单
        If Not IsPostBack Then afficheFormulaire()
    End Sub

在开始处理请求之前,我们会确保应用程序已成功初始化。如果未成功初始化,则通过过程 [afficheErreurs] 显示视图 [erreurs]。 如果是首次请求(IsPostBack=false),则通过 [afficheFormulaire] 显示视图 [formulaire]。

显示视图 [erreurs] 的过程如下:


    Private Sub afficheErreurs(ByRef erreurs As ArrayList)
        ' 构建错误列表
        With rptErreurs
            .DataSource = erreurs
            .DataBind()
        End With
        ' 显示容器 
        panelerreurs.Visible = True
        panelform.Visible = False
        panelsimulations.Visible = False
        Exit Sub
    End Sub

该过程接收一个错误消息列表作为参数,该列表位于 [erreurs] 中,类型为 [ArrayList]。我们只需将此数据源与负责显示它的组件 [rptErreurs] 关联即可。

显示视图 [formulaire] 的过程如下:


    Private Sub afficheFormulaire()
        ' 显示表单
        panelform.Visible = True
        ' 隐藏其他容器
        panelerreurs.Visible = False
        panelsimulations.Visible = False
    End Sub

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

当用户点击视图 [formulaire] 中的按钮 [Calculer] 时,将执行从 POST 到 [main.aspx] 的操作。 先执行过程 [Page_Load],然后执行过程 [btnCalculer_Click]:


    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
        ' 验证输入数据的有效性,如有错误则提示
        If Not Page.IsValid Then
            afficheFormulaire()
            Exit Sub
        End If
        ' 无错误 - 计算税款
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
        ' 将结果添加到现有模拟中
        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)
        ' 将模拟结果存入会话
        Session.Item("simulations") = simulations
        ' 显示模拟页面
        afficheSimulations(simulations, "Retour au formulaire")
    End Sub

该流程首先会验证页面的有效性。需要说明的是,当流程 [btnCalculer_Click] 执行时,验证检查已完成,且页面的 [IsValid] 属性已被设置。 如果页面无效,则重新显示视图 [formulaire],并终止该流程。 视图 [formulaire] 中 [IsValid] 到 [false] 属性的验证组件将显示其 [ErrorMessage] 属性。 如果页面有效,则通过在应用程序启动时存储在其中的类型为 [impot] 的对象计算税额。此新模拟将添加到已执行的模拟列表中,并存储在会话中。

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


    Private Sub afficheSimulations(ByRef simulations As DataTable, ByRef lien As String)
        ' 将数据网格与模拟数据源关联
        With dgSimulations
            .DataSource = simulations
            .DataBind()
        End With
        ' 关联
        lnkForm2.Text = lien
        ' 视图
        panelsimulations.Visible = True
        panelerreurs.Visible = False
        panelform.Visible = False
    End Sub

该过程有两个参数:

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

数据源 [simulations] 与负责显示它的组件相关联。链接文本则位于视图中 [LinkButton] 对象的 [Text] 属性中。

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


    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
        ' 显示空表单
        razForm()
        afficheFormulaire()
    End Sub

    Private Sub razForm()
        ' 清空表单
        rdOui.Checked = False
        rdNon.Checked = True
        txtEnfants.Text = ""
        txtSalaire.Text = ""
    End Sub

上面的代码足够简单,无需注释。接下来我们需要处理对视图 [erreurs] 和 [simulations] 中链接的点击:


    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
        ' 显示表单
        afficheFormulaire()
    End Sub

    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
        ' 显示表单
        afficheFormulaire()
    End Sub

这两个过程仅用于显示视图 [formulaire]。 我们知道,该视图中的字段将获取一个值,该值要么是为其提交的值,要么是其之前的值。正如这里所示,由于客户的 POST 未向表单字段发送任何值,因此这些字段将恢复其之前的值。因此,表单将显示用户输入的值。

8.9.4. 测试

应用程序所需的所有文件都放置在 <application-path> 文件夹中:
[bin]文件夹包含应用程序所需的DLL文件,其中包含[impot]、[impotsData]、[impotsOLEDB]类:

读者如需了解上述 [impot.dll] 文件的创建方法,可重读第 5 章。完成上述操作后,使用参数 (<application-path>,/impots6) 启动 Cassini 服务器。随后通过浏览器访问 URL [http://impots6/main.aspx]:

Image

如果将文件 ACCESS 重命名为 [impots.mdb],即 [impots1.mdb],将显示以下页面:

Image

8.9.5. 结论

我们有一个仅使用服务器组件的应用程序 MVC。 通过使用这些组件,应用程序的展示部分与控制部分得以完全分离。此前,由于展示代码中包含由控制代码计算的变量,且其值包含 HTML 代码,因此无法实现这种分离。现在情况已不再如此。