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





5.00/5 (4投票s)
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 标签的包装器。这意味着 Border
、CellPadding
和 CellSpacing
等属性都可用。此外,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
类简化了建立数据库连接以及处理问题和错误的通常混乱的过程。它允许您多次“打开”而不会报错,并推迟关闭连接(如果需要),以应对嵌套例程。提供了简单的方法来生成数据源无关的 DataReader
、Command
和 DataAdapter
对象。
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 功能 |