8. ASP 服务器组件 - 2
8.1. 简介
我们将继续研究用户界面,重点探讨:
- 数据验证组件
- 如何将数据绑定到服务器组件
- HTML 服务器组件
8.2. 所有数据验证组件
8.2.1. 简介
在基于表单的应用程序中,验证输入数据的有效性至关重要。在税费计算示例中,我们使用了以下表单:

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

ASP.NET 提供了一组称为“验证组件”的组件,可用于检查以下内容:
组件 | 作用 |
检查字段是否为空 | |
检查两个值是否相互匹配 | |
检查值是否在两个限制之间 | |
检查字段是否符合正则表达式 | |
允许开发人员定义自己的验证规则——该组件可替代所有其他组件 | |
允许您将前几个控件生成的错误消息汇总到页面上的单一位置 |
接下来我们将介绍这些组件。
8.2.2. RequiredFieldValidator
我们正在构建以下页面 [requiredfieldvalidator1.aspx]:
![]() |
编号 | name | 类型 | 属性 | 角色 |
1 | 文本框 | EnableViewState=true | 输入字段 | |
2 | 必填字段验证器 | EnableViewState=false 启用客户端脚本=true ErrorMessage=[name] 字段为必填项 | 验证组件 | |
3 | 按钮 | EnableViewState=false ValidationReasons=true | [submit] 按钮 |
[RequiredFieldValidator1] 字段用于在 [txtName] 字段为空或包含一串空格时显示错误消息。其属性如下:
该组件必须对其值进行验证的字段。组件的“值”指什么?即对应 HTML 标签的 [value] 属性的值。对于 [TextBox],这是输入字段的内容;对于 [DropDownList],则是所选项的值。 | |
布尔值 - 当为 true 时,表示前一个字段的内容也必须在客户端进行验证。在此情况下,只有当表单不包含任何错误时,浏览器才会提交表单。不过,如果客户端不是浏览器(例如),服务器端也会执行验证检查。 | |
检测到错误时组件必须显示的错误消息 |
只有当触发页面 [POST] 的按钮或链接具有 [CausesValidation=true] 属性时,才会执行页面上的数据验证。此属性的默认值为 [true]。
现在运行此应用程序。首先,我们将使用 [Mozilla] 浏览器:

[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" />
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<input type="submit" name="btnEnvoyer" value="Envoyer" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="btnEnvoyer" />
</p>
</form>
</body>
</html>
我们可以看到,[btnEnvoyer] 按钮通过其 [onclick] 属性与一个 JavaScript 函数相关联。我们还可以看到,该页面中不包含任何 [JavaScript] 代码。测试 [typeof(Page_ClientValidate) == 'function'] 将失败,且 JavaScript 函数不会被调用。随后表单将提交至服务器,由服务器执行验证检查。 让我们不输入姓名直接点击 [Submit] 按钮。服务器的响应如下:

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

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

IE 接收到的 HTML 代码如下:
<html>
<head>
</head>
<body>
<form name="_ctl0" method="post" action="requiredfieldvalidator1.aspx" language="javascript" onsubmit="ValidatorOnSubmit();" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNDI1MDc1NTU1Ozs+SGtdZvVxefDCDxnsqbDnqCaROsk=" />
<script language="javascript" src="/aspnet_client/system_web/1_1_4322/WebUIValidation.js"></script>
<p>
Demande du dossier de candidature au DESS
</p>
<fieldset>
<legend>[--Identité--]</legend>
<table>
<tbody>
<tr>
<td>
Nom*</td>
<td>
<input name="txtNom" type="text" id="txtNom" />
<span id="RequiredFieldValidator1" controltovalidate="txtNom" errormessage="Le champ [nom] est obligatoire" evaluationfunction="RequiredFieldValidatorEvaluateIsValid" initialvalue="" style="color:Red;visibility:hidden;">Le champ [nom] est obligatoire</span>
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<input type="submit" name="btnEnvoyer" value="Envoyer" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="btnEnvoyer" />
</p>
<script language="javascript">
<!--
var Page_Validators = new Array(document.all["RequiredFieldValidator1"]);
// -->
</script>
<script language="javascript">
<!--
var Page_ValidationActive = false;
if (typeof(clientInformation) != "undefined" && clientInformation.appName.indexOf("Explorer") != -1) {
if (typeof(Page_ValidationVer) == "undefined")
alert("Impossible de trouver la bibliothèque de scripts /aspnet_client/system_web/1_1_4322/WebUIValidation.js. Essayez de placer ce fichier manuellement ou effectuez une réinstallation en exécutant 'aspnet_regiis -c'.");
else if (Page_ValidationVer != "125")
alert("Cette page utilise une version incorrecte de WebUIValidation.js. La page requiert la version 125. La bibliothèque de scripts est " + Page_ValidationVer + ".");
else
ValidatorOnLoad();
}
function ValidatorOnSubmit() {
if (Page_ValidationActive) {
ValidatorCommonOnSubmit();
}
}
// -->
</script>
</form>
</body>
</html>
我们可以看到,这段代码比 [Mozilla] 收到的那段要长得多。我们就不深入探讨细节了。这种差异源于服务器在发送的 HTML 文档中包含了 JavaScript 函数。既然两个浏览器请求的 URL 相同,为什么会有两段不同的 HTML 代码呢?我们之前已经提到,ASP.NET 技术会导致服务器根据客户端的具体环境来调整发送给客户端的 HTML 文档。 市场上的浏览器种类繁多,且功能各异。 例如,Microsoft 和 Netscape 浏览器在处理接收到的文档时,所采用的对象模型并不相同。因此,用于在客户端操作该文档的 JavaScript 代码在两款浏览器中也存在差异。同样地,浏览器厂商不断发布新版浏览器,持续提升其功能。因此,IE5 能解析的 HTML 文档,IE3 可能无法识别。上述情况正是服务器端为客户端进行适配的一个实例。 出于本文未深入探讨的原因,Web 服务器判定客户端 [Mozilla] 不具备处理验证 JavaScript 代码的能力。因此,发送给它的 HTML 文档中未包含该代码,验证操作在服务器端进行。对于 [IE6],如我们所见,该 JavaScript 代码被包含在发送的 HTML 文档中。为了观察其实际效果,让我们尝试以下实验:
- 停止 Web 服务器
- 在不填写 [Name] 字段的情况下点击 [Submit] 按钮
我们将看到以下新页面:

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

发生了什么?浏览器执行了 JavaScript 验证代码。该代码判定页面有效。随后,浏览器将表单提交给了已停止的 Web 服务器。浏览器意识到这一点,并显示了上方的页面。如果我们重启服务器,一切将恢复正常。
我们能从这个例子中学到什么?
- 验证组件概念的价值,它允许将任何格式错误的表单发回客户端
- [VIEWSTATE] 的价值,它能确保表单以用户输入时的原样返回
- 服务器适应客户端的能力。客户端通过发送给服务器的 HTTP 标头 [User-Agent:] 进行身份识别。因此,并非由服务器“猜测”其正在处理的对象是谁。这种适应性对开发人员极具价值,因为他们无需担心应用程序所面对的客户端类型。
8.2.3. CompareValidator
我们正在构建以下页面 [comparevalidator1.aspx]:

编号 | 姓名 | 类型 | 属性 | 角色 |
1 | 下拉列表 | EnableViewState=true | 下拉列表 | |
2 | 下拉列表 | EnableViewState=true | 下拉列表 | |
3 | CompareValidator | EnableViewState=false 启用客户端脚本=true ErrorMessage=选项 1 无效 ValueToCompare=? 运算符=不等于 Type=string | 验证组件 | |
4 | CompareValidator | 启用视图状态=false 启用客户端脚本=true ErrorMessage=选项 2 无效 待比较控件=? 运算符=不等于 Type=string | 验证组件 | |
5 | 按钮 | EnableViewState=false ValidationReasons=true | [提交] 按钮 |
[CompareValidator] 组件用于比较两个值。可用的运算符包括 [Equal、NotEqual、LessThan、LessThanEqual、GreaterThan、GreaterThanEqual]。 第一个值由 [ControlToValidate] 属性设定,第二个值若需与常量比较则由 [ValueToCompare] 设定,若需与另一个组件的值比较则由 [ControlToCompare] 设定。[CompareValidator] 组件的重要属性如下:
必须由该组件验证其内容的字段 | |
布尔值 - 设为 true 时,表示前面的字段内容也必须在客户端进行验证 | |
如果检测到错误,组件应显示的错误消息 | |
[ControlToValidate] 字段的值必须与之进行比较的值 | |
[ControlToValidate] 字段的值必须与其进行比较的组件 | |
两个值之间的比较运算符 | |
待比较值的类型 |
此页面的 HTML 代码如下:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Demande du dossier de candidature au DESS
</p>
<fieldset>
<legend>[--Options du DESS pour lesquelles vous candidatez--]</legend>
<table>
<tbody>
<tr>
<td>
Option1*</td>
<td>
Option2</td>
</tr>
<tr>
<td>
<asp:DropDownList id="cmbChoix1" runat="server">
<asp:ListItem Value="?" Selected="True">?</asp:ListItem>
<asp:ListItem Value="Automatique">Automatique</asp:ListItem>
<asp:ListItem Value="Informatique">Informatique</asp:ListItem>
</asp:DropDownList>
</td>
<td>
<asp:DropDownList id="cmbChoix2" runat="server">
<asp:ListItem Value="?">?</asp:ListItem>
<asp:ListItem Value="Automatique">Automatique</asp:ListItem>
<asp:ListItem Value="Informatique">Informatique</asp:ListItem>
</asp:DropDownList>
</td>
</tr>
<tr>
<td>
<asp:CompareValidator id="CompareValidator1" runat="server" ErrorMessage="Choix 1 invalide" ControlToValidate="cmbChoix1" ValueToCompare="?" Operator="NotEqual"></asp:CompareValidator>
</td>
<td>
<asp:CompareValidator id="CompareValidator2" runat="server" ErrorMessage="Choix 2 invalide" ControlToValidate="cmbChoix2" Operator="NotEqual" ControlToCompare="cmbChoix1"></asp:CompareValidator>
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
</p>
</form>
</body>
</html>
该页面的验证约束如下:
- [cmbChoix1] 中选定的值必须与字符串“?”不同。这意味着 [CompareValidator1] 组件具有以下属性:Operator=NotEqual, ValueToCompare=?, Type=string
- [cmbChoix2] 中选择的值必须与 [cmbChoix1] 中选择的值不同。这意味着 [CompareValidator2] 组件具有以下属性:运算符=不等于,比较控件=cmbChoix1,类型=字符串
我们运行此应用程序。以下是在客户端与服务器交互过程中收到的页面示例:

如果通过 Internet Explorer 6 请求同一页面,其中包含的 JavaScript 代码会导致行为略有不同。用户在其中一个下拉列表中更改值时,系统会立即报告任何错误。这提升了用户体验。
8.2.4. CustomValidator 、RangeValidator
我们正在构建以下页面 [customvalidator1.aspx]:

编号 | 姓名 | 类型 | 属性 | 角色 |
下拉列表 | EnableViewState=true | 下拉列表 | ||
CompareValidator | 启用视图状态=false 启用客户端脚本=true ErrorMessage=文凭无效 运算符=不等于 ValueToCompare=? Type=String | 控件 [1] 的验证组件 | ||
文本框 | EnableViewState=false | 输入字段 | ||
CustomValidator | 启用视图状态=false 启用客户端脚本=true ErrorMessage=学位类型指定无效 客户端验证函数=chkOtherDegree | 用于字段 [3] 的验证组件 | ||
文本框 | EnableViewState=false | 输入字段 | ||
RangeValidator | EnableViewState=false 启用客户端脚本=true ErrorMessage=毕业年份必须在 [1990,2004] 范围内 MinValue=1990 MaxValue=2004 Type=Integer ControlToValidate=txtAnDiplome | 字段验证组件 [5] | ||
必填字段验证器 | 启用视图状态=false 启用客户端脚本=true ErrorMessage=请填写毕业年份 待验证控件=txtAnDiplome | 字段验证组件 [5] | ||
按钮 | EnableViewState=false ValidationReasons=true | [提交] 按钮 |
[RangeValidator] 字段用于验证控件的值是否在 [MinValue] 和 [MaxValue] 两个限制之间。其属性如下:
需要由该组件验证其值的字段 | |
布尔值 - 设为 true 时,表示前一个字段的内容也必须在客户端进行验证 | |
如果检测到错误,组件应显示的错误消息 | |
待验证字段的最小值 | |
待检查字段的最大值 | |
待验证字段值的类型 |
[CustomValidator] 字段允许您执行 ASP.NET 提供的验证组件无法完成的验证。此验证由开发人员编写的函数执行。该函数在服务器端执行。由于验证也可以在客户端进行,因此开发人员可能需要编写一个 JavaScript 函数并将其包含在 HTML 文档中。其属性如下:
必须由该组件验证其值的字段 | |
布尔值 - 设为 true 时,表示前一个字段的内容也必须在客户端进行验证 | |
如果检测到错误,组件应显示的错误消息 | |
要在客户端执行的函数 |
页面模板代码如下:
<%@ 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îtrise">Maîtrise</asp:ListItem>
<asp:ListItem Value="DESS">DESS</asp:ListItem>
<asp:ListItem Value="DEA">DEA</asp:ListItem>
</asp:DropDownList>
</td>
<td>
Si [Autre], précisez</td>
<td>
<asp:TextBox id="txtAutreDiplome" runat="server" EnableViewState="False"></asp:TextBox>
</td>
</tr>
<tr>
<td>
</td>
<td>
<p>
<asp:CompareValidator id="CompareValidator2" runat="server" ErrorMessage="Diplôme invalide" ControlToValidate="cmbDiplomes" ValueToCompare="?" Operator="NotEqual" ></asp:CompareValidator>
</p>
</td>
<td>
</td>
<td>
<asp:CustomValidator id="CustomValidator1" runat="server" ErrorMessage="Précision diplôme invalide" OnServerValidate="CustomValidator1_ServerValidate_1" EnableViewState="False" ClientValidationFunction="chckAutreDiplome"></asp:CustomValidator>
</td>
</tr>
<tr>
<td>
Année d'obtention*</td>
<td colspan="3">
<asp:TextBox id="txtAnDiplome" runat="server" Columns="4"></asp:TextBox>
<asp:RangeValidator id="RangeValidator1" runat="server" ErrorMessage="Année diplôme doit être dans l'intervalle [1990,2004]" ControlToValidate="txtAnDiplome" MinimumValue="1990" MaximumValue="2004" Type="Integer"></asp:RangeValidator>
<asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" ErrorMessage="Année diplôme requise" ControlToValidate="txtAnDiplome"></asp:RequiredFieldValidator>
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
</p>
</form>
</body>
</html>
[CustomValidator] 组件的 [OnServerValidate] 属性允许您指定负责服务器端验证的函数。在上例中,[CustomValidator1] 组件调用了以下 [CustomValidator1_ServerValidate_1] 过程:
<%@ Page Language="VB" %>
<script runat="server">
Sub CustomValidator1_ServerValidate_1(sender As Object, e As ServerValidateEventArgs)
' field [txtAutreDiplome] must be non-empty if [cmbDiplomes]=[other]
e.isvalid=not (cmbDiplomes.selecteditem.text="[Autre]" and txtAutreDiplome.text.trim="") _
and not (cmbDiplomes.selecteditem.text<>"[Autre]" and cmbDiplomes.selecteditem.text<>"?" and txtAutreDiplome.text.trim<>"")
End Sub
</script>
....
与 [CustomValidator] 组件关联的验证函数接收两个参数:
- sender:触发该事件的对象
- e:事件。如果验证的数据正确,该过程必须将 [e.IsValid] 属性设置为 true;否则,将其设置为 false。
此处执行以下检查:
- [cmbDiplomes] 下拉列表的值不能为 [?]。此项由 [CompareValidator2] 组件进行验证
- 用户需从 [cmbDiplomes] 下拉列表中选择一个学位。如果列表中不存在该学位,用户可从列表中选择 [Other] 选项,随后必须在 [txtAutreDiplome] 输入框中输入其学位名称。若在 [cmbDiplomes] 中选择了 [Other],则 [txtAutreDiplome] 字段不得为空。 如果 [cmbDiplomes] 中既未选择 [Other] 也未选择 [?],则 [txtAutreDiplome] 字段必须为空。这由与 [CustomValidator1] 组件关联的函数进行验证。
- 学位获得年份必须在 [1900-2004] 范围内。此条件由 [RangeValidator1] 进行验证。但是,如果用户将该字段留空,则不会使用 [RangeValidator1] 的验证函数。 因此,我们在 [ ] 中添加 [RequiredFieldValidator2] 组件以检查字段是否包含内容。这是一条通用规则:如果字段为空,则不检查其内容。唯一会进行检查的情况是该字段关联了 [RequiredFieldValidator] 组件。
以下是在 [Mozilla] 浏览器中的执行示例:

如果使用 [IE6] 浏览器,我们可以为 [CustomValidator1] 组件添加客户端验证函数。为此,该组件具有以下属性:
- EnableClientScript=true
- ClientValidationFunction=chkAutreDiplome
[chkAutreDiplome] 函数如下:
<head>
<meta http-equiv="pragma" content="no-cache" />
<script language="javascript">
function chkAutreDiplome(source,args){
// vérifie la validité du champ txtAutreDiplome
with(document.frmCandidature){
diplome=cmbDiplomes.options[cmbDiplomes.selectedIndex].text;
args.IsValid= !(diplome=="[Autre]" && txtAutreDiplome.value=="")
&& ! (diplome!="[Autre]" && diplome!="?" && txtAutreDiplome.value!="");
}
}
</script>
</head>
[chkAutreDiplome] 函数接收与服务器函数 [CustomValidator1_ServerValidate_1] 相同的两个参数:
- source:触发该事件的对象
- args:事件。该事件具有 [IsValid] 属性,如果验证的数据正确,函数必须将其设置为 true。如果值为 false,将在验证组件的位置显示相关的错误消息,且表单不会提交至服务器。
8.2.5. 正则表达式验证器
我们正在构建以下页面 [regularexpressionvalidator1.aspx]:

编号 | 姓名 | 类型 | 属性 | 角色 |
文本框 | EnableViewState=true | 输入字段 | ||
必填字段验证器 | ControlToValidate=txtMel 显示=动态 | 控件验证组件 [1] | ||
正则表达式验证器 | 待验证控件=txtMel 显示=动态 | 控件验证组件 [1] | ||
按钮 | 启用视图状态=false CausesValidation=true | [提交] 按钮 |
在此,我们需要验证电子邮件地址的格式。我们使用 [RegularExpressionValidator] 组件来实现,该组件允许我们使用正则表达式对字段进行验证。 请记住,正则表达式是一种字符模式。因此,我们将字段内容与该模式进行比对。由于字段为空时不会进行内容检查,我们还需要一个 [RequiredFieldValidator] 组件来验证电子邮件地址是否为空。[RegularExpressionValidator] 类具有与之前介绍过的组件类类似的属性:
需要由该组件验证其值的字段 | |
布尔值 - 设为 true 时,表示前一个字段的内容也必须在客户端进行验证 | |
如果检测到错误,组件应显示的错误消息 | |
用于与 [ControlToValidate] 内容进行比对的正则表达式 | |
显示模式:静态:即使未显示错误消息,该字段也始终存在;动态:仅在存在错误消息时显示该字段;无:不显示错误消息。该字段在其他组件中也存在,但此前未被使用。 |
电子邮件地址的正则表达式模式可以如下所示:\s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*
电子邮件地址的格式为 [champ1.champ2....@champA.champB...]。@符号前必须至少有一个字段,@符号后必须至少有两个字段。这些字段由字母数字字符和 - 符号组成。 字母数字字符由符号 \w 表示。序列 [\w-] 表示字符 \w 或字符 -。序列 S 后的 + 号表示该序列可以重复 1 次或多次;* 号表示可以重复 0 次或多次;? 号表示可以重复 0 次或 1 次。
电子邮件地址中的一个字段对应于正则表达式 [\w-]+。@符号前必须至少有一个字段,其后必须至少有两个字段(由 . 符号分隔)这一事实,对应于正则表达式:[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+。我们可以允许用户在地址前后添加空格。这些空格将在处理过程中被移除。 一串空格(可能为空)由正则表达式 \s* 表示。因此,电子邮件地址的正则表达式为:\s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*。
页面布局代码如下:
<html>
<head>
</head>
<body>
<form runat="server">
<p align="left">
Demande du dossier de candidature au DESS
</p>
<fieldset>
<legend>[--Votre adresse électronique où sera envoyé le dossier de candidature--]</legend>
<table>
<tbody>
<tr>
<td>
<asp:TextBox id="txtMel" runat="server" Columns="60"></asp:TextBox>
</td>
</tr>
<tr>
<td>
<p>
<asp:RequiredFieldValidator id="RequiredFieldValidator3" runat="server" Display="Dynamic" ControlToValidate="txtMel" ErrorMessage="Votre adresse électronique est requise"></asp:RequiredFieldValidator>
</p>
<p>
<asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" Display="Dynamic" ControlToValidate="txtMel" ErrorMessage="Votre adresse électronique n'a pas un format valide" ValidationExpression="\s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*"></asp:RegularExpressionValidator>
</p>
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
</p>
</form>
</body>
</html>
8.2.6. ValidationSummary
出于美观考虑,您可能希望将错误消息集中显示在同一位置,如下例 [summaryvalidator1.aspx] 所示,其中我们将所有先前示例整合到了一页中:

所有验证控件都具有以下属性:
- [Display=Dynamic],确保当控件的错误消息为空时,控件不会占用页面空间
- [EnableClientScript=false],用于禁用所有客户端验证
- [Text=*]。此属性将作为错误时的显示消息,而 [ErrorMessage] 属性的内容则由下文展示的 [ValidationSummary] 控件显示。
这组控件被放置在一个名为 [vueFormulaire] 的 [Panel] 组件中。在另一个名为 [vueErreurs] 的 [Panel] 组件中,我们放置了一个 [ValidationSummary] 控件:

编号 | 姓名 | 类型 | 属性 | 角色 |
1 | 验证摘要 | 标题文本=发生以下错误,启用客户端脚本=false,显示摘要=true | 显示页面上所有验证控件的错误 | |
2 | 链接按钮 | ValidationCauses=false | 链接回表单 |
对于 [lnkErrorsToForm] 链接,无需启用验证,因为该链接不会提交任何需要检查的值。
当所有数据均有效时,将向用户显示以下 [info] 视图:

1
编号 | 姓名 | 类型 | 属性 | 角色 |
1 | 标签 | 给用户的提示信息 |
此页面的 HTML 代码如下:
<html>
<head>
</head>
<body>
<form runat="server">
<p align="left">
Demande du dossier de candidature au DESS
</p>
<p>
<hr />
<asp:panel id="vueErreurs" runat="server">
<p align="left">
<asp:ValidationSummary id="ValidationSummary1" runat="server" ShowMessageBox="True" BorderColor="#C04000" BorderWidth="1px" BackColor="#FFFFC0" HeaderText="Les erreurs suivantes se sont produites"></asp:ValidationSummary>
</p>
<p>
<asp:LinkButton id="lnkErreursToFormulaire" onclick="lnkErreursToFormulaire_Click" runat="server" CausesValidation="False">Retour au formulaire</asp:LinkButton>
</p>
</asp:panel>
<asp:panel id="vueFormulaire" runat="server">
....
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
</p>
</asp:panel>
<asp:panel id="vueInfos" runat="server">
<asp:Label id="lblInfo" runat="server"></asp:Label>
</asp:panel>
</form>
</body>
</html>
以下是一些获得的结果示例。我们在未输入任何值的情况下提交[表单]视图:

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

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

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

页面控件代码如下:
<%@ Page Language="VB" %>
<script runat="server">
' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
' on the 1st request, we present the [form] view
if not ispostback then
afficheVues(true,false,false)
end if
end sub
sub afficheVues(byval formulaireVisible as boolean, _
erreursVisible as boolean, infosVisible as boolean)
' set of views
vueFormulaire.visible=formulaireVisible
vueErreurs.visible=erreursVisible
vueInfos.visible=infosVisible
end sub
Sub CustomValidator1_ServerValidate(sender As Object, e As ServerValidateEventArgs)
...
End Sub
Sub CustomValidator2_ServerValidate(sender As Object, e As ServerValidateEventArgs)
...
End Sub
Sub CustomValidator1_ServerValidate_1(sender As Object, e As ServerValidateEventArgs)
...
End Sub
Sub lnkErreursToFormulaire_Click(sender As Object, e As EventArgs)
' displays the form view
afficheVues(true,false,false)
' redo validity checks
Page.validate
End Sub
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' is the page valid?
if not Page.IsValid then
' the [errors] view is displayed
afficheVues(false,true,false)
else
' otherwise the view [infos]
lblInfo.Text="Le dossier de candidature au DESS IAIE a été envoyé à l'adresse ["+ _
txtMel.Text + "]. Nous vous en souhaitons bonne réception.<br><br>Le secrétariat du DESS."
afficheVues(false,false,true)
end if
end sub
</script>
<html>
...
</html>
- 在 [Page_Load] 过程(该过程在每次客户端请求时运行)中,我们显示 [form] 视图,同时隐藏其他视图。此操作仅在首次请求时执行。该过程调用一个辅助过程 [displayViews],并向其传递三个布尔值,以确定是否显示这三个视图。
- 当调用 [btnEnvoyer_Click] 过程时,数据验证已经完成。[btnEnvoyer] 按钮具有 [CausesValidation=true] 属性,该属性会触发此数据验证。所有验证控件均已执行,且其 [IsValid] 属性已设置。该属性指示控件验证的数据是否有效。 此外,页面本身的 [IsValid] 属性也已设置。只有当页面上所有验证控件的 [IsValid] 属性均为 [true] 时,该属性才为 [true]。因此,[btnEnvoyer_Click] 过程首先会检查页面是否有效。 如果页面无效,则显示 [errors] 视图。该视图包含 [ValidationSummary] 控件,其中列出了所有控件的 [ErrorMessage] 属性(参见上图)。如果页面有效,则显示 [info] 视图并显示一条信息提示。
- [lnkErrorsToForm_Click] 过程负责以已验证的状态显示 [form] 视图。由于 [form] 视图中的所有输入字段都具有 [EnableViewState=true] 属性,因此它们的状态会自动重新生成。值得注意的是,验证组件的状态并未被恢复。 人们可能会预期,在返回 [errors] 视图时,无效控件会显示其 [Text] 字段。但实际情况并非如此。 因此,我们通过调用页面的 [Page.Validate] 方法强制执行了数据验证。此操作必须在 [formView] 面板可见后进行。因此总共进行了两次验证。在实际应用中应避免这种情况。在此示例中,我们借此机会介绍了关于页面验证的新概念。
8.3. 列表控件与数据绑定
我们已介绍的若干服务器组件允许您显示值列表(如 DropDownList、ListBox)。此外,还有一些尚未涉及的组件可让您在 HTML 表格中显示多个值列表。 对于所有这些组件,均可通过编程方式将列表中的值逐一与对应组件关联。此外,还可将更复杂的对象(如 [Array]、[ArrayList]、[DataSet]、[HashTable] 等类型的对象)与这些组件关联,从而简化数据与组件的关联代码。这种关联称为数据绑定。
所有从 [ListControl] 类派生的组件均可与数据列表关联。这些组件包括 [DropDownList]、[ListBox]、[CheckButtonList] 和 [RadioButtonList]。每个组件均可绑定到数据源。 数据源可以采用多种形式:[Array]、[ArrayList]、[DataTable]、[DataSet]、[HashTable]……通常是实现了 IEnumerable、ICollection 或 IListSource 接口之一的对象。本文仅介绍其中几种。[DataSet] 对象是关系型数据库的表示形式,因此它是一组通过关系链接的表。 [DataTable] 对象即表示这样的表。数据源会设置 [ListControl] 对象中每个 [Item] 的 [Text] 和 [Value] 属性。若 T 为 [Text] 的值,V 为 [Value] 的值,则为 [ListControl] 的每个元素生成的 HTML 标签如下:
<option value="V">T</option> | |
<input type="checkbox" value="V">T | |
<input type="radio" value="V">T |
[ListControl] 组件通过以下属性与数据源相关联:
数据源 [Array]、[ArrayList]、[DataTable]、[DataSet]、[HashTable]、... | |
如果数据源是 [DataSet],则表示将用作数据源的表的名称。此时,实际的数据源即为该表。 | |
如果数据源是一个表([DataTable]、[DataSet]),则表示将为 [ListControl] 项的 [Text] 字段提供值的表列名称 | |
如果数据源是表([DataTable]、[DataSet]),则表示将为 [ListControl] 元素的 [Value] 字段提供值的表列名称 |
将 [ListControl] 组件绑定到数据源并不会使用数据源中的值初始化该组件。这需要通过 [ListControl].DataBind 操作来实现。
根据数据源的性质,将其绑定到 [ListControl] 组件的方式会有所不同:
[ListControl].DataSource=A [ListControl] 元素的 [Text] 和 [Value] 字段将取 A 中元素的值 | |
[ListControl].DataSource=AL [ListControl] 元素的 [Text] 和 [Value] 字段将取 AL 中元素的值 | |
[ListControl].DataSource = DT, [ListControl].DataTextField = "col1", [ListControl].DataValueField = "col2" 其中 col1 和 col2 是 DT 表中的两列。[ListControl] 项的 [Text] 和 [Value] 字段将分别显示 DT 表中 col1 和 col2 列的值 | |
[ListControl].DataSource=DS, [ListControl].DataSource="table" 其中 "table" 是 DS 中某张表的名称。 [ListControl].DataTextField="col1", [ListControl].DatavalueField ="col2" 其中 col1 和 col2 是 "table" 表中的两列。[ListControl] 元素的 [Text] 和 [Value] 字段将分别显示 "table" 表中 col1 和 col2 列的值 | |
[ListControl].DataSource = HT, [ListControl].DataTextField = "key", [ListControl].DataValueField = "value",其中 [key] 和 [value] 分别是 HT 的键和值。 |
我们将此信息应用到以下示例 [databind1.aspx] 中:
![]() |
这里有五个数据绑定,每个绑定包含四个控件,类型分别为 [DropDownList]、[ListBox]、[CheckBoxList] 和 [RadioButtonList]。这五个绑定在数据源方面有所不同:
绑定 | 数据源 |
数组 | |
数组列表 | |
DataTable | |
数据集 | |
哈希表 |
8.3.1. 控件呈现代码
绑定 1 中控件的呈现代码如下:
<td>
<asp:DropDownList id="DropDownList1" runat="server"></asp:DropDownList>
</td>
<td>
<asp:ListBox id="ListBox1" runat="server" SelectionMode="Multiple"></asp:ListBox>
</td>
<td>
<asp:CheckBoxList id="CheckBoxList1" runat="server"></asp:CheckBoxList>
</td>
<td>
<asp:RadioButtonList id="RadioButtonList1" runat="server"></asp:RadioButtonList>
</td>
绑定 2 至 5 的绑定配置完全相同,仅绑定编号不同。
8.3.2. 绑定到数组数据源
上述四个 [ListBox] 控件的绑定在控件代码的 [Page_Load] 过程 中如下所示:
' global data
dim textes() as string={"un","deux","trois","quatre"}
dim valeurs() as string={"1","2","3","4"}
dim myDataListe as new ArrayList
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
dim myHashTable as new HashTable
' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' create the data sources to be linked to the components
createDataSources
' link to an array [Array]
bindToArray
' link to a list [ArrayList]
bindToArrayList
' link to a table [DataTable]
bindToDataTable
' link to a data group [DataSet]
bindToDataSet
' link to a dictionary [HashTable]
bindToHashTable
end if
End Sub
sub createDataSources
' creates data sources to be linked to components
' arraylist
dim i as integer
for i=0 to textes.length-1
myDataListe.add(textes(i))
next
' datatable
' we define its two columns
myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
myDataTable.Columns.Add("texte",Type.GetType("System.String"))
' fill the table
dim ligne as DataRow
for i=0 to textes.length-1
ligne=myDataTable.NewRow
ligne("id")=i
ligne("texte")=textes(i)
myDataTable.Rows.Add(ligne)
next
' dataset - a single table
myDataSet.Tables.Add(myDataTable)
' hashtable
for i=0 to textes.length-1
myHashTable.add(valeurs(i),textes(i))
next
end sub
' panel connection
sub bindToArray
' association with components
with DropDownList1
.DataSource=textes
.DataBind
end with
with ListBox1
.DataSource=textes
.DataBind
end with
with CheckBoxList1
.DataSource=textes
.DataBind
end with
with RadioButtonList1
.DataSource=textes
.DataBind
end with
' item selection
ListBox1.Items(1).Selected=true
ListBox1.Items(3).Selected=true
CheckBoxList1.Items(0).Selected=true
CheckBoxList1.Items(3).Selected=true
DropDownList1.SelectedIndex=2
RadioButtonList1.SelectedIndex=1
end sub
sub bindToArrayList
....
end sub
sub bindToDataTable
...
end sub
sub bindToDataSet
...
end sub
控件仅在首次请求时在此处与数据绑定。此后,控件将通过 [VIEWSTATE] 机制保留其元素。绑定操作在 [bindToArray] 过程 中执行。由于数据源的类型为 [Array],因此仅初始化 [ListControl] 组件的 [DataSource] 字段。 通过 [ListControl].DataBind 方法,控件会从关联的数据源中获取值并填充。只有此时,[ListControl] 对象才拥有元素。随后,您可以选择其中部分元素。
8.3.3. 绑定到 ArrayList 数据源
数据源 [myDataList] 在 [createDataSources] 过程 中初始化:
' global data
dim textes() as string={"un","deux","trois","quatre"}
dim myDataListe as new ArrayList
..
' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' create the data sources to be linked to the components
createDataSources
...
end if
End Sub
sub createDataSources
' creates data sources to be linked to components
' arraylist
dim i as integer
for i=0 to textes.length-1
myDataListe.add(textes(i))
next
...
end sub
在控制代码的 [bindToArrayList] 过程 中,绑定 2 中四个 [ListBox] 控件的绑定如下所示:
' liaison arraylist
sub bindToArrayList
' l'association aux composants
with DropDownList2
.DataSource=myDataListe
.DataBind
end with
with ListBox2
.DataSource=myDataListe
.DataBind
end with
with CheckBoxList2
.DataSource=myDataListe
.DataBind
end with
with RadioButtonList2
.DataSource=myDataListe
.DataBind
end with
' la sélection des éléments
ListBox2.Items(1).Selected=true
ListBox2.Items(3).Selected=true
CheckBoxList2.Items(0).Selected=true
CheckBoxList2.Items(3).Selected=true
DropDownList2.SelectedIndex=2
RadioButtonList2.SelectedIndex=1
end sub
由于数据源的类型为 [ArrayList],因此仅初始化了 [ListControl] 组件的 [DataSource] 字段。
8.3.4. DataTable 类型的数据源
数据源 [myDataTable] 在 [createDataSources] 过程(
' global data
dim textes() as string={"un","deux","trois","quatre"}
dim myDataTable as new DataTable("table1")
...
' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' create the data sources to be linked to the components
createDataSources
...
end if
End Sub
sub createDataSources
' creates data sources to be linked to components
...
' datatable
' we define its two columns
myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
myDataTable.Columns.Add("texte",Type.GetType("System.String"))
' fill the table
dim ligne as DataRow
for i=0 to textes.length-1
ligne=myDataTable.NewRow
ligne("id")=i
ligne("texte")=textes(i)
myDataTable.Rows.Add(ligne)
next
...
end sub
我们首先创建一个包含两列的 [DataTable] 对象:[id] 和 [text]。其中 [id] 列将填充 [ListControl] 项的 [Value] 字段,而 [text] 列将填充其 [Text] 字段。包含两列的 [DataTable] 创建方式如下:
myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
myDataTable.Columns.Add("texte",Type.GetType("System.String"))
因此,我们创建了一个包含两列的表格:
- 第一列名为“id”,类型为整数
- 第二列名为“text”,类型为字符串
现在表格结构已创建完成,我们可以使用以下代码向其中填充数据:
dim ligne as DataRow
for i=0 to textes.length-1
ligne=myDataTable.NewRow
ligne("id")=i
ligne("texte")=textes(i)
myDataTable.Rows.Add(ligne)
next
[id] 列将包含整数 [0,1,..,n],而 [text] 列将包含 [data] 数组中的值。完成此操作后,[dataList] 表格即被填充。绑定 3 中四个 [ListBox] 控件的绑定在控件代码的 [bindToDataTable] 过程 中按以下方式执行:
sub bindToDataTable
' l'association aux composants
with DropDownList3
.DataSource=myDataTable
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with ListBox3
.DataSource=myDataTable
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with CheckBoxList3
.DataSource=myDataTable
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with RadioButtonList3
.DataSource=myDataTable
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
' la sélection des éléments
ListBox3.Items(1).Selected=true
ListBox3.Items(3).Selected=true
CheckBoxList3.Items(0).Selected=true
CheckBoxList3.Items(3).Selected=true
DropDownList3.SelectedIndex=2
RadioButtonList3.SelectedIndex=1
end sub
每个 [ListControl] 组件均通过以下设置与 [myDataTable] 数据源绑定:
[myDataTable] 表是数据源。该表的 [id] 列将填充组件元素的 [Value] 字段,而 [text] 列将填充其 [Text] 字段。
8.3.5. DataSet 类型的数据源
数据源 [myDataSet] 在 [createDataSources] 过程 中初始化:
' global data
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
...
' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' create the data sources to be linked to the components
createDataSources
...
end if
End Sub
sub createDataSources
' creates data sources to be linked to components
' dataset - a single table
myDataSet.Tables.Add(myDataTable)
...
end sub
一个 [DataSet] 对象代表一组 [DataTable] 表。我们将之前创建的 [myDataTable] 添加到 [DataSet] 中。绑定 4 中四个 [ListBox] 控件的绑定是在控件代码的 [bindToDataSet] 过程 中按以下方式完成的:
sub bindToDataSet
' l'association aux composants
with DropDownList4
.DataSource=myDataSet
.DataMember="table1"
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with ListBox4
.DataSource=myDataSet
.DataMember="table1"
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with CheckBoxList4
.DataSource=myDataSet
.DataMember="table1"
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with RadioButtonList4
.DataSource=myDataSet
.DataMember="table1"
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
' la sélection des éléments
ListBox4.Items(1).Selected=true
ListBox4.Items(3).Selected=true
CheckBoxList4.Items(0).Selected=true
CheckBoxList4.Items(3).Selected=true
DropDownList4.SelectedIndex=2
RadioButtonList4.SelectedIndex=1
end sub
每个 [ListControl] 组件与数据源的关联如下:
数据集 [myDataSet] 即为数据源。由于它可能包含多个表,因此我们在 [DataMember] 中指定要使用的表名。该表的 [id] 列将填充组件元素的 [Value] 字段,而 [text] 列将填充其 [Text] 字段。
8.3.6. 哈希表数据源
数据源 [myHashTable] 在 [createDataSources] 过程 中初始化:
' global data
dim textes() as string={"un","deux","trois","quatre"}
dim valeurs() as string={"1","2","3","4"}
dim myHashTable as new HashTable
...
' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' create the data sources to be linked to the components
createDataSources
...
end if
End Sub
sub createDataSources
' creates data sources to be linked to components
...
' hashtable
for i=0 to textes.length-1
myHashTable.add(valeurs(i),textes(i))
next
end sub
字典 [myHashTable] 可以视为一个包含两列的表格,分别称为“键”和“值”。 [key] 列代表字典的键,[value] 列代表与其关联的值。在此,[key] 列由 [values] 数组的内容组成,[value] 列由 [texts] 数组的内容组成。此源与控件的绑定是在 [bindToHashTable] 过程 中完成的:
sub bindToHashTable
' l'association aux composants
with DropDownList5
.DataSource=myHashTable
.DataValueField="key"
.DataTextField="value"
.DataBind
end with
with ListBox5
.DataSource=myHashTable
.DataValueField="key"
.DataTextField="value"
.DataBind
end with
with CheckBoxList5
.DataSource=myHashTable
.DataValueField="key"
.DataTextField="value"
.DataBind
end with
with RadioButtonList5
.DataSource=myHashTable
.DataValueField="key"
.DataTextField="value"
.DataBind
end with
' la sélection des éléments
ListBox5.Items(1).Selected=true
ListBox5.Items(3).Selected=true
CheckBoxList5.Items(0).Selected=true
CheckBoxList5.Items(3).Selected=true
DropDownList5.SelectedIndex=2
RadioButtonList5.SelectedIndex=1
end sub
对于每个组件,均使用以下语句建立绑定:
数据源是字典 [myHashTable]。控件的数值由字典的 [key] 列提供,文本由 [value] 列提供。字典元素按键的顺序插入到控件中,初始顺序是随机的。
8.3.7. 命名空间导入指令
许多命名空间会自动导入到 ASP.NET 页面中。但包含 [DataTable] 和 [DataSet] 类的 "System.Data" 命名空间并非如此。因此,必须显式导入该命名空间。操作方法如下:
<%@ Page Language="VB" %>
<%@ import Namespace="System.Data" %>
<script runat="server">
...
</script>
<html>
...
</html>
8.4. DataGrid 组件与数据绑定
[DataGrid] 组件允许您以表格格式显示数据,但它的功能远不止于简单的显示:
- 它提供了精确配置表格“视觉呈现”的能力
- 它允许您更新数据源
[DataGrid] 组件功能强大且结构复杂。我们将分步为您介绍。
8.4.1. 显示 Array、ArrayList、DataTable 或 DataSet 数据源
[DataGrid] 组件支持将 [Array]、[ArrayList]、[DataTable] 和 [DataSet] 类型的数据源以 HTML 表格形式展示。对于这四种数据类型,只需将数据源与 [DataGrid] 组件的 [DataSource] 属性关联即可:
数据源 [Array]、[ArrayList]、[DataTable]、[DataSet] 等 | |
若数据源为 [DataSet],则表示用作数据源的表的名称。此时实际的数据源即为该表。若此字段留空,则显示 [DataSet] 中的所有表。 |
现在我们展示 [datagrid1.aspx] 页面,该页面展示了一个关联了四个不同数据源的 [DataGrid]:

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

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

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

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

我们为 [DataGrid] #i 选择 [color i]。这些设计选择会反映在页面的呈现代码中:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Liaison de données avec un DataGrid
</p>
<hr />
<p>
<table>
<tbody>
</tbody>
</table>
<table border="1">
<tbody>
<tr>
<td>
Array</td>
<td>
ArrayList</td>
<td>
DataTable</td>
<td>
DataSet</td>
</tr>
<tr>
<td>
<asp:DataGrid id="DataGrid1" runat="server" ShowHeader="False" CellPadding="4" BackColor="White" BorderColor="#CC9966" BorderWidth="1px" BorderStyle="None">
<FooterStyle forecolor="#330099" backcolor="#FFFFCC"></FooterStyle>
<HeaderStyle font-bold="True" forecolor="#FFFFCC" backcolor="#990000"></HeaderStyle>
<PagerStyle horizontalalign="Center" forecolor="#330099" backcolor="#FFFFCC"></PagerStyle>
<SelectedItemStyle font-bold="True" forecolor="#663399" backcolor="#FFCC66"></SelectedItemStyle>
<ItemStyle forecolor="#330099" backcolor="White"></ItemStyle>
</asp:DataGrid>
</td>
<td>
<asp:DataGrid id="DataGrid2" runat="server" ShowHeader="False" CellPadding="4" BackColor="White" BorderColor="#3366CC" BorderWidth="1px" BorderStyle="None">
<FooterStyle forecolor="#003399" backcolor="#99CCCC"></FooterStyle>
<HeaderStyle font-bold="True" forecolor="#CCCCFF" backcolor="#003399"></HeaderStyle>
<PagerStyle horizontalalign="Left" forecolor="#003399" backcolor="#99CCCC" mode="NumericPages"></PagerStyle>
<SelectedItemStyle font-bold="True" forecolor="#CCFF99" backcolor="#009999"></SelectedItemStyle>
<ItemStyle forecolor="#003399" backcolor="White"></ItemStyle>
</asp:DataGrid>
</td>
<td>
<asp:DataGrid id="DataGrid3" runat="server" ShowHeader="False" CellPadding="3" BackColor="#DEBA84" BorderColor="#DEBA84" BorderWidth="1px" BorderStyle="None" CellSpacing="2">
<FooterStyle forecolor="#8C4510" backcolor="#F7DFB5"></FooterStyle>
<HeaderStyle font-bold="True" forecolor="White" backcolor="#A55129"></HeaderStyle>
<PagerStyle horizontalalign="Center" forecolor="#8C4510" mode="NumericPages"></PagerStyle>
<SelectedItemStyle font-bold="True" forecolor="White" backcolor="#738A9C"></SelectedItemStyle>
<ItemStyle forecolor="#8C4510" backcolor="#FFF7E7"></ItemStyle>
</asp:DataGrid>
</td>
<td>
<asp:DataGrid id="DataGrid4" runat="server" ShowHeader="False" CellPadding="3" BackColor="White" BorderColor="#E7E7FF" BorderWidth="1px" BorderStyle="None" GridLines="Horizontal">
<FooterStyle forecolor="#4A3C8C" backcolor="#B5C7DE"></FooterStyle>
<HeaderStyle font-bold="True" forecolor="#F7F7F7" backcolor="#4A3C8C"></HeaderStyle>
<PagerStyle horizontalalign="Right" forecolor="#4A3C8C" backcolor="#E7E7FF" mode="NumericPages"></PagerStyle>
<SelectedItemStyle font-bold="True" forecolor="#F7F7F7" backcolor="#738A9C"></SelectedItemStyle>
<AlternatingItemStyle backcolor="#F7F7F7"></AlternatingItemStyle>
<ItemStyle forecolor="#4A3C8C" backcolor="#E7E7FF"></ItemStyle>
</asp:DataGrid>
</td>
</tr>
</tbody>
</table>
</p>
<asp:Button id="Button1" runat="server" Text="Envoyer"></asp:Button>
</form>
</body>
</html>
在设计模式下,我们仅设置了格式属性。需要在控件代码中将数据与这四个组件关联起来:
<%@ Page Language="VB" %>
<%@ import Namespace="system.data" %>
<script runat="server">
' global data
dim textes1() as string={"un","deux","trois","quatre"}
dim textes2() as string={"one","two","three","for"}
dim valeurs() as string={"1","2","3","4"}
dim myDataListe as new ArrayList
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' create the data sources to be linked to the components
createDataSources
' link to an array [Array]
bindToArray
' link to a list [ArrayList]
bindToArrayList
' link to a table [DataTable]
bindToDataTable
' link to a data group [DataSet]
bindToDataSet
end if
End Sub
sub createDataSources
' creates data sources to be linked to components
' arraylist
dim i as integer
for i=0 to textes1.length-1
myDataListe.add(textes1(i))
next
' datatable
' we define its two columns
myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
myDataTable.Columns.Add("texte1",Type.GetType("System.String"))
myDataTable.Columns.Add("texte2",Type.GetType("System.String"))
' fill the table
dim ligne as DataRow
for i=0 to textes1.length-1
ligne=myDataTable.NewRow
ligne("id")=valeurs(i)
ligne("texte1")=textes1(i)
ligne("texte2")=textes2(i)
myDataTable.Rows.Add(ligne)
next
' dataset - a single table
myDataSet.Tables.Add(myDataTable)
end sub
' panel connection
sub bindToArray
with DataGrid1
.DataSource=textes1
.DataBind
end with
end sub
' arraylist link
sub bindToArrayList
with DataGrid2
.DataSource=myDataListe
.DataBind
end with
end sub
' datatable link
sub bindToDataTable
with DataGrid3
.DataSource=myDataTable
.DataBind
end with
end sub
' dataset link
sub bindToDataSet
with DataGrid4
.DataSource=myDataSet
.DataBind
end with
end sub
</script>
<html>
...
</html>
该代码与前一个示例非常相似,因此我们不再赘述。但请注意数据绑定的实现方式。对于这四个控件中的每一个,只需执行以下步骤即可:
其中 [data source] 的类型为 [Array]、[ArrayList]、[DataTable] 或 [DataSet]。因此,我们可以看到,这是一个用于在表格中显示数据的强大工具:
- 布局是在设计阶段使用 [WebMatrix] 或任何其他 IDE 创建的
- 数据绑定在代码中完成。使用 [WebMatrix] 时,如果数据源是 SQL Server 数据库或 Access 数据库,则可在设计时完成。在这种情况下,需在表单上放置一个 [SqlDataSource] 或 [AccessDataSource] 组件。该组件可在设计时根据实际情况链接到物理数据源(即 SQL Server 数据库或 Access 数据库)。 若将已链接至物理数据源的 [SqlDataSource] 或 [AccessDataSource] 对象赋值给 [DataGrid] 组件的 [DataSource] 属性,则实际数据将在设计模式下显示于 [DataGrid] 组件中。
8.5. 数据列表控件的 ViewState
在此,我们将重点介绍数据列表控件的 [VIEWSTATE] 机制。在两个客户端请求之间维护数据列表控件的状态时,您可能会在两种方法之间犹豫不决:
- 将其 [VIEWSTATE] 属性设置为 true
- 将其 [VIEWSTATE] 属性设置为 false,并将数据源存储在会话中,以便在下次请求时将组件与该数据源关联。
以下示例演示了数据容器 [VIEWSTATE] 机制的某些方面。在客户端的首次请求中,应用程序显示以下视图:
![]() |
编号 | 编号 | 类型 | 属性 | 角色 |
1 | DataGrid | EnableViewState=true | 显示数据源 S | |
2 | DataGrid | EnableViewState=true | 显示与 [DataGrid1] 相同的数据源 S | |
3 | Button | EnableViewState=false | [提交] 按钮 |
[DataGrid1] 组件由 [VIEWSTATE] 机制维护。 我们需要确定该机制——它会在每次请求时重新生成 [DataGrid1] 的显示内容——是否也会重新生成其数据源。为此,我们将数据源与 [DataGrid2] 组件关联起来。每次请求时,[DataGrid2] 通过显式绑定到 [DataGrid1] 的数据源来完成生成。同时,其 [EnableViewState] 属性也被设置为 [true]。
应用程序的 [main.aspx] 呈现代码如下:
<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>
<HTML>
<HEAD>
<title></title>
</HEAD>
<body>
<form runat="server">
<table>
<tr>
<td align="center">DataGrid 1</td>
<td align="center">
DataGrid 2</td>
</tr>
<tr>
<td>
<asp:DataGrid id="DataGrid1" runat="server" ...>
<SelectedItemStyle ...></SelectedItemStyle>
....
</asp:DataGrid></td>
<td>
<asp:DataGrid id="Datagrid2" runat="server" ...>
....
</asp:DataGrid></td>
</tr>
</table>
<asp:Button id="Button1" runat="server" Text="Envoyer"></asp:Button>
</form>
</body>
</HTML>
控制器 [main.aspx.vb] 如下所示:
Imports System.Data
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Datagrid2 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Button1 As System.Web.UI.WebControls.Button
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' on the 1st query, the data source is defined and linked to the 1st datagrid
If Not IsPostBack Then
'define data source
With DataGrid1
.DataSource = createDataSource()
.DataBind()
End With
End If
' for each query, datagrid2 is linked to the source of the 1st datagrid
With Datagrid2
.DataSource = DataGrid1.DataSource
.DataBind()
End With
End Sub
Private Function createDataSource() As DataTable
' initialize the data source
Dim thèmes As New DataTable
' columns
With thèmes.Columns
.Add("id", GetType(System.Int32))
.Add("thème", GetType(System.String))
.Add("description", GetType(System.String))
End With
' column id will be primary key
thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
' lines
Dim ligne As DataRow
For i As Integer = 0 To 4
ligne = thèmes.NewRow
ligne.Item("id") = i.ToString
ligne.Item("thème") = "thème" + i.ToString
ligne.Item("description") = "description du thème " + i.ToString
thèmes.Rows.Add(ligne)
Next
Return thèmes
End Function
Private Sub InitializeComponent()
End Sub
End Class
[createDataSource] 方法创建了一个类型为 [DataTable] 的数据源 S。我们不会过多讨论其代码,因为这并非本示例的重点。这是用于构建我们感兴趣的两个 [DataGrid] 组件的方法:
- [DataGrid1] 组件在首次查询时与 S 表绑定一次,此后不再绑定。
- [DataGrid2] 组件会在每次新查询时绑定到 [DataGrid1.DataSource] 数据源。
在首次查询时,我们得到如下视图:

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

我们观察到 [DataGrid1] 组件保留了其值,但 [DataGrid2] 组件并未保留。解释:
- 即使在 [Page_Load] 过程开始之前,由于 [viewstate] 机制的作用,[DataGrid1] 和 [DataGrid2] 对象已检索到了上一次请求时的值。事实上,两者的 [EnableViewState] 属性均设置为 [true]。
- [Page_Load] 过程开始运行。由于这是一次 [PostBack] 操作,[DataGrid1] 组件不会被 [Page_Load] 修改(参见代码)。因此,它保留了通过 [viewstate] 检索到的值。这就是上图所示的情况。
- 另一方面,[DataGrid2] 组件通过 [DataBind] 绑定到了 [DataGrid1.DataSource] 数据源。因此,它会被重建,而它刚刚通过 [viewstate] 检索到的值也会丢失。因此,在此处将它的 [EnableViewState] 属性设置为 [false] 会更有利,以避免不必要的状态管理。 上图显示 [DataGrid2] 已被绑定到一个空数据源。由于该数据源是 [DataGrid1.DataSource],我们可以得出结论:虽然 [viewstate] 机制成功恢复了 [DataGrid1] 组件的显示,但并未恢复其 [DataSource] 等属性。
从这个示例中我们可以得出什么结论?如果数据容器需要在每次请求时都与数据源绑定(DataBind),则应避免将其 [EnableViewState] 属性设置为 [true]。然而,在某些情况下,即使在此情境下,容器的 [EnableViewState] 属性也必须保持为 [true];否则,您希望处理的事件将不会被触发。 我们稍后将遇到一个此类示例。
通常,数据容器的数据源会在请求过程中发生变化。因此,容器必须在每次请求时都与数据源绑定。数据源通常被限定在会话范围内,以便请求能够访问它。我们的第二个示例演示了这一机制。该应用程序仅提供一个视图:
![]() |
No. | name | type | 属性 | 角色 |
1 | DataGrid | EnableViewState=true | 显示数据源 S | |
2 | DataGrid | EnableViewState=false | 显示与 [DataGrid1] 相同的数据源 S | |
3 | Button | EnableViewState=false | [提交] 按钮 | |
4 | 标签 | EnableViewState=false | 信息文本 |
[DataGrid1] 组件仅在首次请求时与数据绑定。得益于 [viewstate] 机制,它将在多次请求中保留其值。而 [DataGrid2] 组件在每次请求时都会与数据源绑定,且每次新请求都会向该数据源添加一项。因此,[DataGrid2] 组件必须在每次请求时进行绑定(DataBind)。 因此,我们已按先前建议将其 [EnableViewState] 属性设置为 [false]。这样,在第二次请求(使用 [Submit] 按钮)时,我们会得到以下响应:

[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 :
<asp:Label id="lblInfo1" runat="server" EnableViewState="False"></asp:Label>,
<asp:Label id="lblInfo2" runat="server" EnableViewState="False"></asp:Label>,
<asp:Label id="lblInfo3" runat="server" EnableViewState="False"></asp:Label></P>
<P>
<asp:Button id="Button1" runat="server" Text="Envoyer" EnableViewState="False"></asp:Button></P>
</form>
</body>
</HTML>
控制器代码 [main.aspx.vb] 如下:
Imports System.Data
Imports System
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Datagrid2 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Button1 As System.Web.UI.WebControls.Button
Protected WithEvents lblInfo1 As System.Web.UI.WebControls.Label
Protected WithEvents lblInfo2 As System.Web.UI.WebControls.Label
Protected WithEvents lblInfo3 As System.Web.UI.WebControls.Label
Dim dtThèmes As DataTable
Dim numRequête1 As Integer
Dim numRequête2 As Integer
Dim numRequête3 As New entier
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' on the 1st query, the data source is defined and linked to the 1st datagrid
If Not IsPostBack Then
'define data source
dtThèmes = createDataSource()
With DataGrid1
.DataSource = dtThèmes
.DataBind()
End With
' store information in the session
Session("source") = dtThèmes
numRequête1 = 0 : Session("numRequête1") = numRequête1
numRequête2 = 0 : Session("numRequête2") = numRequête2
numRequête3.valeur = 0 : Session("numRequête3") = numRequête3
End If
' a new theme is added to each query
dtThèmes = CType(Session("source"), DataTable)
Dim nbThèmes = dtThèmes.Rows.Count
Dim ligne As DataRow = dtThèmes.NewRow
With ligne
.Item("id") = nbThèmes.ToString
.Item("thème") = "thème" + nbThèmes.ToString
.Item("description") = "description du thème " + nbThèmes.ToString
End With
dtThèmes.Rows.Add(ligne)
'links datagrid2 with the data source
With Datagrid2
.DataSource = dtThèmes
.DataBind()
End With
' info no. of requests
numRequête1 = CType(Session("numRequête1"), Integer)
numRequête1 += 1
lblInfo1.Text = numRequête1.ToString
numRequête2 = CType(Session("numRequête2"), Integer)
numRequête2 += 1
lblInfo2.Text = numRequête2.ToString
numRequête3 = CType(Session("numRequête3"), entier)
numRequête3.valeur += 1
lblInfo3.Text = numRequête3.valeur.ToString
' store some information in the session
Session("numRequête2") = numRequête2
End Sub
Private Function createDataSource() As DataTable
....
End Function
End Class
Public Class entier
Private _valeur As Integer
Public Property valeur() As Integer
Get
Return _valeur
End Get
Set(ByVal Value As Integer)
_valeur = Value
End Set
End Property
End Class
我们没有重写 [createDataSource] 方法的代码。它与前一个应用程序中的代码相同,只是源代码中只包含三行。让我们先看看数据源和两个容器是如何管理的:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' on the 1st query, the data source is defined and linked to the 1st datagrid
If Not IsPostBack Then
'define data source
dtThèmes = createDataSource()
With DataGrid1
.DataSource = dtThèmes
.DataBind()
End With
' store information in the session
Session("source") = dtThèmes
...
End If
' a new theme is added to each query
dtThèmes = CType(Session("source"), DataTable)
Dim nbThèmes = dtThèmes.Rows.Count
Dim ligne As DataRow = dtThèmes.NewRow
With ligne
.Item("id") = nbThèmes.ToString
.Item("thème") = "thème" + nbThèmes.ToString
.Item("description") = "description du thème " + nbThèmes.ToString
End With
dtThèmes.Rows.Add(ligne)
'links datagrid2 with the data source
With Datagrid2
.DataSource = dtThèmes
.DataBind()
End With
...
End Sub
[DataGrid1] 组件仅在首次查询时(非 IsPostBack 状态)与数据源 S 建立绑定。随后该组件会被放入会话中,此后将不再被放入会话。每次请求都会检索会话中的数据源 S,并向其中添加一行。 [DataGrid2] 组件在每次请求时都会显式地绑定到数据源 S。这就是为什么它的内容在每次请求时都会增加一行。请注意,在修改数据源 S 的内容后,它不会通过操作显式地放回会话中:
为什么?当查询开始时,会话中包含一个类型为 [DataTable] 的 Session("source") 对象,该对象代表上次查询时的数据源。我们将该 Session("source") 对象称为 S。当我们编写:
dtThèmes = CType(Session("source"), DataTable)
[dtThèmes] 和 [S] 是指向同一个 [DataTable] 对象的两个引用。因此,当 [Page_Load] 中的代码向 [dtThèmes] 引用的表中添加一个元素时,它同时也会将其添加到 [S] 引用的表中。 在页面执行结束时,会话中的所有对象都将被保存,包括 Session("source") 对象,即 S,即 [dtThemes]。因此,被保存的确实是数据源的新内容。没有必要编写:
来执行此保存操作,因为 Session("source") 已经等于 [dtThèmes]。当会话中放置的数据不是对象时(例如 [Integer、Float、...] 结构),情况就不再如此。查询计数器管理就证明了这一点:
Dim numRequête1 As Integer
Dim numRequête2 As Integer
Dim numRequête3 As New entier
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' on the 1st query, the data source is defined and linked to the 1st datagrid
....
' store information in the session
Session("source") = dtThèmes
numRequête1 = 0 : Session("numRequête1") = numRequête1
numRequête2 = 0 : Session("numRequête2") = numRequête2
numRequête3.valeur = 0 : Session("numRequête3") = numRequête3
End If
' a new theme is added to each query
....
' info no. of requests
numRequête1 = CType(Session("numRequête1"), Integer)
numRequête1 += 1
lblInfo1.Text = numRequête1.ToString
numRequête2 = CType(Session("numRequête2"), Integer)
numRequête2 += 1
lblInfo2.Text = numRequête2.ToString
numRequête3 = CType(Session("numRequête3"), entier)
numRequête3.valeur += 1
lblInfo3.Text = numRequête3.valeur.ToString
' store some information in the session
Session("numRequête2") = numRequête2
End Sub
Private Function createDataSource() As DataTable
....
End Function
Private Sub InitializeComponent()
End Sub
End Class
Public Class entier
Private _valeur As Integer
Public Property valeur() As Integer
Get
Return _valeur
End Get
Set(ByVal Value As Integer)
_valeur = Value
End Set
End Property
我们将请求计数器存储在三个元素中:
- numRequest1 和 numRequest2 的类型为 [Integer] —— [Integer] 不是类,而是一种结构
- numRequest3 的类型为 [integer] —— [integer] 是为此目的定义的类
当我们编写:
numRequête1 = CType(Session("numRequête1"), Integer)
..
numRequête2 = CType(Session("numRequête2"), Integer)
..
numRequête3 = CType(Session("numRequête3"), entier)
..
- 结构 [Session("numRequest1")] 被复制到 [numRequest1] 中。因此,当元素 [numRequest1] 被修改时,元素 [Session("numRequest1")] 本身并不会被修改
- [Session("numRequest2")] 和 [numRequest2] 也是如此
- 元素 [Session("numRequest3")] 和 [numRequest3] 都是指向同一 [integer] 类型对象的引用。被引用的对象可以通过任一引用进行修改。
由此我们可以得出结论,没有必要编写:
来将 [numRequest3] 的新值存储在会话中。相反,您应该写:
来存储结构体 [numRequête1] 和 [numRequête2] 的新值。我们仅对 [numRequête2] 进行了此操作,这解释了为何在第二次查询后获取的屏幕截图中,计数器 [numRequête1] 的数值不正确。
因此需注意:一旦数据被添加到会话中,若其由对象表示,则无需重复添加。在其他情况下,若数据已发生修改,则必须重新添加。
8.6. 使用分页且已排序的 DataGrid 显示数据列表
[DataGrid] 组件允许您显示 [DataSet] 的内容。到目前为止,我们一直都是“手动”构建 [DataSet]。这次,我们将使用来自数据库的 [DataSet]。我们将构建以下 MVC 应用程序:
![]() |
这三个视图将作为容器被整合到 [main.aspx] 控制器中的呈现代码中。因此,该应用程序仅包含一个页面:[main.aspx]。
8.6.1. 业务类
[products] 类提供了对以下 ACCESS 数据库的访问:

[products]类的代码如下:
Imports System
Imports System.Data
Imports System.Data.OleDb
Imports System.Xml
Namespace st.istia.univangers.fr
Public Class produits
Private chaineConnexionOLEDB As String
Public Sub New(ByVal chaineConnexionOLEDB As String)
' save the connection string
Me.chaineConnexionOLEDB = chaineConnexionOLEDB
End Sub
Public Function getDataSet(ByVal commande As String) As DataSet
' create a DataAdapter object to read data from source OLEDB
Dim adaptateur As New OleDbDataAdapter(commande, chaineConnexionOLEDB)
' create a memory image of the select result
Dim contenu As New DataSet
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' we return the result
Return contenu
End Function
End Class
End Namespace
ACCESS 数据库将通过 OLEDB 驱动程序进行管理。我们向 [products] 类的构造函数提供连接字符串、OLEDB 驱动程序以及待管理的数据库。 [products] 类有一个 [getDataSet] 方法,该方法返回一个 [DataSet],该 [DataSet] 通过执行一个 SQL [select] 查询获得,其查询文本作为参数传递。在该方法执行期间,会进行多项操作:建立与数据库的连接、执行 [select] 查询以及关闭连接。所有这些操作都可能引发异常,这里对此进行了处理。一个测试程序可能如下所示:
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Data
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' test pg
Module testproduits
Sub Main(ByVal arguments() As String)
' displays the contents of a product table
' the table is in a ACCESS database whose pg receives the file name
Const syntaxe1 As String = "pg bdACCESS"
' checking program parameters
If arguments.Length <> 1 Then
' error msg
Console.Error.WriteLine(syntaxe1)
' end
Environment.Exit(1)
End If
' prepare the connection chain
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
' creation of a product object
Dim objProduits As produits = New produits(chaineConnexion)
' retrieve the product table from a dataset
Dim contenu As DataSet
Try
contenu = objProduits.getDataSet("select id,nom,prix from liste")
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' display its contents
Dim lignes As DataRowCollection = contenu.Tables(0).Rows
For i As Integer = 0 To lignes.Count - 1
' table line i
Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
"," + lignes(i).Item("prix").ToString)
Next
End Sub
End Module
End Namespace
此应用程序的各个组件编译如下:
dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb
dos>vbc /r:produits.dll /r:system.data.dll /r:system.xml.dll /r:system.dll testproduits.vb
dos>dir
06/05/2004 16:52 118 784 produits.mdb
07/05/2004 11:07 902 produits.vb
07/04/2004 07:01 1 532 testproduits.vb
07/05/2004 14:21 3 584 produits.dll
07/05/2004 14:22 4 608 testproduits.exe
dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30
8.6.2. 视图
既然我们已经有了数据访问类,接下来将为这个 Web 应用程序编写控制器和视图。让我们先看看它是如何工作的。第一个视图如下:
![]() |
编号 | 名称 | 类型 | 属性 | 角色 |
1 | 文本框 | EnableViewState=true | SELECT 查询输入字段 | |
2 | 必填字段验证器 | EnableViewState=false | 检查是否存在 1 | |
3 | 文本框 | EnableViewState=true | 输入字段 - 指定每页结果中要显示的数据行数 | |
4 | RequiredFieldValidator | EnableViewState=false | 检查是否存在 3 | |
5 | RangeValidator | EnableViewState=false | 检查 (3) 是否在 [1,30] 范围内 | |
6 | 按钮 | EnableViewState=false | [提交] 按钮 |
我们将此视图称为 [form] 视图。它允许用户对 [products.mdb] 数据库执行 SQL SELECT 查询。执行该查询将创建一个新视图:
![]() |
否 | 名称 | 类型 | 属性 | 角色 |
1 | 标签 | EnableViewState=false | 信息字段 | |
2 | 单选按钮 | 启用视图状态=false 组名=rdSort | 允许您选择排序顺序 | |
3 | DataGrid | EnableViewState=true 允许分页=true 允许排序=true | 显示查询结果的表格 | |
4 | 链接按钮 | EnableViewState=false | [提交] 按钮 |
我们将此视图称为 [results] 视图。它包含一个 [DataGrid],用于显示 SQL SELECT 语句的结果。用户在查询时可能会出错。借助验证控件,部分错误将在 [errors] 视图中向用户报告。

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

显示 [errors] 视图:
![]() |
否 | 名称 | 类型 | 属性 | 角色 |
1 | 变量 | 显示错误所需的 HTML 代码 | ||
3 | LinkButton | EnableViewState=false | [提交] 按钮 |
该应用程序的三个视图是同一页面 [main.aspx] 内的三个不同容器(面板)。其呈现代码如下:
<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
<HEAD>
</HEAD>
<body>
<P>Liaison de données avec un DataGrid</P>
<HR width="100%" SIZE="1">
<form runat="server">
<asp:panel id="vueFormulaire" runat="server">
<P>Commande [select] à exécuter sur la table LISTE</P>
<P> 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écé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écédent" ...></PagerStyle>
</asp:DataGrid></P>
启用分页 | |
每页显示四行数据 | |
跳转到数据源下一页的链接文本 | |
跳转至数据源上一页的链接文本 |
这些信息可直接输入到 <asp:datagrid> 标签的属性中。您也可以使用 [WebMatrix]。在 [DataGrid] 属性窗口中,单击 [属性生成器] 链接:

将出现以下向导:

选择 [分页] 选项:

上文展示了呈现代码中 [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]
[global.asax.vb]
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' create a product object
Dim objProduits As produits
Try
objProduits = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection"))
' put the object in the application
Application("objProduits") = objProduits
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
当应用程序启动(Application_Start)时,我们会创建一个 [products] 对象并将其存储在应用程序中,以便所有客户端的请求都能使用它。如果在此创建过程中发生异常,则会将其记录在应用程序日志中。[Application_Start] 过程仅执行一次。之后,[global.asax] 控制器将不再参与处理。随后由 [main.aspx.vb] 控制器处理剩余部分:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
Public Class main
Inherits System.Web.UI.Page
' components page
Protected WithEvents txtSelect As System.Web.UI.WebControls.TextBox
Protected WithEvents RequiredFieldValidator1 As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
Protected WithEvents RequiredFieldValidator2 As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents RangeValidator1 As System.Web.UI.WebControls.RangeValidator
Protected WithEvents btnExécuter As System.Web.UI.WebControls.Button
Protected WithEvents vueFormulaire As System.Web.UI.WebControls.Panel
Protected WithEvents lblSelect As System.Web.UI.WebControls.Label
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents lnkRésultats As System.Web.UI.WebControls.LinkButton
Protected WithEvents vueRésultats As System.Web.UI.WebControls.Panel
Protected WithEvents lnkErreurs As System.Web.UI.WebControls.LinkButton
Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
Protected WithEvents rdCroissant As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdDécroissant As System.Web.UI.WebControls.RadioButton
' data page
Protected erreursHTML As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' check for application errors
If CType(Application("erreur"), Boolean) Then
' the application has not initialized correctly
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, False)
Exit Sub
End If
'1st request
If Not IsPostBack Then
' the empty form is displayed
afficheFormulaire()
End If
End Sub
Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
' displays the error view
erreursHTML = ""
For i As Integer = 0 To erreurs.Count - 1
erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
lnkErreurs.Visible = afficheLien
' the [errors] view is displayed
vueErreurs.Visible = True
vueFormulaire.Visible = False
vueRésultats.Visible = False
End Sub
Private Sub afficheFormulaire()
' the [form] view is displayed
vueFormulaire.Visible = True
vueErreurs.Visible = False
vueRésultats.Visible = False
End Sub
Private Sub afficheRésultats(ByVal sqlTexte As String, ByVal données As DataView)
' initialize controls
lblSelect.Text = sqlTexte
With DataGrid1
.DataSource = données
.PageSize = CType(txtPages.Text, Integer)
.CurrentPageIndex = 0
.DataBind()
End With
' the [results] view is displayed
vueRésultats.Visible = True
vueFormulaire.Visible = False
vueErreurs.Visible = False
End Sub
Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
' valid page?
If Not Page.IsValid Then
afficheFormulaire()
Exit Sub
End If
' execute query SELECT customer
Dim données As DataView
Try
données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0).DefaultView
Catch ex As Exception
Dim erreurs As New ArrayList
erreurs.Add("erreur d'accès à la base de données (" + ex.Message + ")")
afficheErreurs(erreurs, True)
Exit Sub
End Try
' all's well - the results are in
afficheRésultats(txtSelect.Text.Trim, données)
' put the data in the session
Session("données") = données
End Sub
Private Sub retourFormulaire(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkErreurs.Click, lnkRésultats.Click
' set of views
vueErreurs.Visible = False
vueFormulaire.Visible = True
vueRésultats.Visible = False
End Sub
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' change page
With DataGrid1
.CurrentPageIndex = e.NewPageIndex
.DataSource = CType(Session("données"), DataView)
.DataBind()
End With
End Sub
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
' sort the dataview
Dim données As DataView = CType(Session("données"), DataView)
données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
' we display it
With DataGrid1
.CurrentPageIndex = 0
.DataSource = données
.DataBind()
End With
End Sub
End Class
当页面加载时 [Page_Load],我们首先检查应用程序是否能够正确初始化。如果不能,我们将显示 [errors] 视图,且不包含返回表单的链接,因为此时该链接已无必要。实际上,只有当应用程序无法正确初始化时,才能显示 [errors] 视图。否则,如果这是客户端的首次请求,我们将显示 [form] 视图。 其余部分,我们留给读者自行理解代码。我们将仅关注三个过程:[btnExecute_Click] 过程(在用户请求执行 [form] 视图中输入的 SQL 查询时运行)、[DataGrid1_PageIndexChanged] 过程(在用户使用 [DataGrid] 中的 [Next] 和 [Previous] 链接时运行), 以及 [DataGrid1_SortCommand] 过程,该过程在用户点击列标题按该顺序排序数据时运行。排序顺序(升序或降序)由两个排序单选按钮决定。
因此,在 [btnExécuter_Click] 过程开始时,我们会首先检查页面是否有效。当 [btnExécuter_Click] 过程运行时,与页面中各个验证控件相关的检查已经完成。对于每个验证控件,已设置了两个属性:
若验证数据有效则设为 true,否则设为 false | |
若验证数据无效,则显示此错误消息 |
对于页面本身,已设置了一个 [IsValid] 属性。只有当所有验证控件的 [IsValid] 属性均设置为 true 时,该属性才为 true。 若非如此,则必须显示 [form] 视图。该视图包含将显示其 [errorMessage] 属性的验证控件。如果页面有效,我们将使用由 [Application_Start] 创建的 [products] 对象来获取与执行 SQL SELECT 查询对应的 [DataSet]。我们将此转换为 [DataView] 对象:
Dim données As DataView
Try
données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0).DefaultView
...
我们本可以直接操作 [DataSet] 并编写如下代码:
Dim données As DataSet
Try
données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim)
...
[DataSet] 对象本质上是由关系链接起来的表集合。在我们的具体应用中,从 [products] 类获取的 [DataSet] 仅包含一个表,即由 [select] 语句生成的那个表。表可以进行排序,而 [DataSet] 则无法排序;然而,我们需要对检索到的数据进行排序。为了处理 [select] 语句生成的结果表,我们可以这样编写:
Dim données As DataTable
Try
données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0)
...
尽管 [DataTable] 对象代表数据库表,但它本身不具备排序方法。要实现排序,您需要创建该表的视图。 视图是 [DataView] 类型的对象。您可以通过过滤器对同一张表创建不同的视图。一张表有一个默认视图,即未定义任何过滤器的视图。因此,它代表了整个表。可以通过 [DataTable.DefaultView] 获取此默认视图。您可以使用其 [sort] 属性对视图进行排序,我们稍后将对此进行讨论。
如果从 [products] 类成功检索 [DataSet],则显示 [results] 视图;否则,显示 [errors] 视图。通过 [displayResults] 过程显示 [results] 视图,该过程接收两个参数:
- 要放置在 [lblSelect] 标签中的文本
- 要绑定到 [DataGrid1] 的 [DataView]
此示例展示了 [DataGrid] 组件的强大灵活性。它能够识别所绑定的 [DataView] 的结构并适应其变化。最后,[btnExécuter_Click] 过程将刚刚获取的 [DataView] 存储在用户的会话中,以便当用户请求同一 [DataView] 的其他页面时,该 [DataView] 仍可使用。
当用户点击 [DataGrid] 中的 [Next] 和 [Previous] 链接时,将执行 [DataGrid1_PageIndexChanged] 过程。该过程接收两个参数:
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.) Handles DataGrid1.PageIndexChanged
触发该事件的对象——在此情况下,即 [Next] 或 [Previous] 链接之一 | |
有关该事件的信息。e.NewPageIndex 属性表示响应客户端请求后应显示的页码 |
该事件处理程序的完整代码如下:
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' change page
With DataGrid1
.CurrentPageIndex = e.NewPageIndex
.DataSource = CType(Session("données"), DataView)
.DataBind()
End With
End Sub
[DataGrid] 组件具有一个 [CurrentPageIndex] 属性,用于指示其当前显示或即将显示的页码。我们将 [e] 参数的 [NewPageIndex] 值赋给该属性。随后,[DataGrid] 便与 [btnExécuter_Click] 过程保存在会话中的 [DataView] 建立了绑定。
有人可能会疑惑,既然 [DataGrid] 的内容是在每次页面重新加载时由代码计算生成的,它是否还需要 [EnableViewState=true] 属性。乍看之下似乎不需要。然而,如果 [DataGrid] 设置了 [EnableViewState=false] 属性,我们会发现 [DataGrid1.PageIndexChanged] 事件永远不会被触发。这就是我们保留 [EnableViewState=true] 的原因。 我们知道,这会导致 [DataGrid] 的内容被存储在页面的隐藏字段 [__VIEWSTATE] 中。如果 [DataGrid] 规模较大,这可能会显著降低页面加载速度。如果这构成问题,您可以自行管理分页,而不使用 [DataGrid] 的自动分页功能。
当用户点击 [DataGrid] 显示的某列标题以请求按该列顺序对数据进行排序时,将执行 [DataGrid1_SortCommand] 过程。该过程接收两个参数:
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
触发该事件的对象——在此情况下,是 [Next] 或 [Previous] 链接之一 | |
有关该事件的信息。[e.SortExpression] 属性即为被点击用于排序的列名 |
该事件处理程序的完整代码如下:
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
' sort the dataview
Dim données As DataView = CType(Session("données"), DataView)
données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
' we display it
With DataGrid1
.CurrentPageIndex = 0
.DataSource = données
.DataBind()
End With
End Sub
我们检索当前会话中由 [DataGrid] 显示的 [DataView]。该 [DataView] 是由 [btnExécuter_Click] 过程放置在那里的。[DataView] 组件有一个 [Sort] 属性,我们将排序表达式赋值给该属性。 其语法为 [select ... order by expr1, expr2, ...],其中每个 [expr] 后面可以跟关键字 [asc] 表示升序排序,或 [desc] 表示降序排序。此处使用的 [order by] 表达式即为 [order by column asc/desc]。 [e.SortExpression] 属性返回被点击用于排序的 [DataGrid] 列的名称。[asc/desc] 字符串根据 [rdTri] 组中单选按钮的值进行设置。一旦 [DataView] 的排序表达式设置完成,[DataGrid] 便与其绑定。我们将 [DataGrid] 定位在其第一页。
8.7. DataList 组件与数据绑定
接下来我们将重点关注 [DataList] 组件。它提供的格式化选项比 [DataGrid] 更多,但灵活性较低。因此,它无法自动适应所关联的数据源。若需适应,必须通过代码实现。如果预先已知数据源的结构,那么该组件提供的格式化选项可能使其比 [DataGrid] 更受青睐。
8.7.1. 应用
为演示 [DataList] 的用法,我们将构建一个与前一个类似的 MVC 应用程序:
![]() |
这三个视图将作为容器嵌入到 [main.aspx] 控制器中的呈现代码中。因此,该应用程序仅包含一个页面 [main.aspx]。
8.7.2. 业务类
[products] 类与之前相同。
8.7.3. 视图
当用户首次向应用程序发送请求时,将看到以下 [results1] 视图:
![]() |
编号 | 名称 | 类型 | 属性 | 角色 |
1 | 单选按钮 | EnableViewState=false | 允许您从两种 [DataList] 样式中选择一种 | |
2 | 按钮 | EnableViewState=false | [提交] 按钮 | |
3 | DataList | EnableViewState=true | 数据列表显示字段 |
如果用户选择样式 #2,他们将看到以下 [results2] 视图:
![]() |
编号 | 名称 | 类型 | 属性 | 角色 |
1 | DataList | EnableViewState=true | 数据列表显示字段 |
[errors] 视图表明访问数据源时出现问题:
![]() |
编号 | 名称 | 类型 | 属性 | 角色 |
1 | 变量 | 用于显示错误的 HTML 代码 |
该应用程序的三个视图是同一页面 [main.aspx] 内的三个不同容器(面板)。其呈现代码如下:
<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
<HEAD>
</HEAD>
<body>
<P>Liaison de données avec un DataList</P>
<HR width="100%" SIZE="1">
<form runat="server" ID="Form1">
<asp:Panel Runat="server" ID="bandeau">
<P>Choisissez votre style :
<asp:RadioButton id="RadioButton1" runat="server" EnableViewState="False" Text="1" GroupName="rdstyle"
Checked="True"></asp:RadioButton>
<asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" Text="2" GroupName="rdstyle"></asp:RadioButton>
<asp:Button id="btnChanger" runat="server" EnableViewState="False" Text="Changer"></asp:Button></P>
<HR width="100%" SIZE="1">
</asp:Panel>
<asp:Panel id="vueRésultats1" runat="server">
<P>
<asp:DataList id="DataList1" runat="server" BorderColor="Tan" BorderWidth="1px" BackColor="LightGoldenrodYellow"
CellPadding="2" RepeatDirection="Horizontal" RepeatColumns="4" ForeColor="Black">
<SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
<HeaderTemplate>
Contenu de la table [liste] de la base [produits]
</HeaderTemplate>
<AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
<ItemTemplate>
nom :
<%# Container.DataItem("nom") %>
<br>
prix :
<%# databinder.eval(Container.DataItem,"prix","{0:C}") %>
<br>
</ItemTemplate>
<FooterStyle BackColor="Tan"></FooterStyle>
<HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
</asp:DataList></P>
</asp:Panel>
<asp:Panel id="vueRésultats2" runat="server">
<P>
<asp:DataList id="DataList2" runat="server">
<HeaderTemplate>
Contenu de la table [liste] de la base [produits]
<HR width="100%" SIZE="1">
</HeaderTemplate>
<AlternatingItemStyle BackColor="Teal"></AlternatingItemStyle>
<SeparatorStyle BackColor="LightSkyBlue"></SeparatorStyle>
<ItemStyle BackColor="#C0C000"></ItemStyle>
<ItemTemplate>
nom :
<%# Container.DataItem("nom") %>
, prix :
<%# databinder.eval(Container.DataItem,"prix","{0:C}") %>
<BR>
</ItemTemplate>
<SeparatorTemplate>
<HR width="100%" SIZE="1">
</SeparatorTemplate>
<HeaderStyle BackColor="#C0C0FF"></HeaderStyle>
</asp:DataList></P>
</asp:Panel>
<asp:Panel id="vueErreurs" runat="server">
<P>Les erreurs suivantes se sont produites :</P>
<%= erreursHTML %>
</asp:Panel>
</form>
</body>
</HTML>
8.7.4. 配置 [DataList] 组件
让我们来看看 [DataList] 组件的各种属性。这些属性数量众多,本文仅选取其中一小部分进行介绍。您可以在 [DataList] 中定义多达七个显示模板:
[DataList] 标题模板 | |
用于显示关联数据列表中各项的行模板。仅此模板为必填项。 | |
为了在视觉上区分连续显示的项目,可以使用两个模板:ItemTemplate 用于第 n 个项目,AlternatingItemTemplate 用于第 n+1 个项目 | |
用于 [DataList] 中所选项的模板 | |
用于 [DataList] 中两个元素之间分隔符的模板 | |
[DataList] 允许您编辑其显示的值。[EditItemTemplate] 是 [DataList] 中当前处于“编辑”模式的项目的模板 | |
[DataList] 页脚的模板 |
[DataList1] 组件使用 [WebMatrix] 构建。在其属性窗口中,已选中 [AutoFormat] 链接:
![]() | 1234567 ![]() |
上文中的 [Color 5] 架构将生成一个 [DataList],并为以下模板应用样式:HeaderTemplate (1)、ItemTemplate (2, 6)、AlternatingTemplate (3, 5)、SelectedItemTemplate (4)、FooterTemplate (7)。生成的代码如下:
<asp:DataList id="DataList1" runat="server" BorderColor="Tan" BorderWidth="1px" BackColor="LightGoldenrodYellow"
CellPadding="2" ForeColor="Black">
<SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
<AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
<FooterStyle BackColor="Tan"></FooterStyle>
<HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
</asp:DataList></P>
尚未定义任何模板。这需要我们来完成。我们定义以下模板:
| |
|
请记住,[ItemTemplate] 模板用于显示与 [DataList] 关联的数据源中的项目。该数据源是一组数据行,每行包含一个或多个值。数据源中的当前行由 [Container.DataItem] 对象表示。此类行包含列。 [Container.DataItem("col1")] 是当前行中“col1”列的值。要在呈现代码中包含此值,我们编写 <%# Container.DataItem("col") %>。有时,我们希望以特殊格式显示当前行中的某个元素。在此,我们希望以欧元显示当前行的“price”列。 我们使用 [DataBinder.Eval] 函数,该函数接受三个参数:
- 当前行 [Container.DataItem]
- 要格式化的列名
- 格式化字符串采用 {0:format} 形式,其中 [format] 是 [string.format] 方法支持的格式之一。
因此,代码 <%# DataBinder.Eval(Container.DataItem,"price",{0:C}) %> 将以货币格式(格式 C=货币)显示当前行中的 [price] 列。
因此,我们将得到一个如下所示的 [DataList]:

上文中的数据已按每行四个数据项进行排列。这是通过以下 [DataList] 属性实现的:
水平 | |
所需列数 |
最终,[DataList1] 的代码即为上文代码片段中所示。关于 [DataList2] 的呈现代码,我们留待读者自行研读。与 [DataGrid] 组件类似,[DataList] 的大多数属性均可通过 [WebMatrix] 向导进行设置。要执行此操作,请使用 [DataList] 属性窗口中的 [属性生成器] 链接:
![]() | ![]() |
8.7.5. 控制器
[global.asax, global.asax.vb] 控制器如下所示:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' create a product object
Try
Dim données As DataSet = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection")).getDataSet("select * from LISTE")
' put the object in the application
Application("données") = données
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
当应用程序启动(Application_Start)时,我们会从 [products] 业务类创建一个 [dataset],并将其添加到应用程序中,以便所有客户端的所有请求都能使用它。如果在此创建过程中发生异常,则会在应用程序中记录该异常。[Application_Start] 过程仅运行一次。此后,[global.asax] 控制器将不再参与。 随后,[main.aspx.vb] 控制器将处理相关工作:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
Public Class main
Inherits System.Web.UI.Page
' components page
Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
Protected WithEvents DataList1 As System.Web.UI.WebControls.DataList
Protected WithEvents DataList2 As System.Web.UI.WebControls.DataList
Protected WithEvents vueRésultats1 As System.Web.UI.WebControls.Panel
Protected WithEvents vueRésultats2 As System.Web.UI.WebControls.Panel
Protected WithEvents RadioButton1 As System.Web.UI.WebControls.RadioButton
Protected WithEvents RadioButton2 As System.Web.UI.WebControls.RadioButton
Protected WithEvents btnChanger As System.Web.UI.WebControls.Button
Protected WithEvents bandeau As System.Web.UI.WebControls.Panel
' data page
Protected erreursHTML As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' check for application errors
If CType(Application("erreur"), Boolean) Then
' the application has not initialized correctly
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, False)
Exit Sub
End If
'1st request
If Not IsPostBack Then
' initialize controls
With DataList1
.DataSource = CType(Application("données"), DataSet)
.DataBind()
End With
With DataList2
.DataSource = CType(Application("données"), DataSet)
.DataBind()
End With
' the empty form is displayed
afficheRésultats(True, False)
End If
End Sub
Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
' displays the error view
erreursHTML = ""
For i As Integer = 0 To erreurs.Count - 1
erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
' the [errors] view is displayed
vueErreurs.Visible = True
vueRésultats1.Visible = False
vueRésultats2.Visible = False
bandeau.Visible = False
End Sub
Private Sub afficheRésultats(ByVal visible1 As Boolean, ByVal visible2 As Boolean)
' the [results] view is displayed
vueRésultats1.Visible = visible1
vueRésultats2.Visible = visible2
vueErreurs.Visible = False
End Sub
Private Sub btnChanger_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnChanger.Click
' change the style
afficheRésultats(RadioButton1.Checked, RadioButton2.Checked)
End Sub
End Class
8.8. Repeater 组件与数据绑定
[Repeater] 组件允许您针对数据列表中的每个项目重复 HTML 代码。假设我们希望以以下格式显示错误列表:

我们之前曾遇到过这个问题,并通过在呈现代码中使用 <% =erreursHTML %> 这种形式来解决,其中 erreursHTML 的值由控制器计算得出。该值包含 HTML 代码,具体来说是一个列表的 HTML 代码。其缺点是,若要修改此 HTML 列表的呈现效果,就必须进入控制器部分,这违背了控制器与呈现的分离原则。[Repeater] 组件提供了一个解决方案。 与 [DataList] 类似,我们可以定义 <HeaderTemplate> 用于页眉、<ItemTemplate> 用于数据列表中的当前项,以及 <FooterTemplate> 用于数据结尾。在此,我们可以为 [Repeater] 组件定义如下:
<asp:Repeater id="Repeater1" runat="server" EnableViewState="False">
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
请注意,如果数据源包含多列,[Container.DataItem] 代表一行数据;如果数据源仅有一列,则代表单个数据点。本例中即属于后者。例如,我们正在构建以下应用程序:

页面布局代码如下:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Liaison de données avec un composant [Repeater]
</p>
<hr />
<p>
<asp:Repeater id="Repeater1" runat="server" EnableViewState="False">
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</p>
</form>
</body>
</html>
控件代码如下:
<%@ Page Language="VB" %>
<script runat="server">
' procedure executed when the page is loaded
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' create a data source
with Repeater1
.DataSource=createDataSource
.DataBind
end with
end if
End Sub
function createDataSource as ArrayList
' create an arraylist
dim erreurs as new ArrayList
dim i as integer
for i=0 to 5
erreurs.add("erreur-"+i.ToString)
next
return erreurs
end function
</script>
<html>
...
</html>
在客户端的首次请求中,我们将一个 [ArrayList] 对象与 [Repeater] 组件关联,该组件用于显示错误列表。
8.9. 应用程序
在此,我们将重新审视一个先前使用服务器端组件实现的应用程序。该应用程序允许用户模拟税费计算。它依赖于一个 [impot] 类,此处不再赘述。该类需要从 OLEDB 数据源中检索数据。在本示例中,我们将使用 ACCESS 数据源。在此版本中,我们引入了以下新功能:
- 使用验证组件检查数据有效性
- 使用与数据源关联的服务器组件来显示结果
8.9.1. 应用程序的 MVC 结构
该应用程序的 MVC 结构如下:
![]() |
三个视图将作为容器嵌入到控制器 [main.aspx] 的呈现代码中。因此,该应用程序仅包含一个页面 [main.aspx]。
8.9.2. 应用程序的视图
[form]视图是用于输入计算用户税款所需信息的表单:

用户填写表单:

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

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

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

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

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

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

8.9.2.1. 呈现代码
请记住,[main.aspx] 页面整合了所有视图。它是一个包含三个容器的单一表单:
- 用于 [form] 视图的 [panelform]
- [panelerrors] 用于 [errors] 视图
- [panelsimulations] 用于 [simulations] 视图
接下来我们将详细说明这三个容器的组成。 [panelform] 容器的视觉呈现如下:
![]() |
编号 | 名称 | 类型 | 属性 | 角色 |
面板 | 表单视图 | |||
单选按钮 | 组名=rdmarie | 单选按钮 | ||
CustomValidator | ErrorMessage=您未指定婚姻状况 EnableClientScript=false | 检查客户端程序是否发送了预期的婚姻状况 | ||
文本框 | 子女数量 | |||
必填字段验证器 | ErrorMessage=请输入子女数量 ControlToValidate=txtChildren | 检查 [txtChildren] 字段是否为空 | ||
范围验证器 | ErrorMessage=请输入 1 到 30 之间的数字 ControlToValidate=txtChildren | 检查 [txtChildren] 字段是否在 [1,30] 范围内 | ||
文本框 | EnableViewState=true | 年薪 | ||
必填字段验证器 | ControlToValidate=txtSalary ErrorMessage=请输入您的年薪 | 检查 [txtSalary] 字段是否为空 | ||
正则表达式验证器 | ControlToValidate=txtSalary ErrorMessage=薪资无效 正则表达式=\s*\d+\s* | 检查 [txtSalary] 字段是否为一串数字 | ||
按钮 | ValidationTriggers=true | 表单上的 [submit] 按钮 - 开始计算税款 | ||
按钮 | Validation=false | 表单 [提交] 按钮 - 清空表单 |
您可能会对 [cvMarié] 检查感到惊讶,它用于验证用户是否选择了两个单选按钮中的一个。这是必要的,因为无法确定用户是否正在使用服务器发送的表单。由于无法保证这一点,我们必须验证所有提交的参数。 另请注意 [btnEffacer] 按钮的 [CausesValidation=false] 属性。当用户点击此按钮时,提交的数据不应被检查,因为它将被忽略。
容器中的所有组件均设置了 [EnableViewState=false] 属性,但 [panelForm, txtEnfants, txtSalaire, rdOui, rdNon] 除外。[panelerreurs] 容器的视觉呈现如下:
![]() |
否。 | 名称 | 类型 | 属性 | 角色 |
面板 | EnableViewState=false | 错误视图 | ||
重复器 | EnableViewState=false | 显示错误列表 |
[rptErrors] 组件定义如下:
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<ItemTemplate>
<li>
<%# Container.Dataitem%>
</li>
</ItemTemplate>
<HeaderTemplate>
Les erreurs suivantes se sont produites
<ul>
</HeaderTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
与 [rptErrors] 组件关联的数据列表将是一个 [ArrayList] 对象,其中包含一组错误消息。因此,如上所示,<%# Container.Dataitem%> 指代当前的错误消息。[panelsimulations] 容器的可视化表示如下:
![]() |
编号 | 名称 | 类型 | 属性 | 角色 |
面板 | EnableViewState=false | 模拟视图 | ||
链接按钮 | EnableViewState=false | 链接到表单 | ||
DataGrid | EnableViewState=false | 负责显示放置在 [DataTable] 对象中的模拟数据 |
在 [Webmatrix] 中,[dgSimulations] 组件的配置如下。在 [dgSimulations] 属性窗口中,我们选择 [AutoFormat] 链接并选择 [Color 3] 配色方案:
![]() | ![]() |
接着,仍在 [dgSimulations] 属性窗口中,我们点击 [Property Generator] 链接。我们要求显示列标题:

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

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

列标题,此处为“已婚” | |
数据源中该列的名称,该名称将由 [DataGrid] 的此列显示。此处即 [DataTable] 中的“married”列。 |
第二列的定义如下:
子女 | |
children |
第三列定义如下:
年薪 | |
salary | |
{0:C} - 货币显示格式,用于获取欧元符号 |
第四列定义如下:
税额 | |
税 | |
{0:C} - 货币显示格式,用于显示欧元符号 |
我们将呈现代码和控件代码分别放在两个独立的文件中。第一个文件位于 [main.aspx],第二个位于 [main.aspx.vb]。 [main.aspx] 的代码如下:
<%@ page src="main.aspx.vb" inherits="main" AutoEventWireUp="false" %>
<HTML>
<HEAD>
<title>Calculer votre impôt</title>
</HEAD>
<body>
<P>Calculer votre impôt</P>
<HR width="100%" SIZE="1">
<FORM id="Form1" runat="server">
<asp:panel id="panelform" Runat="server">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
<TR>
<TD height="19">Etes-vous marié(e)</TD>
<TD height="19">
<asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
<asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non
<asp:CustomValidator id="cvMarie" runat="server" ErrorMessage="Vous n'avez pas indiqué votre état marital"
Display="Dynamic" EnableClientScript="False" Visible="False" EnableViewState="False"></asp:CustomValidator></TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD>
<asp:TextBox id="txtEnfants" runat="server" Columns="3" MaxLength="3"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvEnfants" runat="server" ErrorMessage="Indiquez le nombre d'enfants" Display="Dynamic"
ControlToValidate="txtEnfants" EnableViewState="False"></asp:RequiredFieldValidator>
<asp:RangeValidator id="rvEnfants" runat="server" ErrorMessage="Tapez un nombre entre 1 et 30" Display="Dynamic"
ControlToValidate="txtEnfants" Type="Integer" MaximumValue="30" MinimumValue="0" EnableViewState="False"></asp:RangeValidator></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD>
<asp:TextBox id="txtSalaire" runat="server" Columns="10" MaxLength="10"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvSalaire" runat="server" ErrorMessage="Indiquez le montant de votre salaire"
Display="Dynamic" ControlToValidate="txtSalaire" EnableViewState="False"></asp:RequiredFieldValidator>
<asp:RegularExpressionValidator id="revSalaire" runat="server" ErrorMessage="Salaire invalide" Display="Dynamic"
ControlToValidate="txtSalaire" ValidationExpression="\s*\d+\s*" EnableViewState="False"></asp:RegularExpressionValidator></TD>
</TR>
</TABLE>
<P>
<asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
<asp:Button id="btnEffacer" runat="server" Text="Effacer" CausesValidation="False"></asp:Button></P>
</asp:panel>
<asp:panel id="panelerreurs" runat="server" EnableViewState="False">
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<ItemTemplate>
<li>
<%# Container.Dataitem%>
</li>
</ItemTemplate>
<HeaderTemplate>
Les erreurs suivantes se sont produites
<ul>
</HeaderTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</asp:panel>
<asp:panel id="panelsimulations" runat="server" EnableViewState="False">
<P>
<asp:DataGrid id="dgSimulations" runat="server" BorderColor="#DEBA84" BorderStyle="None" CellSpacing="2"
BorderWidth="1px" BackColor="#DEBA84" CellPadding="3" AutoGenerateColumns="False" EnableViewState="False">
<SelectedItemStyle Font-Bold="True" ForeColor="White" BackColor="#738A9C"></SelectedItemStyle>
<ItemStyle ForeColor="#8C4510" BackColor="#FFF7E7"></ItemStyle>
<HeaderStyle Font-Bold="True" ForeColor="White" BackColor="#A55129"></HeaderStyle>
<FooterStyle ForeColor="#8C4510" BackColor="#F7DFB5"></FooterStyle>
<Columns>
<asp:BoundColumn DataField="marié" HeaderText="Marié"></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ôt" HeaderText="Montant de l'impô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] 文件的定义如下:
[global.asax.vb] 文件内容如下:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
Imports System.Data
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' create an impot object
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' put the object in the application
Application("objImpot") = objImpot
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' start of session - create a list of empty simulations
Dim simulations As New DataTable("simulations")
simulations.Columns.Add("marié", Type.GetType("System.String"))
simulations.Columns.Add("enfants", Type.GetType("System.String"))
simulations.Columns.Add("salaire", Type.GetType("System.Int32"))
simulations.Columns.Add("impôt", Type.GetType("System.Int32"))
Session.Item("simulations") = simulations
End Sub
End Class
当应用程序启动(即收到对应用程序的首次请求)时,将执行 [Application_Start] 过程。该过程尝试通过从 OLEDB 数据源检索数据来创建一个 [tax] 类型的对象。若读者已忘记该类定义,建议查阅第 5 章。如果数据源不可用,[tax] 对象的创建可能会失败。 在此情况下,错误信息将存储在应用程序中,以便后续所有请求知晓该对象未能正确初始化。若创建成功,生成的 [impot] 对象也会存储在应用程序中,供所有税费计算请求使用。当客户端发出首次请求时,[Application_Start] 过程会为其创建一个会话。 该会话旨在存储用户将执行的各种税费计算模拟。这些数据将存储在与“simulations”会话键关联的 [DataTable] 对象中。会话启动时,该键将与一个结构已定义的空 [DataTable] 对象相关联。应用程序所需的信息保存在其配置文件 [wenConfig] 中:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\devel\aspnet\poly\webforms2\vs\impots6\impots.mdb" />
</appSettings>
</configuration>
[connectionString] 键指定了连接到 OLEDB 数据源的连接字符串。其余的控件代码位于 [main.aspx.vb] 中:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System
Imports System.Web.UI.Control
Imports System.Data
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
Protected WithEvents panelform As System.Web.UI.WebControls.Panel
Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
Protected WithEvents rfvEnfants As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rvEnfants As System.Web.UI.WebControls.RangeValidator
Protected WithEvents rfvSalaire As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
Protected WithEvents dgSimulations As System.Web.UI.WebControls.DataGrid
Protected WithEvents revSalaire As System.Web.UI.WebControls.RegularExpressionValidator
Protected WithEvents cvMarie As System.Web.UI.WebControls.CustomValidator
Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
' local variables
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheErreurs(ByRef erreurs As ArrayList)
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub afficheSimulations(ByRef simulations As DataTable, ByRef lien As String)
...
End Sub
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
....
End Sub
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
...
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
End Sub
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
End Sub
Private Sub razForm()
...
End Sub
Private Sub cvMarie_ServerValidate(ByVal source As System.Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs)
...
End Sub
End Class
该代码处理的第一个事件是 [Page_Load]:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' first, we look at the state of the application
If CType(Application("erreur"), Boolean) Then
' the application failed to initialize
' the error view is displayed
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs)
Exit Sub
End If
' no errors - on the 1st request, the form is presented
If Not IsPostBack Then afficheFormulaire()
End Sub
在处理请求之前,我们会验证应用程序是否已正确初始化。如果未正确初始化,我们将使用 [displayErrors] 过程显示 [errors] 视图。如果这是第一次请求(IsPostBack=false),我们将使用 [displayForm] 显示 [form] 视图。
显示 [errors] 视图的存储过程如下:
Private Sub afficheErreurs(ByRef erreurs As ArrayList)
' builds a list of errors
With rptErreurs
.DataSource = erreurs
.DataBind()
End With
' container display
panelerreurs.Visible = True
panelform.Visible = False
panelsimulations.Visible = False
Exit Sub
End Sub
该过程接收一个参数,即 [errors] 中类型为 [ArrayList] 的错误消息列表。我们只需将此数据源绑定到负责显示它的 [rptErrors] 组件即可。
显示 [form] 视图的存储过程如下:
Private Sub afficheFormulaire()
' displays the form
panelform.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
此过程仅将 [panelform] 容器设为可见。组件将显示其提交后的值或上一次的值(VIEWSTATE)。
当用户在 [form] 视图中单击 [Calculate] 按钮时,会向 [main.aspx] 发送一个 POST 请求。系统将执行 [Page_Load] 过程,随后执行 [btnCalculate_Click] 过程:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' we check the validity of the data entered; if there are any errors, we report them
If Not Page.IsValid Then
afficheFormulaire()
Exit Sub
End If
' no errors - tax is calculated
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' the result is added to existing simulations
Dim simulations As DataTable = CType(Session.Item("simulations"), DataTable)
Dim simulation As DataRow = simulations.NewRow
simulation("marié") = CType(IIf(rdOui.Checked, "oui", "non"), String)
simulation("enfants") = txtEnfants.Text.Trim
simulation("salaire") = CType(txtSalaire.Text.Trim, Long)
simulation("impôt") = impot
simulations.Rows.Add(simulation)
' put the simulations in the session
Session.Item("simulations") = simulations
' the simulations page is displayed
afficheSimulations(simulations, "Retour au formulaire")
End Sub
该过程首先检查页面的有效性。请注意,当 [btnCalculate_Click] 过程运行时,验证控件已完成其工作,且页面的 [IsValid] 属性已被设置。如果页面无效,则再次显示 [form] 视图,并结束该过程。 对于 [form] 视图中 [IsValid] 属性设置为 [false] 的验证组件,将显示其 [ErrorMessage] 属性。如果页面有效,则使用启动时存储在应用程序中的 [tax] 对象计算税额。此新的模拟将添加到已执行的模拟列表中,并存储在会话中。
最后,通过以下 [displaySimulations] 过程显示 [simulations] 视图:
Private Sub afficheSimulations(ByRef simulations As DataTable, ByRef lien As String)
' we link the datagrid to the simulations source
With dgSimulations
.DataSource = simulations
.DataBind()
End With
' link
lnkForm2.Text = lien
' the views
panelsimulations.Visible = True
panelerreurs.Visible = False
panelform.Visible = False
End Sub
该过程有两个参数:
- [simulations] 中的模拟列表,类型为 [DataTable]
- [link] 中的链接文本
[simulations] 数据源已绑定至负责显示它的组件。链接文本放置在视图中 [LinkButton] 对象的 [Text] 属性中。
当用户点击 [form] 视图中的 [Delete] 按钮时,将执行 [btnDelete_click] 过程(始终在 [Page_Load] 之后):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' displays the empty form
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' empty the form
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
上面的代码非常简单,无需任何注释。我们还需要处理对 [errors] 和 [simulations] 视图链接的点击:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' displays the form
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' displays the form
afficheFormulaire()
End Sub
这两个过程仅用于显示 [表单] 视图。我们知道,该视图中的字段将接收其提交值或其上一次的值。由于在此情况下,客户端的 POST 请求未发送任何表单字段值,因此这些字段将恢复为上一次的值。因此,表单将显示用户输入的值。
8.9.4. 测试
应用程序所需的所有文件都放置在名为 <application-path> 的文件夹中: ![]() | [bin] 文件夹中包含应用程序所需的 [import]、[importData] 和 [importOLEDB] 类所在的 DLL 文件: |
如有需要,读者可参阅第 5 章,其中解释了如何创建上述提到的 [impot.dll] 文件。完成此操作后,使用参数 (<application-path>,/impots6) 启动 Cassini 服务器。然后,我们使用浏览器访问 URL [http://impots6/main.aspx]:

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

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






















