基于 jQuery 的 Ajax.Net 库






4.91/5 (68投票s)
基于 jQuery 的 Ajax.Net 库
引言
一套基于 jQuery、支持 Ajax 的 ASP.NET 控件,包括:- 多列自动完成
- 掩码输入
- 日历下拉框
- 验证(包括标注式验证器)
- 拼写检查
- 标签栏控件
还包括一组辅助控件,使常规的 ASP.NET 编程更加简便,例如:
- 带有插入行和总计部分的 GridView
- 自动生成插入/更新语句的 SqlDataSource
目录
历史
大约一年前,我开始使用 Ajax.NET Ajax/CTP
。虽然 Ajax CTP 让为应用程序添加 Ajax 功能变得相当容易,但我遇到了一些问题:
- 我的大多数用户仍在使用 IE6。Ajax CTP 有一个已知问题,即
SELECT
和BUTTON
元素会穿透所有弹出窗口。这影响了所有使用弹出行为的组件(例如日历和自动完成扩展器)。 Auto-complete
组件非常不灵活。大多数时候,我希望我的自动完成功能有多个独立的列,而不是把所有东西都堆在一起。此外,对自动完成扩展器进行自定义也相当困难:例如,我曾尝试显示名字、姓氏和社保号(SSN),并在用户选择时只填充 SSN。Masked edit
组件也相当不灵活。例如,如果要收集社保号,我希望空字段保持为空,而有任何数据的字段则填充掩码字符。但默认情况下,它要么移除所有掩码字符,要么保留它们,这导致我的空字段被掩码字符填充。- 大小——尽管这些 JavaScript 文件会被浏览器缓存,但它们的大小很容易从大约 50K 开始,对于行为脚本可能超过 100K。
在尝试解决所有这些问题时,我想创建一个新的组件,而不想创建一个有相关学习曲线的新框架。创建一个带有日历弹出窗口的日期字段,不应该需要多个标签来创建输入字段、弹出按钮、日历扩展器、掩码编辑扩展器、验证器、标注式验证器……它应该像设置一个属性一样简单。
附件中的库是我解决所有这些问题的尝试。这个库的一个优点是没有额外的学习成本。你几乎可以直接用 <lib:Input/>
替换 <asp:TextBox/>
,用 <lib:InsertableGrid/>
替换 <asp:GridView/>
,用 <lib:TableDataSource/>
替换 <asp:SqlDataSource/>
。
这个库功能相当丰富,在本文的篇幅内很难讨论所有代码的内部工作原理。因此,我只想概述主要的对象和接口。
Ajax
为了替代 AJAX-CTP,我决定使用 jQuery 库。代码能变得如此简洁,真是令人惊叹。我平均的 JavaScript 代码只有大约 6K(未压缩),而 AJAX CTP 的行为脚本平均约为 100K。诚然,这些 JavaScript 文件会被浏览器缓存,但差异仍然巨大。你可以随意浏览代码,看看内部是如何实现的。所有的 Ajax 交互都是隐藏的——你除了包含<asp:ScriptManager EnablePageMethods="true"/>
之外,不需要做任何其他事情。示例应用程序设置。
示例应用程序 (default.aspx) 使用 MS-Sql Server 的示例数据库 pubs。唯一缺少的部分是拼写检查器字典。它假设存在一个名为 Dictionary 的表,其中有一个名为 Word 的列。你可以在网上搜索包含英语词典的文件,但即使缺少这部分,示例也应该能正常工作。默认数据库
出于懒惰,我在库中的某些地方决定不明确传递连接字符串。因此,默认情况下,我会使用 web.config 文件中定义的最后一个连接。Utils.ConStr
属性将获取此默认连接。Input 控件
这个库的核心是 Input 控件。该控件内置了掩码、水印、自动完成、验证(范围、比较、必需和正则表达式验证器)以及验证失败时的等效标注式扩展器。它派生自 TextBox,可以像文本框一样使用——实际上,我通常只是盲目地用 cc1:Input 替换 asp:TextBox。然而,它包含一些额外的属性,可以用来赋予它更强大的功能。
DataType |
要创建的控件类型。可能的值有:
|
||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MinVal / MaxVal | 控件的最小值和最大值。内部会生成 RangeValidator 以确保值在范围内。如果任一值以 '#' 开头,则假定它是一个要进行比较的控件的 ID,并将生成 CompareValidator。但在这种情况下,只会使用 MinVal 或 MaxVal(而不是两者都用)进行比较,而不是范围比较。 可以使用特殊值 ${CurDate} 来与当前日期进行比较。![]() <lib:Input ID="qty" runat="server" DataType="Int"
MinVal="0" MaxVal="99" />
|
||||||||||||||||||||
必需 | Required - 必需字段标志。将生成 RequiredFieldValidator。同时会尝试执行客户端验证。 | ||||||||||||||||||||
Watermark | ![]() 当控件为空时生成水印文本。如果使用了水印并且用 JavaScript 修改了输入控件的值,需要使用特殊的 val() 函数来确保水印被正确显示/隐藏(例如: this.val('hello'); )。 <lib:Input ID="name" runat="server"
Watermark="press any key to see list of authors" />
|
||||||||||||||||||||
Mask | 掩码输入验证。输入文本必须与掩码精确匹配,否则会出错。字段为空时,不显示掩码字符。内部使用正则表达式验证器来强制有效性。以下是可用的掩码字符:
<lib:Input ID="phone" runat="server" Mask="999-999-9999" />
|
||||||||||||||||||||
方法 | ![]() Method - 仅用于 DataType=List。如果提供,则假定为提供自动完成项的 PageMethod 名称。否则,将使用下拉控件,其值由 ChkVal 属性提供(见下文)。 WebMethod 需要有以下签名: [WebMethod(), ScriptMethod()]
public static string GetPayTerms(String prefixText, String contextKey) {
return "Cash\tCash is always good!!!\n"
+ Utils.Tbl2Str("SELECT distinct top 10 payterms, @p, @c from sales",
"@p", prefixText, "@c", contextKey);
}
<lib:Input ID="payTerms" runat="server"
DataType="List" Method="GetPayTerms"/>
它必须返回一个字符串。每行由 '\n' 分隔,每列由 '\t' 分隔。在简单情况下,返回值可以是类似 "Male\nFemale" 或 "M\tMale\nF\tFemale" 的形式。 |
||||||||||||||||||||
OnSelect | 仅在自动完成模式下使用(必须指定 Method)。这是当用户从自动完成列表中选择一个值时要调用的客户端方法。如果未提供,默认方法如下:function AutoCompleteDefault(input, data) {
input.value = data[0];
} 该方法的第一个参数是要修改的输入控件,第二个是转换为数组的选定行(每列是一个条目)。如你所见,默认情况下,输入将被设置为选定行的第一列。但是,你也可以在这里做更多的事情,比如设置其他元素的值(例如:<script runat="server">
[WebMethod(), ScriptMethod()]
// column 0 - last name
// column 1 - first name
// column 2 - phone
public static string GetAuthors(String prefixText, String contextKey) {
return Utils.Tbl2Str(@"SELECT distinct top 10 au_lname, au_fname, phone
from authors where au_lname like @v+'%'", "@v", prefixText);
}
</script>
<script>
function SelectAuthor(inp, data) {
inp.value = data[0]; // input is the control (id=last)
$get('<%=phone.ClientID%>').value = data[2]; // set phone to the 3-rd col
}
</script>
<lib:Input ID="last" runat="server" DataType="List"
Method="GetAuthors" OnSelect='SelectAuthor'/>
<lib:Input ID="phone" runat="server" Mask="999-999-9999" />
|
||||||||||||||||||||
ChkVal | ChkVal 与 Method 一起用于获取列表项。 如果提供了 Method,那么 ChkVal 是一个参数字符串,将作为 contextKey 发送给 WebMethod。在发送之前,它会按 ';' 字符分割,然后所有以 '#' 开头的条目将被假定为控件的 ID。这些控件的值将被提取出来,然后连接在一起发送到服务器。一个有用的例子是为汽车制造商拉取车型。ChkVal 可以是 'Car;#Maker',其中 Maker 是控件的 ID。那么 PageMethod 调用中的 contextKey 将是 'Car;Toyota'。 如果未指定 Method(将生成下拉控件),则 ChkVal 被假定为 SELECT 语句(如果 ChkVal 以 "SELECT" 开头)或下拉列表的选项列表。 如果 ChkVal 是一个 SELECT 语句,结果集的第一列被假定为下拉列表的值,第二列是下拉列表的文本。数据从默认数据库中获取(参见 数据库 部分)。 示例:
<lib:Input ID="store" runat="server" DataType="List"
ChkVal="SELECT stor_id, stor_name from stores"/>
![]() 如果 ChkVal 不是 SELECT,那么它被假定为下拉列表的项列表,格式为 item;item;...;item 或 key=value;...key=value。 示例:
<lib:Input ID="hair" runat="server" DataType="List"
ChkVal="Brn=Brown;Black;Blond;Gry=Gray"/>
![]() 注意: 关于微软 DropDown 实现的一点让我抓狂的是,它在绑定期间对错误值的反应——我们可能都见过,如果绑定的值在下拉列表中找不到时抛出异常。在这里,如果(Text 属性)值在选项列表中找不到,它只会被添加到列表的末尾。 |
||||||||||||||||||||
Desc | ![]() 默认情况下,验证错误消息将基于失败的验证类型,并且足够通用,不会提及失败验证的字段。当在客户端捕获到验证错误时,这不是问题,因为会使用验证器标注来指向该字段。但如果验证错误在后端捕获,并且验证摘要控件充满了“"请输入一个介于 0 和 99 之间的值"”这样的消息,就可能令人困惑。此属性可用于提供字段描述。那么同样的错误消息将看起来像“请输入数量的值,介于 9 和 99 之间”——这样就好多了。 注意: 如果未指定 |
||||||||||||||||||||
ErrMsg | 允许完全自定义错误消息。但是,所有验证错误消息都将被替换为提供的消息,无论是必需字段、范围还是正则表达式验证失败。 |
InsertableGrid
<lib:InsertableGrid ... AllowInsert="true" SelectableRow="true" OnLoadTotals="GridView1_LoadTotals">
<Columns>
<lib:BoundInput DataField="ord_num" HeaderText="Order Number" SortExpression="ord_num" />
<%-- next line creates a date column. Edit field is 80px wide --%>
<lib:BoundInput DataField="ord_date" HeaderText="Order Date" SortExpression="ord_date"
DataType="Date" Width="80px" />
<%-- next line creates 80px wide numeric column with valid data range 0-90 --%>
<lib:BoundInput DataField="qty" HeaderText="Quantity" Width="80px"
SortExpression="qty" DataType="Int" MinVal="0" MaxVal="99"/>
<%-- next line creates 50px wide autocomplete column with maxLength of 12 char.
GetPayTerms page method will be executed to fetch data --%>
<lib:BoundInput HeaderText="Terms" SortExpression="payterms" Width="50px" MaxLength="12"
DataType="List" Method="GetPayTerms" ChkVal="ChkVal Parameters"/>
<%-- next line creates drop down list box with options from ChkVal prop --%>
<lib:BoundInput HeaderText="Title" SortExpression="title_id" Width="100px"
DataType="List" ChkVal="select title_id, title from titles" />
<lib:ExtButtons />
</Columns>
<%-- create a total portion --%>
<TotalsTemplate>
<tr><td align="right" colspan='2'>Total Quantity</td>
<td align="right"><asp:Label ID="totQty" runat="server" Text="to be filled" /></td></tr>
</TotalsTemplate>
</lib:InsertableGrid>
在这里,我继续了前段时间开始的工作(参见InsertableGrid文章)。增加了一些新功能。总的来说,以下是实现的功能列表:
- 可选择性地生成插入行。默认情况下,插入行将使用为更新行定义的编辑器,除非为列定义了
<InsertTemplate/>
。不使用页脚模板。为RowInserting / RowInserted
情况提供了相应的事件。 - 可以为小计部分定义单独的
<TotalsTemplate/>
。可以通过提供LoadTotals
事件处理程序来提供动态数据。 - 可选择地通过点击行上的任何位置来选择 GridViewRow。
- 更改选择会将插入/编辑行的更改刷新到数据库(如果适用),并将选择(编辑)设置为新行。
- 可选择地在没有数据的情况下也创建标题行(而不是使用 EmptyData)。
这些功能可以通过以下属性控制:
AlwaysHeader | 通常,如果没有数据,GridView 会显示 EmptyDataTemplate。通过设置此属性,可以强制它无论有无数据都显示标题行。 |
---|---|
AllowInsert | 将根据为常规编辑行指定的字段编辑器自动生成插入行。有关插入行的讨论,请参阅 InsertableGrid 文章。 |
SelectableRow | 设置后,单击网格行上的任何位置都会选择整行。如果 AllowInsert 也被设置,那么在切换选择的同时,它将刷新当前编辑/插入行的数据更改,并将新选择的行置于编辑模式。 |
TotalsTemplate | 允许指定总计部分。在上图中,请看 Total Quantity 字段。例如:<lib:InsertableGrid ... OnLoadTotals="GridView1_LoadTotals">
...
<TotalsTemplate>
<tr><td align="right" colspan='2'>Total Quantity</td>
<td align="right"><asp:Label ID="totQty"
runat="server" Text="TBD" /></td></tr>
</TotalsTemplate>
</lib:InsertableGrid>
|
LoadTotals | 为总计行提供数据时生成的事件。例如://event generated by the grid to get values for the total row controls
protected void GridView1_LoadTotals(object sender, DataEventArgs e) {
Utils.SetCtrlText(e.cont, "totQty",
Utils.Scalar("SELECT sum(qty) from sales where stor_id=@s",
"@s", store.Text));
}
|
DataError | 在插入/更新/删除操作期间发生错误时生成的事件。这样我就不需要捕获3个事件来检查错误代码,而只需捕获一个。 |
RowInserting | 在将数据插入数据库之前生成的事件。将使用 GridViewUpdateEventArgs 。 |
RowInserted | 在插入完成后立即生成的事件。使用 GridViewUpdatedEventArgs 。 |
FlushEditChanges | 将编辑行数据(插入或更新)刷新到数据源的方法。当表单上有“保存”按钮并且您想确保用户最后键入的内容被刷新到数据源时非常有用。 |
Input
控件与 InsertableGrid
集成,可以获得更强大的功能。因此,提供了两个额外的类。它们间接地派生自 DataControlField 基类,因此可以在 InsertableGrid、GridView 或 FormView 中使用。BoundInput
<lib:InsertableGrid ... AllowInsert="true" SelectableRow="true" >
<Columns>
<lib:BoundInput HeaderText="Order Number" SortExpression="ord_num" Mask="CCC-999999" />
<lib:BoundInput HeaderText="Order Date" SortExpression="ord_date" DataType="Date"
Width="80px" />
<lib:BoundInput HeaderText="Quantity" Width="80px"SortExpression="qty" DataType="Int"
MinVal="0" MaxVal="99"/>
<lib:BoundInput HeaderText="Terms" SortExpression="payterms" Width="50px" MaxLength="12"
DataType="List" Method="GetPayTerms" ChkVal="ChkVal Parameters"/>
<lib:BoundInput HeaderText="Title" SortExpression="title_id" Width="100px"
DataType="List" ChkVal="select title_id, title from titles" />
<lib:ExtButtons />
</Columns>
</lib:InsertableGrid>
这个类的巨大好处是,它允许你绕过 Item/EditTemplate
来处理像 MaxLength
验证或设置 TextBox Width
这样的简单事情。由于它公开了 Input
控件的其他属性,它提供了几乎所有 Input
控件能做的功能。对我来说,这个类取代了 <asp:BoundField> 作为默认的列定义。在非编辑模式下,这个类还将执行以下操作:
- 对数字列数据进行右对齐,使用 #,### 或 #,###.00 格式掩码格式化数据。
- 执行键值替换(将键替换为下拉列表列中的文本值),这样你就不必手动编写 select 语句来获取键和描述。
以下是该控件公开的属性列表(有关 asp:BoundField 公开的其他属性,请参阅 MSDN):
宽度 | Width - Input 控件的宽度(TextBox 宽度)。 |
---|---|
DataType | DataType - 字段数据类型。有关可能的数据类型,请参阅 Input 控件。 这里使用了一个额外的数据类型 Check。除了它在绑定要求方面限制较少(你有没有遇到过必须将复选框绑定到空值的情况?),它还允许最小化模板的使用。 |
InsertVisible | 清除后,该字段的插入行数据输入将被禁用。 |
ReadOnly | UpdateVisible - 设置后,该字段的更新行数据输入将被禁用。 |
DataField | 如果未提供,则将使用 SortExpression 来指定列名。 |
Rows | Rows - 仅用于多行输入 - 多行输入控件的行数。 |
MaxLength | MaxLength - 输入控件的最大长度。 |
MinVal | 有关描述,请参阅 Input 控件。 |
MaxVal | 有关描述,请参阅 Input 控件。 |
Mask | 有关描述,请参阅 Input 控件。 |
ChkVal | 有关描述,请参阅 Input 控件。 |
方法 | 有关描述,请参阅 Input 控件。 |
OnSelect | 有关描述,请参阅 Input 控件。 |
ExtButtons
这个类实现了行的控制按钮。对于常规行,将生成选择和删除按钮;对于编辑或插入行,将生成更新/插入和取消按钮。单击删除按钮时,会生成确认消息。
TabControl
<lib:TabControl ID='tabs' runat="server">
<Tabs>
<lib:TabPage TabId='tab1' Title="Tab1" />
<!-- show confirmation before switching to this tab -->
<lib:TabPage TabId='tab2' Title="Tab2"
Confirm='You sure you want to switch?'/>
<!-- clicking on this tab will cause postback -->
<lib:TabPage TabId='tab3' Title='Tab3' PostBack="true" />
</Tabs>
</lib:TabControl>
我之所以对 AjaxCTP 标签控件不满意,主要是因为如果我有一个足够大的表单,并且想在 FormView 中间放置标签页,它就无法工作。所以替代品必须具备以下功能:
我设计的标签页满足了这两个要求以及更多。以下是它公开的属性:
选项卡 | TabPages - TabPage 的集合。每个 TabPage 公开以下属性:
|
||||||||
---|---|---|---|---|---|---|---|---|---|
TabChanged | 当用户点击标签页导致回发时生成的事件。 | ||||||||
CurrentTab PrevTab | 公开当前选择和先前选择的属性。可用于事件处理程序处理期间执行某些操作或更改用户选择。设置 CurrentTab=PrevTab 实际上只会刷新屏幕。 |
TableDataSource
这个类派生自 SqlDataSource,旨在自动生成 INSERT/UPDATE/DELETE 语句和所有相关参数。
我在引言中提到,我经常需要为一个已经有超过200列的表添加一两列。更新插入和更新语句以及向 DataSource 添加所有参数很快就变成了一件真正的苦差事。
另一个问题是 SqlDataSource 不会检查数据是否已更改——如果你想更新数据,即使没有更改,它也会更新数据。大多数情况下,如果没有更改,我不想进行更新。当 `InsertableGrid` 可以通过单击行就进入编辑模式时,这一点变得尤其重要。
对我来说,另一个问题是在同一事务范围内对数据库执行其他操作,例如,在插入订单明细记录时,更新订单头表中的总计。
TableDataSource 是在 SqlDataSource 之上一个相当薄的层。然而,它极大地简化了我的编码工作。以下是它(相对于 SqlDataSource)公开的附加属性:
TableName | 用于生成所有更新的表名。UPDATE/DELETE/INSERT 语句以及所有参数都将自动针对此表生成,除非指定了 InsertCommand、UpdateCommand 或 DeleteCommand——那样的话将使用默认实现。 |
---|---|
InsertIdentity | 如果表中的某一列是自增列,那么在成功插入后,InsertIdentity 将被填充为由 `TableName` 指定的表生成的标识值。 |
GetView | 从设计上看,Datasource 只是 DatasourceView 类的一个非常薄的包装器。DatasourceView 类是执行在数据库中更改数据的繁重工作的类。然而,微软对我们隐藏了视图。此函数允许您在需要明确进行数据更新/插入时访问该视图。 |
事务 | Transaction - 默认情况下,SqlDataSource 会在数据访问完成后关闭使用的连接。但是,如果您在数据访问之前设置此属性,则数据访问将在事务范围内完成。 当 GridView 的数据是两个表的连接时,我需要这个功能。然后我会为每个表设置一个 TableDataSource。GridView 中提供的那个会被自动调用,在该 DataSource 的 OnUpdated 处理程序中,我会更新第二个表。 protected void ds_Updated(object sender, SqlDataSourceStatusEventArgs e) {
if (e.Exception == null) {
ds1.Transaction = e.Command.Transaction;
ds1.GetView().Update(ds.Keys, ds.NewValues, ds.OldValues);
}
}
<lib:TableDataSource ... TableName="Detail" SelectCommand="SELECT * FROM Detail"
OnInserted="ds_Inserted" OnUpdated="ds_Updated">
...
</lib:TableDataSource>
|
Keys, NewValues, OldValues | 所有这些属性都公开了用于更新的数据。有关这些属性如何有用的示例,请参阅 Transaction 属性。 |
DeleteParameters UpdateParameters InsertParameters | 这些都派生自 SqlDataSource,严格来说它们不是必需的。当您需要强制某个参数的值为特定值(其他控件、会话或请求变量、当前时间)时,您会想要提供它们。 当前时间通过使用 ${CurTime} 值来指定。例如: <InsertParameters>
<asp:ControlParameter ControlID="store" Name="stor_id"
PropertyName="Text" Type="String" />
<asp:SessionParameter Name="created_by"
SessionField="__LoggedInUser__" />
<asp:Parameter Name="created_on" DefaultValue="${CurTime}" />
</InsertParameters>
|
Utilities 模块 (Utils)
Utilities 模块提供了一堆我在整个库中使用的静态函数。以下是我认为最有用的一些:
ConStr
返回默认的数据库连接——即 web.config 文件中定义的最后一个连接字符串。
public static void SetCtrlText(Control nc, string name, object text)
在命名容器中查找具有特定名称的控件,并将其值设置为文本。会处理不同类型的控件,如 DropDowns、Labels、TextBoxes、Checkboxes... 如果名称为 null,则会设置 nc 的文本。
public static DataTable GetTable(string sql, params object[] prm)
为 select 语句返回一个 DataTable。第一个参数是 select 语句,其余的参数是:
- Connection - 用于命令的连接对象(如果未提供,则使用默认连接字符串 (ConStr))。
- Transaction - 使用连接和事务获取数据。如果指定,将使用与此事务关联的连接。
- SQL 参数的名称/值对。
示例
DataTable tbl = Utils.GetTable("SELECT distinct top 10 payterms, @p, @c from sales",
"@p", prefixText, "@c", contextKey);
public static string Tbl2Str(string sql, params object[] prm)
与 GetTable 相同,但返回的数据被连接成一个长字符串,其中行由换行符 (\n) 分隔,列由制表符 (\t) 分隔——适合用于自动完成。
public static Object Scalar(string sql, params object[] prm)
执行 sql 字符串并返回结果。如果 sql 字符串以 "SELECT" 开头,则返回值为返回数据的第一行第一列。否则,它是受 INSERT/DELETE/UPDATE 操作影响的行数。其他参数可以是连接、事务或名称-值对。(参见上面的 GetTable 函数)
版权声明
我个人对所附代码没有任何许可限制。然而,其中某些部分(即 jQuery 和 Josh Bush 的掩码编辑插件)可能有一些许可限制。我鼓励您仔细检查与这些组件相关的可能限制。
摘要
我注意到关于 jQuery 库的一件事是,在使用它 15 分钟后,你就不禁会想如何能让你的代码更精简一点。我在这里介绍的库将这一理念带入了 HTML 标记编程——一个具有足够复杂的 Grid/FormView 的屏幕可以轻松地缩减一半。你最终会得到更少的标记,剩下的部分也更易于维护。除此之外,所包含的 javascript 库虽然不如 Ajax CTP 全面,但大小却只是它的一小部分。我希望你能像我一样,从这个库中获得乐趣。
历史
2008年11月5日 - 初始发布
2008年11月12日 - 对日期本地化进行了一些修复。仍需对不同的国际化设置进行更多测试。特别是目前一周总是从周日开始。
2008年11月13日 - 修复了日历弹出窗口的一个小错误。实现了日历释放时重置焦点的功能。
2008年11月19日 - 实现了 Double 数据类型。此外,WebLib 现在将注册所需的 CSS。
2008年12月8日 - 添加了此项目到 CodePlex.com 的引用。
2009年3月9日 - 完全重构了验证功能。解决了 ASP:UpdatePanel 中的 IE 内存泄漏问题。新增了 MultiCheck 控件类型。
2008年4月23日 - 修复了以编程方式切换标签页的错误。进行了一些其他清理。
2009年5月7日 - 进行了一些清理。将示例更改为使用 UpdatePanel。将 MultiChk 类型更改为 DropCheck。
2009年8月25日 - 修复了设计模式。为公共属性添加了描述。切换为使用一些 LINQ 查询。
待办事项
这个库是我日常使用的代码的一部分。我时不时会有幸回去改造我之前做过的项目。平均而言,通过使用这个库,我可以实现大约 50% 的代码/标记减少——从长远维护来看,这应该能让我少长不少白发。
虽然这个库对我来说似乎工作得很好,但我的环境可能相当有限。我只需要支持 IE6 和仅限英语。因此,我可以想象在不同的设置下,有些东西可能不会按预期工作。
- 国际化
- 其他浏览器(IE7 似乎可以工作,除了拼写检查器;拼写检查器似乎在 Firefox 上也能工作)。
- InsertableGrid 验证组 - 目前,将对数据更新执行默认页面验证。

非常感谢您的捐赠