ASP.NET 动态数据系统支持定义元数据类。这些自定义作用于数据模型并允许一些特别细致的控制。
1. 创建元数据类
元数据的 ASP.NET 动态数据系统依赖于部分类。我们要为自定义的数据模型类型创建一个部分类并创建一个要应用于元数据的一个单独的类,然后通过标记把这两个类关联起来。
添加一个新类,类文件的名称并不重要,这里我命名为 Metadata.cs,删除所有内容,使用下面的内容替换:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(Order_DetailsMetadata))]
public partial class Order_Details{
}
public class Order_DetailsMetadata{
}
using 语句引用我们后面要使用的属性归属的命名空间。有趣的部分在于 Order_Details 类。这个部分类扩展了由数据模型生成的 Order_Details 类。我们把 MetadataType 属性添加到部分类表示要给这个数据类型提供元数据,这个属性要的参数是要使用的类的类型(这里是 Order_DetailsMetadata)。
这就是创建元数据类的全部内容。你可以在同一个类文件里定义多个元数据关系,例如,如果要为 Products 表声明元数据,可以创建 Products 部分类:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(Order_DetailsMetadata))]
public partial class Order_Details
{
}
public class Order_DetailsMetadata
{
}
[MetadataType(typeof(ProductsMetadata))]
public partial class Products
{
}
public class ProductsMetadata
{
}
2. 修改显示名称
创建元数据类后,就可以在动态数据应用程序中使用它了。修改 Order_Details 表的显示名称是一个不错的开始。并不只是因为数据库不允许在表名中使用空格,就认为用户一定要面对那些带有下划线的表名。这里要用到 DisplayName 属性:
[DisplayName("Order Details")]
public class Order_DetailsMetadata
{
}
现在运行程序,可以看见这样的效果:
因为我们是在数据模型中执行修改的,因此在所有使用到这个表名的页面模板里它都会起作用,这通常是我们所期望的。我们也可以改变表的某个列的显示名称。为此,需要把和列具有相同名称的属性增加到元数据类。作为示例,我们修改 Products 表里的 UnitsInStock 和 UnitPrice 列:
public class ProductsMetadata
{
[DisplayName("In Stock")]
public object UnitsInStock { get; set; }
[DisplayName("Price")]
public object UnitPrice { get; set; }
}
我们并不关心这些属性的类型,因此我们使用了 object 类型。重要的是属性的名称必须和表的列名相匹配。同样,这些修改会影响所有使用到该列名的地方。
3. 改变可见性
可以通过属性来控制基架模板中表和列的可见性。应用参数为 false 的 ScaffoldColumn 属性可以隐藏列:
public class ProductsMetadata
{
[DisplayName("In Stock")]
public object UnitsInStock { get; set; }
[DisplayName("Price")]
public object UnitPrice { get; set; }
[ScaffoldColumn(false)]
public object SupplierID { get; set; }
}
通过 ScaffoldTable 属性,对表也可以进行同样的控制。下面的代码为 Customers 表定义了一个元数据类并设置属性隐藏整个表:
[MetadataType(typeof(CustomersMetadata))]
public partial class Customers { }
[ScaffoldTable(false)]
public class CustomersMetadata { }
检查主页,已经没有 Customers 表的链接了。
这和使用路由来隐藏具有不同的效果。虽然没有表的链接,但 ASP.NET 动态数据系统仍把表用于外键关系。单击 Orders 表,可以发现仍然可以按客户来过滤数据。
4. 自定义字段格式
观察 Orders 表的记录,可以发现 OrderDate、RequiredDate、ShippedDate 列的时间戳都被设置为午夜,这是因为默认的时间戳格式同时包含了日期和时间:
可以使用 DisplayFormat 来进行格式化,它通过 .NET 标准的格式化字段的显示格式:
[MetadataType(typeof(OrdersMetadata))]
public partial class Orders { }
public class OrdersMetadata
{
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public object OrderDate { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public object RequiredDate { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public object ShippedDate { get; set; }
}
5. 使用自定义的字段模板
使用元数据,可以改变指定数据列的外观。首先需要创建一个新的字段模板。从 FieldTemplates 目录里复制 Text.ascx 文件,粘帖到原始目录,并重命名为 REDText.ascx,然后按下面的代码修改文件的内容:
<%@ Control Language="C#" CodeFile="REDText.ascx.cs" Inherits="TextField" %>
<asp:Label runat="server" ID="Label1" Text="<%# FieldValueString %>"
BackColor="Red" ForeColor="White" />
通过 UIHint 属性告诉 ASP.NET 动态数据系统要使用自定义的字段模板来显示某个列:
public class OrdersMetadata
{
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public object OrderDate { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public object RequiredDate { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public object ShippedDate { get; set; }
[UIHint("REDText")]
public object ShipName { get; set; }
}
自定义验证
可以借助元数据系统自定义数据字段的验证。
1. 要求字段值
Required 属性表示必须要为某个指定的字段提供值。ErrorMessage 属性则为提示信息:
public class ProductsMetadata
{
[DisplayName("In Stock")]
[Required(ErrorMessage="You must enter how many items we have in stock.")]
public object UnitsInStock { get; set; }
[DisplayName("Price")]
public object UnitPrice { get; set; }
[ScaffoldColumn(false)]
public object SupplierID { get; set; }
}
运行程序,选择 Products 表,直接插入新项,可以看到下图:
第一个验证是由 ASP.NET 动态数据系统通过数据模型架构产生:ProductName 列不允许有 null 值。
第二个验证源于我们刚才的元数据 Required 属性。它让我们不需要修改数据模型或底层的数据库就提供架构约束。
2. 指定合法的范围
Range 属性限制数值类型的合法字段范围,见下面代码:
public class ProductsMetadata
{
[DisplayName("In Stock")]
[Required(ErrorMessage="You must enter how many items we have in stock.")]
[Range(0,100)]
public object UnitsInStock { get; set; }
[DisplayName("Price")]
public object UnitPrice { get; set; }
[ScaffoldColumn(false)]
public object SupplierID { get; set; }
}
3. 使用扩展方法自定义验证
展开 App_Code 目录里的 Northwind.dbml 项,找到 Northwind.designer.cs 代码文件。打开该文件就可以看见由 LINQ to SQL 生成的类。它是驱动动态数据应用程序的数据模型。每个类型都有一片被标注为“可扩展性方法定义”的区域。我们将使用这些方法来实现自定义的字段验证。下面是 Products 类型的部分方法:
#region 可扩展性方法定义
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnProductIDChanging(int value);
partial void OnProductIDChanged();
partial void OnProductNameChanging(string value);
partial void OnProductNameChanged();
partial void OnSupplierIDChanging(System.Nullable<int> value);
partial void OnSupplierIDChanged();
partial void OnCategoryIDChanging(System.Nullable<int> value);
partial void OnCategoryIDChanged();
partial void OnQuantityPerUnitChanging(string value);
partial void OnQuantityPerUnitChanged();
partial void OnUnitPriceChanging(System.Nullable<decimal> value);
partial void OnUnitPriceChanged();
partial void OnUnitsInStockChanging(System.Nullable<short> value);
partial void OnUnitsInStockChanged();
partial void OnUnitsOnOrderChanging(System.Nullable<short> value);
partial void OnUnitsOnOrderChanged();
partial void OnReorderLevelChanging(System.Nullable<short> value);
partial void OnReorderLevelChanged();
partial void OnDiscontinuedChanging(bool value);
partial void OnDiscontinuedChanged();
#endregion
我们感兴趣的是那些具有 On<FieldName>Changing 格式的方法。如果在自己的部分类里实现这些方法,用户提供字段时就会调用这些方法。假设我们要继续完善前一个示例的 Range 属性的使用,让 UnitsInStock 字段只能接受偶数值:
public partial class Products
{
partial void OnUnitsInStockChanging(short? value)
{
if (value % 2 == 1)
{
throw new ValidationException("Stock level must be an even number");
}
}
}
现在编辑任一产品,如果给定的数值超出合法的范围,你会看到一个范围验证的错误。如果给定值是奇数,你会看见扩展方法里抛出的异常。(扩展方法中抛出的异常会被用于给用户呈现验证错误)
动态数据最具冲击力的一面是它可以进行定制的方式。页面模板、字段模板、元数据、属性、扩展方法等都可以用于修改动态数据应用程序的外观、功能和验证,这一切只需要很少的编码甚至不需要进行编码!