ASP.NET 动态数据系统使用 ASP.NET 的路由功能把请求映射到页面模板。因此,我们可以通过这一特性对数据驱动的网站进行一系列控制。路由可能会非常复杂,这需要一段较长的时间去适应它。
1. 理解路由
路由能够定义动态数据应用支持的 URL 以及在何种情况下使用它们。如下所示:
routes.Add(new DynamicDataRoute("AllRows.aspx")
{
Action = PageAction.List,
ViewName = "List",
Model = DefaultModel
});
路由是客户端调用的 URL 和页面模板间的映射:
- DynamicDataRoute 类的构造函数参数指定应用程序 URL 要映射的客户端 URL(这里是 AllRows.aspx),这表示应用程序所有以 AllRows.aspx 结尾的 URL 都使用这个映射。
- DynamicDataRoute 类的属性告诉 ASP.NET 映射被应用到哪里以及被应用的时机。
- Action:它确定在哪里应用路由,它是 PageAction 枚举的一个值。该枚举的值包括 Details、Edit、Insert、List。把 Action 属性设置成 PageAction.List 将告诉 ASP.NET 动态数据系统可以通过这个 URL 列出表中的所有行。
- ViewName:映射 URL 的页面模板名称。上面的路由我们设置了 List 模板,它将映射 PageTemplates 目录的 List.aspx 模板。如果提供了表特定的自定义模板,它将映射 CustomTemplates 目录里的 List.aspx 模板。
- Model:URL 要应用到的数据模型。对于我们所有的示例,都把它设置成了 DefaultModel。
在 ASP.NET 动态数据应用程序里,路由在 Global.aspx 文件的 RegisterRoutes 方法里定义。调用 RouteCollection 实例的 Add 方法增加新路由。
当 ASP.NET 动态数据系统为应用程序创建基架时,它首先寻找能够和数据模型中每个表的每个 CRUD 活动一起使用的路由。如果找到这样的路由,它就知道能够为那个表启用 CRUD 活动,如果找不到,则意味着这些活动对该表不可用。
下面,我们通过示例来对此进行解释。修改 RegisterRoutes 方法,注释掉开始的路由定义,把示例路由定义复制进去,修改后的方法代码应该和下面的代码类似:
public static void RegisterRoutes(RouteCollection routes)
{
// 重要: 数据模型注册
// 取消对此行的注释,为 ASP.NET Dynamic Data 注册 LINQ to SQL 模型。
// 若要设置 ScaffoldAllTables = true,需符合以下条件,
// 即确定希望数据模型中的所有表都支持基架(即模板)。若要控制各个表的
// 基架,请为表创建分部类并将
// [ScaffoldTable(true)] 特性应用于分部类。
// 注意: 请确保将“YourDataContextType”更改为应用程序的数据上下文类的
// 名称。
DefaultModel.RegisterContext(typeof(NorthwindDataContext),
new ContextConfiguration() { ScaffoldAllTables = true });
routes.Add(new DynamicDataRoute("AllRows.aspx")
{
Action = PageAction.List,
ViewName = "List",
Model = DefaultModel
});
// 下面的语句支持分页模式,在这种模式下,“列表”、“详细”、“插入”
// 和“更新”任务是使用不同页执行的。若要启用此模式,请取消注释下面
// 的 route 定义,并注释掉后面的 combined-page 模式部分中的 route 定义。
//routes.Add(new DynamicDataRoute("{table}/{action}.aspx")
//{
// Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
// Model = DefaultModel
//});
// 下面的语句支持 combined-page 模式,在这种模式下,“列表”、“详细”、“插入”
// 和“更新”任务是使用同一页执行的。若要启用此模式,请取消注释下面
// 的 routes,并注释掉上面的分页模式部分中的 route 定义。
//routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx") {
// Action = PageAction.List,
// ViewName = "ListDetails",
// Model = DefaultModel
//});
//routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx") {
// Action = PageAction.Details,
// ViewName = "ListDetails",
// Model = DefaultModel
//});
}
保存更新,运行程序,将鼠标移动到列表里的表名称处,应该看到这样的 URL:
这是在 Global.aspx 里使用路由映射的 URL。ASP.NET 动态数据系统查找了为 PageAction.List 活动映射 URL 的路由,并找到了我们定义的路由。单击 Customers 表的链接。虽然列表的显示和之前相似,但这里有一个显著的区别,试着单击任一行 “编辑”或“详细”链接,它们不会执行任何动作,这是因为我们还没有定义任何支持访问编辑或查看数据行详细内容的页面模板的路由。
修改 URL 的格式
使用路由可以进行的自定义包括改变 URL 格式。可以看到示例打开的路由指向上述的 URL ,而 ASP.NET 动态数据系统把表名作为了 URL 的参数。现在,修改路由为下面的模样:
routes.Add(new DynamicDataRoute("{table}/{action}/AllRows.aspx")
{
Action = PageAction.List,
ViewName = "List",
Model = DefaultModel
});
再次启动程序,相应表的 URL 就成为了下面这个样子:
构建 URL 时使用了 {table} 和 {action} 标记。一旦 URL 里包含了表的名称,ASP.NET 动态数据系统就不再需要把它们作为 URL 的参数引入。这看起来有点奇怪,这是因为路由在代码里出现的方式给人的映像是我们在定义和某个特定 URL 映射相关的活动。但事实上,路由定义的 URL 可以被应用到一个或多个模板,ASP.NET 动态数据系统查找我们定义的所有路由以确定调用哪一个来执行基架所需的活动。
约束路由
可以给 DynamicDataRoute 类的 Table 属性提供值,以限制路由只被应用到某个特定的表。做如下修改:
routes.Add(new DynamicDataRoute("AllRows.aspx")
{
Action = PageAction.List,
ViewName = "List",
Model = DefaultModel,
Table = "Products"
});
启动程序,现在会发现只有 Products 表被暴露了。这是因为我们创建的唯一路由被限制到了 Products 表。记住,ASP.NET 动态数据系统会为每个表的每个 CRUD 活动查找路由。在这个示例里,它只找到了支持 Products 表的 List 操作的路由。而其他表的所有操作都没有路由可用,所有它们不能够被暴露。
如果要创建同时应用到多个活动或多个表的路由,就要用到 DynamicDataRoute.Constrains 属性。它需要 RouteValueDictionary 类的一个实例。例如,为了让路由同时支持 Products 以及 Orders 表的 List 和 Details 操作,作如下更改即可:
routes.Add(new DynamicDataRoute("{table}/{action}/AllRows.aspx")
{
Model = DefaultModel,
Constraints = new RouteValueDictionary(
new
{
action = "List|Details",
table = "Products|Orders"
})
});
现在,主列表里可以看到 Orders 和 Products 表,并且两个表的 Details 操作也可以工作了。
我们给 RouteValueDictionary 的构造函数传递了一个匿名类型,它包含了 DynamicDataRoute 类型本来需要设置的属性,以“|”作为分隔符可以指定多个值。
两个小技巧使得该路由能够工作:
- 使用这样的约束时,必须在 URL 里使用 {table} 和 {action} 标记,否则 ASP.NET 动态数据系统就不能够正确构造 URL
- 如果省略了 ViewName 属性(它指定要使用的页面模板名称),模板的名称默认为活动名称,List 活动被映射到 List.aspx 模板(这正是我们期望的)
现在,我们再回头看一看一开始被我们注释掉的路由,应该已经具备理解该路由的知识了:
routes.Add(new DynamicDataRoute("{table}/{action}.aspx")
{
Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
Model = DefaultModel
});
- 它应用到所有的表,因为没有 Table 属性,且所有的表都可以进行 CRUD 的操作。
- 因为没有 ViewName 属性的特殊指定,所有的活动被映射到默认的页面模板名称。
- 路由支持的 URL 格式里同时包含了表的名称和活动的名称。
2. 切换到单页编辑
创建好动态数据项目后,Global.aspx 包括 3 个路由,其中两个被注释掉了。这里,我们要修改其中两个默认路由来改变网站的编辑模式。现在,注释掉除了这两个之外的所有路由,保存更新:
routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx")
{
Action = PageAction.List,
ViewName = "ListDetails",
Model = DefaultModel
});
routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx")
{
Action = PageAction.Details,
ViewName = "ListDetails",
Model = DefaultModel
});
第一个路由暴露 ListDetails.aspx 模板,它用于所有表的 List 活动。第二个路由也暴露相同模板,用于所有表的 Details 活动。(额外提醒一下:之所以知道这些路由作用到所有的表,是因为没有给 Tables 和 Constraints 属性设置任何的值)
这些路由看起来可能有点奇怪,毕竟它们指向相同的页面模板却支持不同的活动。其他活动发生了什么?答案都在模板里。ListDetails.aspx 页面模板在同一个页面里处理所有的活动!
现在,编辑会在同一行里进行:
单击某个数据行的“选择”,就可以看见位于表数据行下面的记录详细信息:
两个路由使用同一个页面模板的原因是因为模板被设计为同时处理多个活动。只有 Details 和 List 活动需要路由的原因是其他所有活动由 Ajax 在 ListDetails 页面模板里处理。这是页面模板特性和动态数据路由组合的很好示例。
3. 给表使用不同的模板
ASP.NET 动态数据系统处理路由时,它逐表查找每个页面活动,当找到匹配项时搜索停止。这就是说,第一个支持表活动的路由会被使用。借此,我们可以精密控制要使用的模板。
按下面的代码编辑 Global.aspx 文件的 RegisterRoutes 方法:
public static void RegisterRoutes(RouteCollection routes)
{
DefaultModel.RegisterContext(typeof(NorthwindDataContext),
new ContextConfiguration() { ScaffoldAllTables = true });
// Route 1
routes.Add(new DynamicDataRoute("Products/{action}.aspx")
{
Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
Model = DefaultModel,
Table = "Products"
});
// Route 2
routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx")
{
Action = PageAction.List,
ViewName = "ListDetails",
Model = DefaultModel
});
// Route 3
routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx")
{
Action = PageAction.Details,
ViewName = "ListDetails",
Model = DefaultModel
});
}
为了理解这些路由的效果,我们需要探索 ASP.NET 动态数据系统所使用的发现机制,在数据模型中为每个表查找每个操作。
首先从 Customers 表开始。顺着路由列表,你可以找到匹配 Details、Edit、Insert 和 List 活动的路由吗?路由 1 仅作用到 Products 表,因此它不匹配。路由 2 作用到所有表并支持 List 活动,它匹配。路由 3 作用到所有表并支持 Details 活动,它也匹配。但其中没有 Edit 和 Insert 活动的匹配项。
对 Orders 和 Order_Details 表重复上述过程可以得到相同的结果。
对 Products 表,结果就不同了。仅路由 1 就已经能匹配 Products 表的 4 个活动了(由于 Table 属性和 Constraints 属性)。
我们可以得到下表的结果:
表 |
活 动 |
路 由 |
页面模板 |
Customers Orders Order_Details | List | 2 | ListDetails.aspx |
Customers Orders Order_Details | Details | 3 | ListDetails.aspx |
Customers Orders Order_Details | Edit | No match | |
Customers Orders Order_Details | Insert | No match | |
Products | List | 1 | List.aspx |
Products | Details | 1 | Details.aspx |
Products | Edit | 1 | Edit.aspx |
Products | Insert | 1 | Insert.aspx |
我们运行程序检查结果。如果单击 Products 表,就会看到它使用多页模板进行显示,而其他表则使用单一页面基于 Ajax 的模板。
使用路由来匹配模板和表,这和创建自定义模板是兼容的。路由决定要使用的模板的名称,但它还是会检查 CustomTemplates 目录来确定是否存在自定义的模板。为了演示,我们来定以 Orders 表的模板。
在 CustomTemplates 目录下新建 Orders 目录,复制默认的 List.aspx 页面过去进行编辑,我们额外增加了一段文本消息,如下图:
得到自定义模板后,就可以自定义路由了。我们修改了路由1以支持 Products 和 Orders 表,这包括修改路由映射的 URL 格式,这样 ASP.NET 动态数据系统可以同时在 URL 里包含这两个表名以及活动,并且不和其他路由映射的格式产生冲突:
// Route 1
routes.Add(new DynamicDataRoute("{table}/{action}.aspx")
{
Constraints = new RouteValueDictionary(
new { action = "List|Details|Edit|Insert", table = "Products|Orders" }),
Model = DefaultModel
});
现在运行程序,单击 Customers 或 Order_Details 表,会使用单页的 ListDetails.aspx 模板。如果单击 Products 表,会使用标准的多页 List.aspx 模板。如果单击 Orders 表,就会使用新创建的 List.aspx 定制模板!