一个轻量级的支持AJAX.NET的网格控件






4.65/5 (9投票s)
一种轻量级的方法来创建支持AJAX.NET的网格,内置高级功能。
引言
管理数据库中的项目列表是大多数Web应用程序的基础,但创建用户友好的网格可能会有问题。在AJAX.NET UpdatePanel
中创建Microsoft网格控件的显而易见的解决方案,在处理大量数据时会创建非常大的HTML页面,导致响应时间过长。此外,每个网格都必须在每个页面上单独创建,这使得创建和维护共同的“外观和感觉”变得困难。
这个轻量级网格输出非常紧凑的HTML,并使用JavaScript在更新时动态生成输入框或选择框。它包括内置的AJAX排序和过滤功能,非常用户友好,并免费提供CSV导出功能。它具有固定的表头和可滚动的内容,便于处理大量数据。
使用XML控制文件和单个通用样式表,可以非常快速地以标准方式生成许多不同的网格。可以定义和使用多个AjaxGrid,甚至可以在同一页面上。
您可以自定义网格列数据以包含链接、图像等,并支持您自己的自定义验证。
背景
大多数开发人员会通过使用 UpdatePanel
来涉足诱人的AJAX.NET领域,但直接调用AJAX方法可以更加灵活和高效。
任何由ASP.NET代码在服务器端更新的HTML元素都必须包含一个长且“装饰”过的ID,这些ID在典型的网格和UpdatePanel
中被广泛使用。但是,如果您直接生成HTML,那么您可以依赖JavaScript遍历DOM来为元素分配事件,为用户动态弹出HTML元素以进行编辑请求,并识别任何已更改的元素。AJAX允许您在服务器端完成大部分繁重的工作,这样您就不会试图用JavaScript编写整个应用程序(这是一个真正的性能“陷阱”)。
由于HTML更加紧凑,要传输的数据也少得多。可以有效地向用户返回大量行。此实现包括一种在大多数情况下和用户控制下避免分页的方法,即使是对于大量数据。借助固定网格标题和可滚动数据部分,用户的滚动体验可以真正利用浏览器优化的平滑滚动和后台更新。这种方法对拨号上网的用户尤其友好。
此控件在会话期间会记住用户对每个网格的偏好,也可以设置为在会话之间记住这些偏好。多亏了AJAX.NET,用户在浏览器中的数据也可以随时在服务器端可用;您可以扩展该控件以记住用户输入的所有数据(即使是逐个按键的数据),甚至允许在会话中断时关闭浏览器并在以后在同一点恢复工作——就像它是一个本地应用程序一样。特别是经验不足的用户会感谢不会意外丢失输入数据的安全性。
Using the Code
源文件被设置为一个完整的ASP.NET 2.0网站。
此代码使用了由开源“Tackle”应用程序设置的数据库中的两个表,该应用程序用于SCRUM流程管理,其本身使用带有UpdatePanel
的AJAX.NET。这是一个有趣的比较。您可以在此处下载它。(我还为示例数据库包含了一个最小的数据库.mdf文件,如果您不想为此费心的话。)
您需要编辑 AjaxGrid.db.cs 中的连接字符串。它假定数据库是SQLExpress实现。
您自己实现的此控件几乎肯定会包含一个经过编辑的样式表,以匹配您自己的应用程序品牌(您可能还想改进图形!)。
最后,您需要编辑 ajaxgrid.xml 来定义和格式化您的 Web 应用程序中的每个网格。
源文件
这个控件不能很好地作为一个完全独立的程序集工作,因为您需要将它的 .css 和 .js 文件以及 ajaxgrid.xml 包含在包含的网站中。您可能还需要根据您的 Web 应用程序的需求调整核心源文件。
- Default.aspx 和 app.css 是您自己的目标 Web 应用程序的模拟文件。Default.aspx.cs 展示了如何序列化和反序列化“配置文件”数据,以及如何为每个页面注册自定义验证和回发方法。
- ajaxgrid.xml 指定了语言特定的文本以及每个网格的格式和内容。每个网格都有一个名称,并定义了SQL表、主键和基本查询字符串。然后定义网格的每一列,包括数据类型、可显示标题和SQL列名。如果某一列的可用选项受到限制,可以指定静态列表或数据库生成的列表。您可以定义任意数量的网格,它们都将具有相同的外观和行为。此文件在Web应用程序加载时加载到静态数组中,以确保运行时高效访问。
- web.config 展示了如何设置AJAX.NET子系统以使用JSON格式的Web服务进行AJAX。
- AjaxGrid.Webservices.cs 包含由JavaScript调用的实际AJAX方法。
- AjaxGrid.db.cs 为网格实现了数据库访问。任何生产Web应用程序都应实现
MultipleActiveResultSets
、防御SQL注入并实现异步I/O,因此此控件完成了所有这些。然而,异步I/O是AJAX.NET的一个问题,因为目前没有可用的JSON异步处理程序,并且AJAX回调仍然仅限于同步I/O。不要混淆客户端的异步AJAX调用(没问题)与服务器请求的异步处理(高效使用服务器线程所需)。此处对此进行了讨论。 - AjaxGrid.main.cs 是此控件的核心代码。它包含通过生成 HTML 字符串来绘制或重新绘制控件,或仅绘制控件的数据部分的方法。HTML 可用于在服务器端(页面初始渲染或回发)或客户端(响应 AJAX 调用)填充控件。每行可以包含多种不同类型的单元格(由 XML 控制文件定义),并且有创建新方法来处理其他数据类型(例如数据、货币或图像)的范围。
- AjaxGrid.js 是该控件的配套 JavaScript。它展示了如何使用 JavaScript 遍历表格单元格及其内容,以及如何定位弹出编辑控件以覆盖网格单元格。
- AjaxGrid.css 是该控件的配套样式表。通过将此样式表作为一个单独的文件包含,该控件大大减少了需要在每个页面中包含的样式信息。
- AjaxGrid.ascx/AjaxGrid.ascx.cs 是实际的控件。它展示了如何从 ASP.NET 页面启动异步 IO 操作。
代码的作用
服务器“手动”使用 StringBuilder
对象生成包含 HTML 的字符串。然后,HTML 可以用于在 C# 中在服务器端填充控件
divGrid.InnerHtml = AjaxGrid.Paint(Session["GridProfile"], gridId);
或从 JavaScript 中的 AJAX 回调在客户端填充控件
divGrid.innerHTML = response.html;
当点击“添加新行”图像时,标题表中会添加一行。这允许用户无论数据表滚动到何处,都能看到并编辑新行;一旦填充了列并接受了新行,表将自动重新绘制,新行将以正确的排序顺序出现在数据表中。
关注点
此控件展示了一种有趣的技术,通过AJAX回调服务器,使JavaScript设置的文本实现语言独立。在许多情况下,通过为每个单独用户自定义JavaScript文件也可以实现同样的目的,但按需回调服务器可以减少发送到浏览器的不必要数据量,并且更易于维护。
AJAX 应用程序在您的本地机器上(延迟很小)和互联网上会呈现不同。尽管应用程序正确处理了多个未完成的 AJAX 调用,但向用户显示动画 GIF 文件可以帮助用户了解 AJAX 响应正在进行中。关于是每次按键时启动 AJAX 调用还是在 "onchange
" 事件处理程序触发时启动 AJAX 调用,没有明确的规则(除了如果某个其他 JavaScript 事件点击处理程序(例如,点击按钮)被调用,您可能会丢失 onchange
操作的通知)。当要创建新行时,所有列必须是默认值或已填写,然后才能在数据库中创建新行,并且需要有一个按钮来指示该行已准备好创建。尽管如此,方便的是每列完成时都将其 AJAX 到服务器。
一种可能的网格更新实现是将它们保存在用户的会话数据中,并仅在用户明确请求“保存”命令时才实际执行更新。这模仿了许多桌面应用程序,允许用户在将更新提交到数据库之前查看更新的外观。如果多个用户可以看到相同的数据,特别是如果保存的更新在会话之间存储,这会产生影响。但即使没有这些改进,用户读取和写入行数据之间也总会存在一个窗口期。
使用数据库锁(可能通过 SQL Server 更改行数据时的通知进行补充)实现起来很复杂,并且可能会显著影响性能。一个轻量级的解决方案是记录数据上次更改的时间和人员,如果与读取行数据的时间不同,则通知用户。在许多情况下,用户可以相当容忍被告知他们必须重复编辑和更新,因为在现实世界中,如果两个人错误地同时处理同一任务,通常这是一个错误。在以下从存储过程更新行的 SQL 片段中,在执行更新之前检测到冲突,如果自原始数据读取以来其他人编辑了该行,则放弃更新。
-- @editdate DATETIME parameter is the date that
-- the record was last edited (remembered from read)
-- @userid INT parameter is the caller's id
-- @newdate DATETIME OUT parameter is the new date
-- that the record was last edited (or null if not updated)
-- @editid INT OUT parameter is the person who made that last edit
-- each table so protected needs two new columns:
-- editdate DATETIME is the date last edited
-- updaterId INT is the person who last updated the record
IF @editdate IS NOT NULL
BEGIN
SELECT @editid = updaterId FROM [table]
WHERE rowid=@rowid AND editdate <= @editdate
IF @editid IS NOT NULL AND @editid <> @userid
BEGIN
SELECT @newdate =NULL
RETURN
END
END
SELECT @newdate = GETDATE()
SELECT @editid = @userid
顺便说一下,如果上次编辑者是调用者,那就算没问题。用户可能通过使用两台不同的机器或两个不同的浏览器给自己造成了麻烦,但最有可能的是,如果网站阻止他们更新自己的编辑,他们会非常恼火。
您还可以更改每个单元格更新时的AJAX回调,使其包含新值和原始值(尽管被编辑弹出窗口覆盖,原始值仍然存在)。这允许更细粒度的方法,即仅当该行的特定列被其他人更改时才拒绝更新,而不是整个记录。
Web 应用程序中的一个主要难题是回发管理和“后退”按钮的随意使用。对于启用 AJAX 的应用程序来说,这不是一个大问题,因为用户无法通过在等待响应时刷新来两次启动相同的操作,并且“后退”按钮会将用户带离当前页面。如果您有一个不能按两次的按钮,请添加一些自定义 JavaScript 以便在按下时禁用它。