通用数据库前端 – 筛选、自由文本搜索、排序和分页






4.93/5 (15投票s)
本文描述了一个通用数据库前端Web应用程序的服务器端和客户端代码,并包含相关解释。
引言
本文描述了一个通用数据库前端Web应用程序的服务器端和客户端代码,并包含相关解释。此外,它还为所有数据库表提供了一个标准UI,该UI可以在客户端和服务器应用程序中自定义以生成特定的UI或附加补充。
背景
作为一名开发人员,我的大多数项目都涉及数据库UI实现,其中数据库的模式是系统设计的精髓。对于我提供的所有Web应用程序或移动应用程序,都提供了一个直接的CRUD(创建、读取、更新和删除)UI,用于应用程序背后的数据库。还有其他系统,其中大部分工作是提供重复的CRUD UI,只进行少量定制。有一些工具提供具有特定代码生成和ORM对象的快速开发功能。由于使用闭源工具进行定制可能比开发整个代码花费更多时间,因此我开发了一个开源工具,它提供了更好的解决方案,我在每个项目中都对其进行了改进。当通用UI不足时,这个工具为我节省了开发时间,并且还提供了一种相对简单的方法来定制客户端和服务器端的特殊需求。由于描述整个通用基础设施过于复杂,我将这个工具的描述分成了几篇文章。每篇文章都描述了额外的通用功能。本文中显示的网格几乎没有CSS。欢迎您分享您的CSS建议。
范围
本文讨论了排序、筛选、搜索、分页和CRUD等最基本的表格功能。后续文章将讨论以下通用功能:多选清单、子网格、基于角色的安全性、国际化、日期、富文本、自动完成——避免在大型表格中使用下拉菜单和清单、添加或编辑对话框中字段之间的依赖关系,例如国家和城市、导入和导出、跟踪更改、通过单个对话框更改多行、公式列、连接流行存储(如亚马逊或Azure)的媒体管理、菜单、CRUD驱动的工作流、网格编辑、客户端验证、其他视图设计(如Outlook样式、Google样式和联系人样式)、处理多个模式、其他数据库(如MySQL)的实现等等。每篇文章都将涉及上述一个或多个功能。您的反馈将有助于确定这些文章的顺序。同时,欢迎您在backand.com上查看这些功能的实际运行情况。
问题
基于数据系统的主要挑战是选择在网格中显示的数据,因为我们不一定希望显示数据库表的全部数据,主要是因为用户并不真正需要它。例如,在位类型列中,如果值为0或1,我们希望显示一个勾号,因为它对用户更有意义。在外键列中,如果其数值引用Employees表,我们希望显示员工的全名而不是其数字标识。本文我们将重点介绍数据库中表之间的关系。为了简化,我们将重点介绍关系中的父端。在网格中,我们将显示描述性值而不是外键值,并且在添加和编辑对话框中,我们将显示一个带有相同描述性值的下拉选择元素。默认情况下,系统使用第一个文本列作为其表的描述性语句,尽管这可能并非总是如此。例如,在Employees表中,如果我们要显示员工的全名,我们可以执行以下操作
employeessTableInfo.DescriptiveSelectStatement = "[Employees].[First Name] + ' ' + [Employees].[Last Name]";
在以下部分中,我们将使用关系的子端来显示多选清单以及展开和折叠子网格。
使用代码
这个系统
服务器端架构
该系统采用ASP.NET MVC平台实现。在MVC(Model View Controller)中,控制器生成一个模型并将其分配给一个视图,视图使用该模型创建http响应标记。这个系统也这样做。该系统中的基本控制器称为TableController。它使用TableModelGenerator
生成TableModel。tableController
将TableModel分配给表视图。TableModelGenerator
使用两个主要组件:SqlGenerator
和UiInfo
。SqlGenerator
为控制器动作生成SQL语句。UiInfo
包含并启用了通用用户界面信息的配置,例如列的顺序和标题以及要使用的样式表类。TableModel还包含一个对象,该对象允许根据数据本身进一步定制视图。在我们的示例中,运费背景单元格根据单元格值具有不同的颜色。
UI 信息
UI Info 提供配置 aspx 和 ascx 视图显示方式的能力。根对象是 Schema Info。它包含所有与任何表无关的通用信息。例如,如果我想添加一个 logo 图像并将其放置在多个视图上,Schema Info 将是放置此类项的地方。我在此示例中没有这样做,但欢迎您自行添加。Schema Info 还包含一个 Table Info 对象集合。Table Info 对象包含表的所有通用信息,例如表标题、其页面大小、列顺序等,这意味着数据集中每个 DataTable 都有一个关联的 Table Info 对象。Table Info 还包含一个 Data Info 集合,这里有点棘手。DataInfo
是一个抽象类;它可以是 SimpleDataInfo
、ParentRelationInfo
或 ChildRelationInfo
。SimpleDataInfo
关联到非外键 DataColumn
。ParentRelationInfo
关联到 DataRelation
的父端,ChildRelationInfo
关联到 DataRelation
的子端。ParentRelationInfo
和 ChildRelationInfo
可以根据关系列关联到多个 DataColumn
。在本文中,ChildRelationInfo
不起作用,但在未来的文章中,它将实现多选复选框和子网格。
SQL 语句生成器、安全性和性能
SqlGenerator 负责生成 SQL 语句。所有 SQL 语句都使用 SqlParameters 来防止 SQL 注入。假定乐观锁定,因此所有 FROM 语句都使用 with(nolock)。为了获取整个表的行数,使用 sysindexes 表而不是 count(*),以提高性能。
客户端架构
整个客户端是一个UI示例,因此您可以完全更改它,但必须遵循两个基本规则
- 在所有ajax调用中始终发送过滤、排序和分页参数。
- 使用控制器名称作为参数,因为不同的表使用不同的控制器
用法
要运行演示,只需下载并使用带有 ASP.NET MVC4 的 VS 2010 打开它,如果您没有,可以在此处下载。您还需要在您的机器上运行 SQL Server,并使用默认的 .\sqlexpress 实例。如果您的机器上没有 SQL Server,或者您不使用默认实例,请更改 web.config 中的连接字符串,并将其指向您的 SQL Server 实例,并使用正确的凭据。如果您收到此错误消息:“此版本的 SQL Server 不支持用户实例登录标志。连接将关闭。”请从连接字符串中删除“User Instance=true”。
由于此基础设施是一个开源项目,而不是二进制组件,因此要在您的解决方案中使用它,您需要复制一些文件
- 对于模型和控制器,复制 Generic 文件夹
- 对于视图,复制 Views/Generic 文件夹
- 除了默认的 jquery 脚本 jquery-1.7.1.js 和 jquery-ui-1.8.20.js,还需要复制 Script/Generic 文件夹,它包含
- database.frontend.js
- jquery.ajax.filter.js
- jquery.clearsearch-1.0.3.js
- jquery.highlight.v4.js
- 对于 CSS,除了 jQuery CSS 外,复制 Content/Generic 文件夹
UiInfo
组件的默认配置。您不需要为此级别继承或重写任何内容。对于第三个级别,包括步骤 6-8,您可以根据行的值自定义视图。最后,对于最后一个级别,步骤 9,您可以简单地创建自己的视图或复制一个视图并向其添加更多标记。
要在不进行任何调整的情况下使用标准 UI,请执行以下步骤
- 添加一个将作为数据库模式的数据集。如服务器端架构中所述。在我们的示例中,它是
NorthwindDataSet
。默认情况下,系统使用web.config中带有“ConnectionString
”配置键的连接字符串。如果您使用不同的键,那么您应该在TableController
中重写GetTableModelGenerator()
方法,并将连接字符串作为构造函数参数提供。 - 继承Table Controller并重写
GetDataSet
方法以返回您创建的特定数据集。这个继承的控制器将成为所有表的基本控制器。请参阅下面的代码示例。 - 重写
GetSchemaInfo
并将BaseController
设置为您创建的控制器名称。在我们的示例中,BaseController
是NorthwindController
。public class NorthwindController : TableController // replace NorthwindController with your controller { // add this method to your controller, without changing anything protected override DatabaseFrontend.Generic.UiInfo.SchemaInfo GetSchemaInfo() { DatabaseFrontend.Generic.UiInfo.SchemaInfo schemaInfo = base.GetSchemaInfo(); schemaInfo.BaseController = GetControllerName(); return schemaInfo; } // add this method to your controller and return your dataset instead of NorthwindDataSet protected override DataSet GetDataSet() { return new NorthwindDataSet(); // Replace NorthwindDataSet with your dataset } // in the web.config, if your connection string has a different key then "ConnectionString" then add this method to your controller and replace "your key" with your key protected override DatabaseFrontend.Generic.Models.TableModelGenerator GetTableModelGenerator() { // change the connection string return new DatabaseFrontend.Generic.Models.TableModelGenerator(System.Configuration.ConfigurationManager.ConnectionStrings["your key"].ConnectionString); }
- 在您的路由中添加另一个路由。在 VS 2010 中,默认情况下它位于
RouteConfig
类中。在此新路由中,控制器应该是您创建的新控制器,并且tableName
应该是您希望用户第一次打开网站时看到的第一个表名。public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // add this route routes.MapRoute( name: "Default", url: "{controller}/{action}/{tableName}", defaults: new { controller = "Orders" /* Replace "Orders" with your controller */, action = "Index", tableName = "Orders" /* replace "Orders" with your first time table */ } ); } }
** 建议数据集包含表之间的关系,因为这样系统就知道如何显示有意义的数据而不是主键代码。如果您使用数据集设计器并且数据库中有关系,数据集将自动显示它们,但如果您想从数据库中删除它们,您以后可以仅将它们添加到数据集中。 - 在
GetSchemaInfo
方法中获取要更改的 Table Info 对象并更改其属性 - 继承您之前创建的Base控制器,并以您要自定义的表的名称命名它。在我们的示例中,
OrdersController
继承了NorthwindController
- 重写方法
GetCustomizationHandler
并返回一个CustomizationHandler
的新继承类 - 在此继承类中,重写
GetDataCellCss
方法。此方法接受数据行和 UI 信息对象作为参数,因此您可以操作它们并返回自定义 CSS。在我们的示例中,我们根据值返回红色、黄色或绿色的单元格背景。public class OrdersCustomizationHandler : CustomizationHandler { public override string GetDataCellCss(DataInfo dataInfo, System.Data.DataRow row) { if (dataInfo.Name == ((DatabaseFrontend.Models.NorthwindModels.NorthwindDataSet)dataInfo.TableInfo.SchemaInfo.Schema).Orders.Shipping_FeeColumn.ColumnName) { decimal shippingFee = 0; if (Decimal.TryParse(dataInfo.GetValue(row), out shippingFee)) { if (shippingFee >= 100) { return CustomizationHandler.RedBackground; } else if (shippingFee < 100 && shippingFee >= 10) { return CustomizationHandler.YellowBackground; } else if (shippingFee < 10) { return CustomizationHandler.GreenBackground; } else { return base.GetDataCellCss(dataInfo, row); } } else { return base.GetDataCellCss(dataInfo, row); } } else { return base.GetDataCellCss(dataInfo, row); } } }
- 通过这种方式,您可以重写其他操作并使用自定义逻辑处理它们,以返回一个与标准视图完全不同的自定义索引视图。您可以创建一个视图,并在被重写的
GetIndexViewName
方法中返回它。在我们的示例中,我返回 OrdersIndex.aspx 和 OrdersTable.ascx 视图,因为我想添加一个描述红色、黄色和绿色颜色的图例。我只添加了一些标记。您可以创建一个完全不同的标记并使用模型填充它。protected override string GetIndexViewName(TableModel tableModel) { return "~/Views/Northwind/OrdersIndex.aspx"; }
定制化
Northwind 示例演示了如何在控制器中继承和重写操作。也可以继承和重写模型和 UI 信息基础设施,但设计意图是这些操作是不必要的。模型和 UI 信息旨在通用,以便控制器负责自定义代码。但是,建议根据需要使用附加的通用代码增强模型和 UI 信息。
以下步骤将描述您可以进行的一些自定义。如果您想向 UI 信息对象添加特定的 UI,请执行此额外步骤
如果您想(例如)根据行的数据更改元素的 CSS,就像我在运费示例中所做的那样,那么请执行以下附加步骤