65.9K
CodeProject 正在变化。 阅读更多。
Home

用于编辑 SQL 表的 ASP.NET Web 组件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2012年11月28日

CPOL

11分钟阅读

viewsIcon

45079

downloadIcon

2589

ASP.NET C# 组件,采用插件式列格式适配器架构,用于编辑 SQL 表。

引言

我对使用 ASP.NET 数据组件(如 DataGrid)的复杂性感到沮丧,并希望对布局、字段格式化和错误检测有更多控制权,因此我决定“自己动手”。我希望能够查看分页的行列表,编辑或删除任何行,并添加新行,并且组件能自动处理关键列和各种标准数据类型。我还希望能够以最小的自定义编码实现下拉列表输入。

随着项目的演进,它被分为两个层:一个层是 System.Data.Common.DbConnection 的包装器和实用工具,用于构建具有完整格式和错误检查的 SQL 操作语句;另一个层是一个用于编辑表行的 GUI 组件。第一个组件可以在代码中操作 SQL 数据表时通用,而第二个组件则设计用于基于 Web 的数据库维护站点。

下一个开发周期涉及创建一种在列级别自定义 GUI 的方法,以便使用不同类型的 Web 控件进行编辑,并提供自定义格式化和解析。开发了一种插件适配器架构,为常见数据类型提供了标准行为,并且还可以在项目级别为特定的表列进行扩展。此机制的一个常见用途是提供一个下拉框作为输入方式,使用值表或文字值列表来填充控件。客户端 JavaScript 验证也可以包含在这些适配器中。

使用代码

EditTable 组件设计为与其他 ASP.NET 控件一样使用。Web 项目应引用 EditTable 项目,任何页面都应包含对 BFCS.Data.Common 命名空间的引用。您的项目 Bin 文件夹必须包含 Connection.dll,EditTable 类库在内部会使用它。

这是一个包含该控件的示例标记片段

//
// Sample EditTable control markup
//
<@ Register assembly="EditTable" namespace="BFCS.Data.Common" tagprefix="data" %>
//
// Style definitions are used here for setting appearance properties. You can also use literal styles.
//
//
// EditTable control instance
//
<data:EditTable ID="EditTable1" runat="server" 
    RowsPerPage="12" ScrollBars="Auto" BorderStyle="None" 
    FooterClass="th" HeadlineClass="th" 
    ItemClass="td" style="width: 700px;" 
    TableClass="table" Visible="False" 
    onerror_changed="EditTable1_Error_Changed" 
    SelectedClass="tds" KeyHeadlineClass="thk" 
    ErrorClass="tdsError" CellSpacing="0">
</data:EditTable>

为了有资格使用此控件,表必须具有单个主键列,该列可以是自动生成的,也可以由用户编辑。不支持多列主键和没有主键列的表。

组件属性、方法和事件

EditTable 组件派生自 System.Web.UI.WebControls.Table,这是一个普通的 HTML Table 标签的包装器。这意味着 BorderCellPaddingCellSpacing 等属性都可用。此外,EditTable 控件还提供了一套丰富的样式属性,此处有详细介绍

组件显示的数据由另一组属性控制,此处有详细介绍。这组属性用于定义数据库连接字符串和提供程序类型(例如,对于 Microsoft SQL Server,可能是 System.Data.SqlClient)、要修改的表或视图的名称,以及用于选择要显示和/或过滤行的实际列的 SQL SELECT 语句。在示例应用程序中,连接值是从 Web.Config 文件的 ConnnectionStrings 部分读取的,而表名和 SQL SELECT 语句是通过从预定义列表中选择要编辑的表来构建的。

此外,还有行为属性,用于打开或关闭编辑、删除和添加行的能力,并控制是否显示初始化错误以及每页显示的行数。

上述属性在 Visual Studio 的设计窗格属性表中可用。此外,还有一个 CallingAssembly 属性,必须在代码中设置(通常在页面的 Page_Load 事件处理程序中),如下面的示例所示

//
// Example of setting CallingAssembly property from Page_Load event handler
//
using System.Reflection;
using BFCS.Data.Common;

namespace EditTableDemo
{
    public partial class TableEditor : System.Web.UI.Page
    {
        protected void Page_Load(object Sender, EventArgs e)
        {
            Type pageType = Page.GetType();
            EditTable1.CallingAssembly = 
                Assembly.GetAssembly(pageType.BaseType == null || 
                pageType.BaseType == typeof(Page) ? pageType : pageType.BaseType);
//
// ...
//
        }

此属性的目的是告知控件它正在运行的程序集。(有没有更简单的方法?请告诉我!)这样做的原因在下一节中描述,用于检测自定义表适配器。

最后有一个属性 LastError,读取该属性会生成在尝试修改表时检测到的最新错误或错误组。更改时,此属性会触发 Error_Changed 事件,您的项目可以使用该事件刷新错误显示标签。这些错误可能在尝试修改表数据之前就被捕获,例如无效的字符串格式或超出范围的值,也可能由 SQL 提供程序本身返回,例如索引冲突。

提供给调用代码的主要方法是 Refresh,它的作用如其名:从数据存储中重新检索数据并重新显示表。

用户界面

表以标准格式显示,数据源的实际列名用于标记顶行。最初,所有行都显示为“显示行”。如果启用,每行都有该行的“编辑”和“删除”功能的按钮,顶部会出现一个单一的“添加”按钮。如果启用,“查找”字段会出现在底部,并附有一个搜索输入值的按钮。根据表的大小和顶行的位置,将有“上一个”和“下一个”按钮用于分页浏览表。

Add显示一个单独的添加行,带有“确定”和“取消”按钮,所有字段为空。确定按钮保存添加的行,取消按钮放弃它
未使用。将选定的行更改为编辑行,带有“确定”和“取消”按钮,如上
删除请求确认,然后删除当前行,拉取后续行
查找如果输入了值,则尝试将表定位到主键列值与输入字符串匹配的最近行,或定位到该行
上一个/下一个分页浏览表数据

自定义表列适配器

在许多情况下,您可能希望微调编辑器与表中的特定列的交互方式。一个常见的情况是,一列包含一个对用户没有意义的数字代码,但有一个查找表为有效代码分配了提示值 - 这是下拉框编辑控件的自然用途。您可能希望使用 DateTime 控件来显示和编辑数据的仅时间部分。您可能希望为少量互斥的选择使用单选按钮控件。更简单地说,可能存在一个必须强制执行的合法数值范围,或者必须检查其格式的格式,例如税号或社会安全号码。

为了处理这些情况,我创建了一个表列适配器架构。Connection 项目提供了一套标准适配器,这些适配器默认用于常见的 SQL 数据类型。对于更专业的情况,您的项目定义了自己的适配器,并将其分配给特定的表/列组合。这些类通常作为 Web 项目中的类来编写。这些类必须遵守以下条件

  • 该类必须是 BFCS.Data.Common.TableAdapter 类的子类
  • 它必须实现 BFCS.Data.Common.ITableAdapter 接口
  • 它必须有一个合适的构造函数,如下所述

BFCS.Data.Common.ITableAdapter 接口指定了处理字段数据的方法,如下面的摘录所示

public interface ITableAdapter
{
    /// Given a value retrieved from a data source, return a System.Web.Control to display it
    /// in a non-edit, non-append row
    Control DisplayValue(object value);
    /// Given a value retrieved from a data source, return a System.Web.Control to display it
    /// in an edit row
    Control EditValue(object value);
    /// Given a value retrieved from a data source, return a System.Web.Control to display it
    /// in an edit or append row. Appending flag informs routine that it is being used to
    /// display an append row; if false, it is an edit row.
    Control EditValue(object value, bool Appending);
    /// Given a value entered as a string, check for validity, modify as necessary to use
    /// in an action SQL statement. Set 'error' value to non-empty, non-null string if an error is detected
    /// in the supplied value.
    string SaveValue(string setting, out string error);
}

前三个方法生成一个 System.Web.UI.Control 派生实例,该实例将在表填充并返回之前插入到表中。在上面,“非编辑行”指的是显示但未打开编辑的行,“编辑行”指的是单击行上的“编辑”按钮后显示的行,或者在显示添加行时显示的行。SaveValue 方法用于解析在 PostBack 过程中由编辑控件返回的输入数据,检查其有效性,并在必要时重新格式化它以包含在 SQL 操作语句(INSERT INTO 或 UPDATE)中。如果此例程检测到提交值中的错误,则应将 out 参数 error 设置为用户可读的错误消息,描述错误并建议如何纠正它。

出现在 Web 应用程序中的自定义表列适配器必须用 BFCS.Data.Common.CustomTableAdapter 属性进行装饰,如下面的示例所示

[CustomTableAdapter("Calendar", "StartTime")]

正如示例所示,该属性有两个强制参数。第一个是表名,第二个是要应用适配器的列名。如果相同的适配器要应用于多个列,则必须为每个表/列组合声明一个单独的类,并让它们继承自基适配器类。

为了进一步微调表列适配器的行为,还有一个可选的 String 属性 Modifiers,可以设置为命名参数。这允许传递附加参数,编码到一个字符串中,供适配器使用。

根据装饰中是否出现 Modifiers 参数,表列适配器类必须提供一个遵循以下模板之一的构造函数

//
// Constructor for table column adapter that does not use Modifiers argument in decorating attribute
// NOTE on third argument of base constructor: this argument is used for type checking only. If used,
// type passed should match the type of the column, after mapping. To disable the type checking, pass null.
// 
//
public MyTableAdapter(BFCS.Data.Common.DbValidator validator, string ColumnName)
: base(validator, ColumnName, typeof(String))
{
// ...
}
//
// Constructor for table column adapter that uses Modifiers argument
//
private string m_Modifiers;

public MyTableAdapter(BFCS.Data.Common.DbValidator validator, string ColumnName, string Modifiers)
: base(validator, ColumnName, typeof(String)
{
    m_Modifiers = Modifiers;        // Save for use by adapter
// ..
}

当然,您可以提供两个构造函数。组件根据 Modifiers 参数是否存在来选择使用哪个。Modifiers 参数的一些用法是

  • 定义数值或日期字段的下限和上限
  • 声明下拉框的合法值的文字列表,或
  • 声明一个查找表来为下拉框提供值的规范
  • 定义一个正则表达式,文本输入必须满足该正则表达式

如上所述,单个适配器代码负责解释和使用 Modifiers 值。

在运行时,如果调用页面代码设置了 EditTable 控件的 CallingAssembly 属性(如上所示),控件会搜索页面程序集中带有 CustomTableAdapter 属性装饰的类型,其属性与正在初始化的表和列匹配。如果找到匹配的适配器,组件会搜索一个构造函数来生成适配器实例,该实例附加到列并用于将数据移入和移出数据库存储,以及进行错误检查。

概括自定义表列适配器的使用规则,它们必须

  • 派生自 TableAdapter
  • 适当地实现 ITableAdapter 接口
  • CustomTableAdapter 属性进行装饰,参数指示表和列,以及可选的修饰符
  • 根据属性参数提供适当的构造函数
  • 通过设置 EditTable 控件的 CallingAssembly 属性使其可见

幕后

该项目封装了多年来与 SQL 和 ASP.NET 合作的经验。EditTable 控件的源代码是许多自定义控件编码功能的详尽、近乎痛苦的示例,而 Connection 项目则封装了使 SQL 工作起来轻松无误的丰富经验。

Connection 项目采用分层实现。DataConnection 类简化了建立数据库连接以及处理问题和错误的通常混乱的过程。它允许您多次“打开”而不会报错,并推迟关闭连接(如果需要),以应对嵌套例程。提供了简单的方法来生成数据源无关的 DataReaderCommandDataAdapter 对象。

DbValidator 类检索表的架构,并使用它来验证,并在必要时重新格式化字符串值以用于操作 SQL 语句。SQLStatement 类派生自 DbValidator,它就像一个智能的 SQL 语句 StringBuilder,检查有效性并轻松地重新格式化字符串值以确保它们能够执行,在尝试对数据库提供程序执行 SQL 语句之前捕获许多常见问题(例如过长的字符串、必需列值缺失以及重复键设置)。

EditTable 类展示了控件编码中的一系列概念。ViewState 在内部用于在 postback 之间维护控件的位置和其他状态。最棘手的特性之一是让动态输入的按钮控件的事件正确触发。解决方案归结为在 postback 之后重建控件树,复制此类控件的控件 ID,处理由此检测到的事件,然后根据事件处理程序的动作修改控件树,并在 Page 控件的正确生命周期阶段完成所有这些操作。

附录:EditTable 属性

EditTable 控件外观属性

属性名称描述
TableStyle应用于整个表的样式
TableClass应用于整个表的预定义类
HeadlineStyle应用于表标题的样式
HeadlineClass应用于表标题的预定义类
ItemStyle应用于显示行单元格的样式
ItemClass应用于显示行单元格的预定义类
SelectedStyle应用于选定的编辑行或添加行的样式
SelectedClass应用于选定的编辑行或添加行的预定义类
ErrorStyle应用于格式错误的编辑单元格的样式
ErrorClass应用于格式错误的编辑单元格的预定义类
FooterStyle应用于表页脚的样式
FooterClass应用于表页脚的预定义类

EditTable 控件数据属性

属性名称描述
DataProvider数据连接提供程序的名称(例如,System.Data.SqlClient)
ConnectionString数据源的连接字符串
TableName要修改的表名
TableSQL用于检索表行的 SQL SELECT 语句

EditTable 控件行为属性

属性名称描述
RowsPerPage要显示的表的最大行数
AllowEdit标志:启用行的编辑
AllowDelete标志:启用行的删除
AllowAdd标志:启用新行的添加
AllowFind标志:启用 FIND 功能

© . All rights reserved.