.NET RIA 服务

构建一个数据驱动开支 Silverlight 3 的应用程序

Jonathan Carter

代码下载可从 MSDN 代码库
浏览代码联机

本文基于.NET RIA 服务的预发布版本。如更改,恕所有信息。

本文讨论:

  • 开始使用.NET RIA 服务
  • 数据服务和域操作
  • 代码项目
  • 灵活的数据控件
本文涉及以下技术:
Silverlight 3.NET RIA 服务,WPF

内容

快速入门
数据服务库
域操作
代码项目
数据控件
ObjectDataSource
DataPager
DataForm
元数据
验证
共享的代码
总结

在软件开发 有是的每个带有自己目的和要求、 优点和缺点的应用程序的很多不同样式。选择一个开发平台或 Framework 时, 您的决策过程的一部分最有可能将依赖是否在 Framework 可以轻松地启用您要查找来构建的应用程序的类型。没有开发人员想要花费时间编写管道或基础结构代码,并且没有客户想要进行支付该类型的工作。能够要主要关注业务需求是非常重要,因此具有一个开发平台,它更好地使该焦点并提高工作效率是绝对必须的。

Silverlight 是一种很好的技术用于创建引人注目的 Web 应用程序。Silverlight 2.0 包括 Windows Presentation Foundation (WPF) 的子集,不继承功能进行更加轻松地创建数据驱动的应用程序的 WPF 中的大量。这导致许多开发人员将最终传上使用 Silverlight,因为它需要过多实现更复杂的 Web 解决方案的以数据为中心基础结构的工作。作为 Silverlight 3 发布,新控件和功能是被引入了特别是使数据驱动的应用程序更易于开发。这包括新的数据控件、 导航、 验证和嵌入式的对话框 Windows。

而这些客户端的增强功能提供其自己,Silverlight 的大量值是一个 multitiered 环境,并因此要求如何处理客户机和服务器之间通讯的知识。提供要解决此问题,.NET RIA 服务 (代号为"Alexandria") 了一套服务器组件和轻松地完成该 n 物理) 层开发过程的 ASP.NET 扩展使您的应用程序几乎一样容易开发它们像单个的层上运行的一样。此外,还提供了身份验证、 角色和配置文件管理等服务。客户端和服务器增强 Silverlight 3 和 ASP.NET 和.NET RIA 服务,添加的组合简化的开发数据驱动的 Web 应用程序端到端体验也称为 Rich Internet Application 或 RIA。

fig01.gif

图 1 默认.NET RIA Services 项目

此文章中, 我将介绍如何对 Silverlight 3 协同使用由.NET RIA 服务,引入的功能增强功能提供丰富的环境为开发以数据为中心 Web 应用程序。为了最说明 Silverlight 3 和.NET RIA 服务提供的功能,我将建立费用报表跟踪将利用新的功能集的应用程序。应用程序将允许登录和管理并创建其个人零用金报销单的员工。完成费用报表后, 它然后提交,等待一个经理的审批。

快速入门

一旦您已安装.NET RIA 服务和 Silverlight 3,Visual Studio 会包括调用 Silverlight 数据应用程序,以获取最新的项目模板和快速运行。在创建您的解决方案时系统要显示两个项目: Silverlight 项目和 ASP.NET 项目 (请参见 图 1 )。Silverlight 项目代表客户端应用程序,并且 ASP.NET 项目将包含您的后端逻辑。

此默认项目结构的优点是什么?玩为提供的 Silverlight 项目是,它等同于您会得到什么如果您创建常规的 Silverlight 项目。有标准的 App.xaml 文件和默认页。有,但是,此 Silverlight 项目将帮助您开发的目标的细微差异。

ASP.NET 项目也,是简单,但如果您查看 Default.aspx 页中,您会发现它使新的服务器控件的使用名为 SilverlightApplication。

<ria:SilverlightApplication 
  ID="Xaml1" runat="server" 
  Source="~/ClientBin/ExpenseReports.xap" 
  MinimumVersion="1.0" 
  Width="100%" Height="100%" />

此控件旨在使承载一个 Silverlight 应用程序更容易 ASP.NET WebForms 应用程序中的操作。 针对在 XAP 将 Silverlight 项目中创建已配置 SilverlightApplication 控件。

到目前为止,您有一个完全功能和托管的 Silverlight 应用程序。 现在只需要开始添加自定义业务逻辑和 UI。

让我们假定已经了数据库位置为此应用程序,因此为接下来是将一个 ADO.NET 实体数据模型添加到服务器项目。 我将使用实体框架作为对象关系映射 (O / RM),本文,但也可以使用 LINQ to SQL、 NHibernate、 传统 ADO.NET 或任何其他数据访问方法。

数据模型是非常简单,包含只有三个实体: ExpenseReport,ExpenseReportDetail,和员工 (参见 图 2 )。 有关详细信息, ADO.NET 实体框架 在 MSDN。

fig02.gif

图 2 数据模式

与在位置和 O / RM 选定内容进行数据模型,您现在可以开始实现后端逻辑。 由于此项目使用 Silverlight 为客户端和作为主机的 ASP.NET,您需要决定两层之间的通信的方法。 可以使用任何在当前可用的通信框架,如 Windows Communication Foundation (WCF) 或 ADO.NET 数据服务,根据您的要求。

数据服务库

.NET RIA 服务引入了构建数据驱动 RIA 更容易的服务器组件的库。 库不是依靠任何特定的 UI 框架,而本文着重于其消耗量的 Silverlight 的是其可能的客户端选项之一。 在将来.NET RIA 服务的版本将还使用 ADO.NET 数据服务。

.NET RIA 服务主要是围绕一个名为 DomainService 充当业务逻辑和数据模型交互的一个服务器终结点的新类。 创建一个就像利用业务逻辑类模板安装在 Visual Studio.NET RIA 服务一样简单。

两个窗体中有一个 DomainService: 数据 model–specific 或泛型。 数据 model–specific 实现有效地包装数据模型允许您编写的业务逻辑和数据访问代码组合的选项。 这使得更加方便地获取设置和运行快速地在您已经有想要使用一个数据模型的情况下。

有是两个数据 model–specific DomainService 实现提供.NET RIA 服务: ADO.NET 实体框架和 LINQ to SQL。 如果您的应用程序使用的那些,,然后创建一个 DomainService 时可以向导中选择适当的选项。 如果您使用其他类型的数据模型或 O / RM,您可以创建自己的特定实现也的 DomainService。

本文中,因为我已经有一个实体数据模型就地,我将继续并创建实体 Framework–specific DomainService。 我收到 LinqToEntitiesDomainService <t> 所继承的一个新类:

[EnableClientAccess]
public class ExpenseService : 
  LinqToEntitiesDomainService<ObjectContext> {

  //public IEnumerable<Employee> GetEmployees()
  //{
  //    return this.Context.Employee;
  //}
}

泛型参数在这种情况下表示 ObjectContext 实例代表连接到实体数据模型的类型。 第一步是 heed TODO 注释的建议,并用实际的 ObjectContext,在此例 ExpenseReportContext 中替换类型参数占位符。

除了继承 DomainService (或派生) 域服务可以有附加的 EnableClientAccessAttribute。 此属性是实际上表示您的类为域服务,允许您指定是否它应公开提供。 通过公开公开,我意味着客户端应用程序的访问。 这允许您选择来确定是否仅在的服务器上需要某个逻辑,或如果它还应在客户端上可用。

域操作

域服务不自己非常有用除非将某些功能添加到它的域的操作。 域操作代表您的域服务的终结点,来执行创建、 读取、 更新,和删除对您的数据模型、 您的任意的业务逻辑或两者的 (CRUD) 操作。 每个域操作必须映射到特定的操作类型包括查询、 插入、 更新、 删除、 服务操作和自定义。 按约定或配置,可能出现操作类型映射。

根据您的域操作要执行的操作类型,它必须遵循特定的签名。 此外,某些操作必须遵循特定的命名约定,或者具有确定的操作是类型的属性。 对于是实例如果然后它的返回类型必须为 IEnumerable <T> 或者 IQueryable <T> 其中 T 是实体类型是一个查询操作它适用于。 它可以接受任意数量的参数可用作筛选器,但不是必需的。

创建用于检索所有支出报告域操作可能看起来这类似于:

public IEnumerable<ExpenseReport> 
  GetExpenseReports() {

  return Context.ExpenseReports;
}

ExpenseReport 是基础数据模型中的一个实体,因此,是有效的返回类型。 DomainService 类包含一个名为可以访问您的数据模型 (类型作为泛型参数提供),在这种情况下可以查询有关的所有支出报告列表的实例的上下文属性。 按照惯例,此操作映射到类型。

创建数据方法来检索特定费用报表可能看起来这类似于:

[Query(IsComposable = false)]
public IEnumerable<ExpenseReport> 
  GetExpenseReport(int id) {

  var expenseReport = 
    from rep in Context.ExpenseReports
    where rep.Id == id
    select rep;

  return expenseReport;
}

请注意,此数据方法采用以检索特定费用报表的 ID。 参数 因为您具有访问基础 ObjectContext,还可以编写使用 LINQ 查询。 此方法将映射配置,使用该 QueryAttribute 它还允许您指定不能将达到 conventionally 的其他属性。 我将介绍在 QueryAttribute 在 IsComposable 属性本文后面的作用。

根据需要许多查询方法可包含一个 DomainService。 除了可以检索数据,您可以创建数据模型回保持数据的操作。 为支出报告实现基本的持久性操作 (插入/更新 / 删除) 可能看起来这类似于:

public void InsertExpenseReport(
  ExpenseReport expenseReport) {
  Context.AddToExpenseReports(expenseReport);
}

public void UpdateExpenseReport(
  ExpenseReport current, ExpenseReport original) {
  Context.AttachAsModified(current, original);
}

public void DeleteExpenseReport(
  ExpenseReport expenseReport) {
  Context.DeleteObject(expenseReport);
}

您可以在相同大致定义持久性操作的费用报表详细信息。 您可以只有一个插入、 更新,和删除每个实体类型 (ExpenseReport 在这种情况下) 的方法,因为请求中而言将更改保存回数据模型 DomainService,在该 DomainService 需要时知道要调用的方法。

注意有关这些操作是它们的签名。 创建插入或删除方法时, 它必须符合的单个参数执行方法负责插入或删除的实体类型。 当您创建的 Update 方法时, 必须采取两个参数: 该修改或当前的实体实例都并且原始的实体实例。 结合方法名称,方法签名是内容表示为哪种方法负责的实体类型的操作的 DomainService 的。

当定义持久性操作是为前缀的操作类型在方法名称时可以使用命名约定。 对于是实例因为该方法调用 DeleteExpenseReport,删除前缀表示的是按照约定的删除操作。 签名然后定义实体类型与 (ExpenseReport) 关联。 您会希望传统的方法名称前缀更新操作是更新和插入操作是插入。 这是原因不需要执行这些操作工作的任何其他配置。

如果操作不会按照预期的命名约定,或者您需要指定其他元数据为操作 (如我那样使用 GetExpenseReport 方法),然后您可以应用配置属性,如 QueryAttribute、 InsertAttribute、 DeleteAttribute,和 UpdateAttribute 我那样使用 GetExpenseReport 方法。

如果您需要定义不一定依赖于一个的 CRUD 操作,但与实体类型相关联的业务逻辑,您可以创建服务操作。 在这种情况下,我需要批准和拒绝零用金报销单的操作。 这只是意味着修改费用报表的状态值 (请参见 图 3 )。 为服务操作的签名必须接受实体类型,它与相关联并且必须返回 void 的实例。 请注意没有定义服务操作,因此必须应用配置任何适当的域操作 ServiceOperationAttribute 没有约定。

图 3 服务操作

[ServiceOperation]
public void ApproveExpenseReport(
  ExpenseReport expenseReport) {

  if (expenseReport.Status == 1) {
    if (expenseReport.EntityState == 
      System.Data.EntityState.Detached) {
      Context.Attach(expenseReport);
    }
    expenseReport.Status = 2;
  }
}

[ServiceOperation]
public void RejectExpenseReport(ExpenseReport er) {
  if (er.Status == 1) {
    if (er.EntityState == 
      System.Data.EntityState.Detached) {
       Context.Attach(er);
    }
    er.Status = 0;
  }
}

既然您已获得域服务和操作创建,如何执行您着手 ASP.NET 服务器主机到 Silverlight 客户端应用程序中与服务交互? 如果使用 WCF 或 ADO.NET 数据服务,将指向该服务会生成代理的 Silverlight 项目中创建引用。 .NET RIA 服务提供了稍有更丰富的体验。

代码项目

当您创建.NET RIA 服务解决方案时,Silverlight 项目文件会有一些特殊 MSBuild 任务添加到处理客户端的 projecting 特定的服务器组件的。 应用于一个 EnableClientAccessAttribute 的 ASP.NET 项目中创建域服务时, MSBuild 任务将项目 Silverlight 应用程序的域服务。 (如果您没有创建解决方案使用新的 Silverlight 的数据应用程序项目模板,您可以手动链接一个 Silverlight 项目和 ASP.NET 项目在 Visual Studio 以获得相同的效果)。

如果您查看 Silverlight 项目中 main.xaml 文件的代码隐藏中,您会发现 ExpenseContext 和 ExpenseReport 类型。 数据提供程序预计到 Silverlight 客户端应用程序,时不再 DomainService (或子类型),但而不是一个 DomainContext。 实际上,如果您的域服务后缀与 Word 服务,它将被替换与在项目 (这是为什么 ExpenseService 类将反映为 Silverlight 项目中的支出上下文) 的上下文。 DomainContext 类都可以充当一个 DomainService 一个客户端代理,并且包含逻辑所必需的通信两层之间的请求。 它表示的工作单元,并可以建立对当前正在跟踪的任何实体实例所做的更改集的一系列。

此外,将某些方法 (公用方法为域操作来约定或配置的) 预计与其父域服务。 在六个数据方法类型的唯一的三个计划: 自定义,服务,和查询。 如果查询方法的名称作为前缀获取,项目时获取将被替换与负载。 是例如因为我定义同名 GetExpenseReports 的 Query 方法,它将显露在客户端上作为 LoadExpenseReports。 两个服务操作也已计划的并 DomainContext 上反映为实例方法。

最后,域操作中返回的任何实体类型也将被计划。 <expensereport>在我的示例因为 GetExpense 报告方法的返回类型是 IEnumerable < ExpenseReport >,ExpenseReport 实体类将被计划到 Silverlight 客户端。 计划的实体类是从特殊类调用提供了如更改跟踪、 验证检查和 WPF/E SL 兼容 editability 的行为的实体的继承。 从 API 角度而言您的客户端的实体类在的服务器上都具有外观像该实体,但将包含所需的与 Silverlight 数据控件进行交互的其他功能。

有关于代码投影行为与此处讨论的更多详细信息。 本文未介绍可能包括自定义用于确定如何将特定类型的代码形之前它投影到客户端应用程序逻辑的许多其他方案。

我因为我认为它适当地描述将在使用术语代码项目。 ExpenseService 类不只是被复制从一个项目到另 (没有实际的例外,我将稍后讨论)。 它将被检查,定形,并提取到为易于使用的代理客户端应用程序。 因此,您可以利用从客户端层类型,就好像这是本地对象。

在隐藏计划的域服务? 从其创建的状态中 Silverlight 项目查找不变。 如果您打开相应的选项显示为 Silverlight 项目的所有文件,您会看到该原因 (请参见 图 4 )。 包含所有已被从伙伴服务器项目计划的代码的调用的 Generated_Code 会创建一个文件夹。 目前尚单个文件中,ExpenseReportsServer.g.cs (g 代表生成),其包含整个 ExpenseReportsServer 项目代码,并且为 ExpenseContext 类的主页。

fig04.gif

图 4 为 The Silverlight 项目生成的代码

如果您创建一个新的数据提供程序,或修改现有,所做的更改将自动更新保持不断同步客户端和服务器在 Silverlight 项目中。 在为支持应用程序的客户端的目的创建服务的方案需要不断地刷新开发过程中的服务代理可以是一个获得认可。 除了在投影,重新整形此细微问题证明是一项非常有用的功能。

数据控件

因此我已获得数据库、 数据模型、 服务,和客户端代理位置。 现在我需要建立用于显示和编辑 Silverlight 客户端中的费用报表数据用户界面。 处理数据驱动的应用程序时有通常两种类型的显示数据的方法: 表格格式和基于表单的。 处理数据驱动的 RIA 时表格和基于窗体的数据需要以使用户体验高度信息和高效的方式显示。

Silverlight 2 现成的没有非常强支持表格数据演示文稿。 甚至 ListView 控件提供基本的网格在 WPF 中的演示文稿) 是位于其使有点难构建商业软件的 Silverlight。 Silverlight 以后接收明确帮助,DataGrid 控件但仍然缺少大量所需的功能。 内部控件作为 TextBox、 ComboBox、 按钮、 列表框和进行开发的窗体可能的 RadioButton 提供但存在不强支持所需数据验证和错误报告的其他行为。

Silverlight 3 引入了一组专门针对使创建以数据为中心的 RIA 更轻松的新控件。 这些控件包括 DataGrid、 DataForm、 DataPager、 FieldLabel、 DescriptionViewer、 ErrorSummary 和 ChildWindow。 我的时间的 DataGrid、 DataForm 和 DataPager 可以表格形式和我的示例应用程序的基于窗体的样式中的数据表示。 FieldLabel、 DescriptionViewer,和 ErrorSummary 提供了动态用户界面、 数据验证和错误报告所需开发响应数据输入。 最后,ChildWindow 使格式模式对话框。

DataGrid 控件附带 Silverlight 3 是可靠的。 此外,它包括 reorderable 和可调整大小列、 行分组、 内联编辑,和验证。 它允许您定义两种方式在网格中显示的列: 生成明确。

DataGrid 具有它的 AutoGenerateColumns 属性设为 True 将自动创建的每个公共属性列在它所绑定到类型上,如果一个属性,指定它 BindableAttribute 附加到它正在,该异常不是可绑定,然后 DataGrid 不会为其创建列。 这是如何新的数据控件利用实体的元数据的一个示例。

在 DataGrid 中明确定义列时, 您将范围显示的数据只是想要显示列。 这使您控制的列类型使用,以及标题文本和许多其他列属性。 显式定义列显示支出报告数据的 DataGrid 可能类似于 图 5

图 5 DataGrid 开支报告

<dataGrid:DataGrid
  x:Name="ExpenseReportDataGrid"
  AutoGenerateColumns="False">
  <dataGrid:DataGrid.Columns>
    <dataGrid:DataGridTextColumn 
      Binding="{Binding Company}" 
      Header="Company" />
    <dataGrid:DataGridTextColumn 
      Binding="{Binding Department}" 
      Header="Department" />
    <dataGrid:DataGridTextColumn 
      Binding="{Binding Description}" 
      Header="Description" />
    <dataGrid:DataGridCheckBoxColumn 
      Binding="{Binding Status}" 
      Header="Approved" />
  </dataGrid:DataGrid.Columns>
</dataGrid:DataGrid>

列绑定中指定的属性引用 ExpenseReport 实体上的属性。 请记住类已计划到 Silverlight 应用程序使其在客户端中可用。 DataGridTextColumn 类为只读模式中的文本和在编辑模式下的文本框,将显示数据。 在 DataGridCheckBoxColumn 将为在所有模式下的复选框显示该字段,并提供三种状态。

若要填充 DataGrid 只需将其 ItemSource 属性设置任何 IEnumerable 对象中。 请记住我定义费用的服务和 GetExpenseReports 数据方法时, 它的返回类型是 IEnumerable <expensereport>。 现在我只需要弄清如何使用数据提供程序生成的客户端代理。

ExpenseService 类已计划到客户端应用程序时, 其 GetExpenseReports 方法时也预计 (后被重命名为 LoadExpenseReports)。 这意味着您应该能够只需创建 ExpenseService 的实例并调用其 LoadExpenseReports 方法。 过程执行正确的方法的它不会产生自己的任何结果。 有趣的是,LoadExpenseReports 方法不会返回任何内容。 如何获取费用报表数据?

Silverlight 要求以便不会锁定在 UI 线程异步执行任何阻塞调用。 因此,.NET RIA 服务项目到 Silverlight 客户端在域服务时它 refactors 所有其查询操作,以便它们不返回任何内容。 这说明为什么.NET RIA 服务将重命名您的查询方法在加载而不是获取 (或提取、 查找、 查询、 检索或选择),带有前缀,因为其更适当地反映它的行为。

而不是使用一个回调方法,由.NET RIA 服务生成的类使用异步调用完成后,请通知您的事件模型。 因此,响应查询操作的加载,您只需订阅 DomainContext 的加载事件,将会触发一旦任何查询操作完成的。 因为事件处理程序在 UI 线程上调用的可执行其中任何数据绑定。

图 6 开支报告数据加载

public partial class Main : Page {
  ExpenseContext _dataContext;

  public Main() {
    InitializeComponent();

    this.Loaded += Main_Loaded;

    _dataContext = new ExpenseContext();
    _dataContext.Loaded += dataContext_Loaded;
  }

  void dataContext_Loaded(object sender, LoadedDataEventArgs e) {
    ExpenseReportDataGrid.ItemsSource = e.LoadedEntities;
  }

  void Main_Loaded(object sender, RoutedEventArgs e)  {
    _dataContext.LoadExpenseReports();
  }
}

fig07.gif

图 7 开支报告网格 UI

一旦在触发加载的事件处理程序有两种方法,可以检索请求的数据: 通过将 LoadedDataEventArgs.LoadedEntities 属性或通过在域上下文 (如 ExpenseContext) 上的强类型化的实体属性之一。 通过事件参数访问数据是首选的方法,因为这将确保得到所需的数据。 每次查询操作被调用的返回的实体实例将添加到在域上下文因此只要您访问它的内容将获取每个的查询而不仅仅是一个您最近执行的累积。

实现用于检索所有支出报告,以及使用返回的数据填充 DataGrid 逻辑可能类似于 图 6 。 到目前为止我们数据驱动的 RIA 类似 图 7

除了能够加载数据,DomainContext 类 (和生成的子类型) 包含包含管理更改跟踪和数据持久性的方法。 跟踪您通过一个 DomainContext 做被检索 (或添加或删除) 的实体的每个更改。 如果要保存更改,可以只是调用 SubmitChanges 方法:

private void SaveChangesButton_Click(
  object sender, RoutedEventArgs e) {
  if (_dataContext.HasChanges) {
    _dataContext.SubmitChanges();
  }
}

因为 DataGrid 使嵌入式默认,编辑可以修改任何费用报表记录并单击保存更改按钮以它们保存到服务器。 调用 SubmitChanges 方法时,将 DomainContext 将程序集为其跟踪实体的所有更改,并将其发送到相应的 DomainService。 然后,在 DomainService decomposes 更改集,并调用相应的域操作对于已插入、 更新,或删除的每个实体。

您编辑 DataGrid 控件中的数据它提供验证和自动报告的错误。 如果您输入了无效的数据您会视觉通知进行的错误的说明 (请参见 图 8 )。 此功能提供任何配置或工作您。 正如您稍后将文章中看到,还可以在数据模型,将提取并确保通过 DataGrid 定义自定义验证规则。

fig08.gif

图 8 DataGrid 验证

此视图支出报告的正常,但将得更有用,以查看按状态分组的费用。 这样我可以立即找出该报表是等待由经理进行审批。 幸运的是,这可以实现轻松地在 Silverlight 3 中的将此代码追加到现有的 DataGrid 定义:

<dataGrid:DataGrid.GroupDescriptions>
  <dataGrid:PropertyGroupDescription  
    PropertyName="Status" />
</dataGrid:DataGrid.GroupDescriptions>

结果如下所示 图 9 .

fig09.gif

图 9 DataGrid 分组

ObjectDataSource

有些开发人员将希望强制性数据绑定到目前为止使用方法时, 其他人可能喜欢能够执行数据绑定仅仅是声明方式更可能使用 WPF 中 ObjectDataProvider 的方式类似。 为此,Silverlight 3 引入了 ObjectDataSource 控件。

ObjectDataSource 是知道如何特别是使用 DomainContext 类型派生自非可视控件。 可以被认为的以前的强制性方法未提供的所有功能但方法完全声明 (加号)。 您只需告诉它 DomainContext 类型正在使用和的所需调用其查询操作数。 ObjectDataSource 将处理其余部分。

您可以从前一节中删除所有代码并替换 ObjectDataSource,并它将自动调用指定的加载方法:

<ria:ObjectDataSource
  x:Name="ExpenseReportsObjectDataSource"
  DataContextType="ExpenseReports.ExpenseContext"
  LoadMethodName="LoadExpenseReports"
  PageSize="20">
  <ria:ObjectDataSource.Filter>
    <data:FilterDescriptor Member="Department" 
      Operator="IsEqualTo" Value="IT" />
  </ria:ObjectDataSource.Filter>
  <ria:ObjectDataSource.Sort>
    <data:SortDescriptor Member="Status" 
      Direction="Descending" />
  </ria:ObjectDataSource.Sort>
</ria:ObjectDataSource>

使用 ObjectDataSource 控件只是不提供数据绑定的声明性方法,它还使查询可组合。 可以添加排序、 组和筛选器表达式 ObjectDataSource,以及将应用到对其查询操作将调用的页面大小。 最佳的部分是的 ObjectDataSource 将修改对域服务,这样,任何指定的排序或筛选应用在服务器端的请求这意味着任何不必要的数据将被传递通过缆线。

还记得在 DomainOperationAttribute 在 IsComposable 属性吗? 这就是确定域操作是否允许要传递给它,使其返回的数据可组合的其他查询参数。 我 GetExpenseReports 方法没有添加任何排序或筛选代码,但因该可组合性,DomainService 和 ObjectDataSource 知道如何进行这一事实的构成查询),我可以自动得到该功能。

ObjectDataSource 是实际 DomainContext 实例的包装,,因此受益同样的更改跟踪问题。 它包含相同的负载和 DataContext 执行允许您以编程方式控制像一个 DomainContext SubmitChanges 方法。

DataPager

由于当前显示在网格中的数据量是有点不实用的它将可能意义页它用于演示文稿。 而在 DataGrid 控件本身不包含分页行为,Silverlight 3 引入了一个新的 DataPager 控件,与其他数据控件轻松地提供与它们的分页功能的。

DataPager 控件只是通过提供的数据源中提供用于分页所需的用户界面。 如果您将一个 DataPager 绑定到同一数据源为其他的数据控件 (如 DataGrid),将 DataPager 数据分页将还页其他绑定的控件中显示的数据。 代码以零用金报销单的列表中添加一个 DataPager 可能如下所示:

<data:DataPager
  Source="{Binding Mode=TwoWay, Source={StaticResource ExpenseReportDataSource}, Path=Data}"/>

fig10.gif

图 10 DataPager 位于 A DataGrid Of </a0>-底部

请注意我所要做的就是定义该控件,并将它绑定到正确的源控件将处理其余部分 (请参阅 图 10 ).

在 DataPager 有您可以从选择不同方式向用户显示可用的页的多个模式。 此外,您可以完全 re-skin 查看但是想同时仍能保持其现有的功能 DataPager。

虽然 DataGrid 和 DataPager 提供编辑体验的强大内联,如果想要基于窗体的布局中显示数据? 用于创建或修改零用金报销单,您希望用户提供使用而不必依赖于在网格的直观窗体。 为此您可以使用新的 DataForm 控件。

DataForm

DataForm 控件允许您定义一组字段将显示在一个基于窗体的布局中,并可以绑定到一个单个实体实例或集合。 只读的中允许使用您的数据、 插入,和能够自定义的每个外观中编辑模式,。 可以选择显示和之间切换模式,时绑定到集合,在 DataForm 还可以显示用于导航的页导航的控件。 就像在的 DataGrid DataForm 也有各种形式: 生成、 显式,和模板。

生成模式的工作方式就与 DataGrid 类似。 将它绑定到类型上创建一个字段和标签对每个公共属性。 DataForm 考虑在 BindableAttribute 也,它允许您定义在实体级别可绑定的字段列表。 定义的零用金报销单的编辑窗体可能是像这样简单:

<dataControls:DataForm
  x:Name="ExpenseReportDataForm" Header="Expense Report"
  ItemsSource="{Binding Source={StaticResource ExpenseReportsObjectDataSource}, Path=Data}" />

通过生成的模式您要允许将根据实体的元数据的所有用户界面假设 DataForm。 在生成的窗体如 图 11 所示。

fig11.gif

图 11 DataForm 派生支出项窗体

加标签的任何必需的字段 (属性标记与所需的属性) 中显示粗,指示用户要求。 此外,输入控件右侧信息标志符号描述鼠标悬停工具提示的预期的输入。 说明是可选的是否将字段的相应属性具有附加一个 DescriptionAttribute 将通过检查来检索。 这些就是的新的 Silverlight 3 数据控件如何启用数据驱动方案,通过将添加到 UI 以响应您的数据模型的元数据的两个更多示例。

就像在的 DataGrid,DataForm 还提供数据验证和错误报告。 两个控件都具有一致的外观和提供了总体良好的用户体验,无论的数据表示您需要的功能。

使用显式窗体,您可以声明要的字段显示,什么类型的字段使用,和显示 (在其他内容中) 的标签文本内容。 此窗体时不想要保留用户界面创建到 DataForm 和实体的元数据。 显式声明一个 DataForm 可能类似于 图 12

图 12 Of A DataForm 的显式创建

<dataControls:DataForm
  x:Name="ExpenseReportDataForm"
  ItemsSource="{Binding Source={StaticResource ExpenseReportsObjectDataSource}, Path=Data}"
  AutoGenerateFields="False">
  <dataControls:DataForm.Fields>
    <dataControls:DataFormTextField 
      Binding="{Binding Company}" Label="Company" />
    <dataControls:DataFormTextField 
      Binding="{Binding Department}" Label="Department" />                
    <dataControls:DataFormTextField 
      Binding="{Binding Description}" Label="Description" />
    <dataControls:DataFormCheckBoxField 
      Binding="{Binding Status}" Label="Approved" />
  </dataControls:DataForm.Fields>
</dataControls:DataForm>

除了文本和复选框的字段,有字段可用于日期、 组合框、 模板、 分隔符、 标题和字段组。 使用这些可以显式定义要显示,并显示它们在 DataForm 使基本指令的字段。 即使有此大的灵活性您仍限制到一个上的每个字段是一个传统表单,标签和输入控制对。 而在 DataFormTemplateField 允许您定义所有模式 (显示和编辑) 的模板,它限于字段级。 如果您想模板整个窗体?

完全控制您的用户界面是需要 (或需要),将 DataForm 将允许您为每个其模式 (显示、 插入和编辑) 定义自定义数据模板。 与这一功能可以跳出默认自上而下的窗体样式,并创建任何外观符合您的具体情况。

特定的行为是 DataForm 的全局,如导航、 验证,和错误报告的所有三种形式。 当您选择重新定义数据模板,但您丢失自动字段标签和说明的处理模型的元数据的查看器。 在开发数据驱动应用程序时将丢失此有用的行为,只是因为您需要自定义版式的这。 幸运的是,的控件的内部使用,dataform ; 提供此行为也是可用手动。

元数据

如果允许为您生成的字段列表 DataForm,它自动将使用两个控件提供该标签和说明的问题: FieldLabel 和 DescriptionViewer。 这些控件都易于使用和可以任何包括自定义 DataForm 模板下的数据绑定方案中使用。

如果您要显示由其关联的绑定属性的元数据的控件的标签,FieldLabel 很有用。 用于该标签的文本是从附加到绑定到的属性的 DisplayAttribute Name 属性派生的。 此外,如果该属性需要 (表示通过标记为 True 附加一个 RequiredAttribute),字段标签文本将以粗体。

除了作为可以指定自定义的名称与在 DisplayAttribute,您可以指定属性的说明。 如果您想显示字段的说明,可以使用 DescriptionViewer 控件自动将此处理的。 它将显示一个信息标志符号,提供工具提示包含与之关联的属性的说明。

与 FieldLabel 和 DescriptionViewer 的控件可以开发利用您的数据模型的元数据,而无需复制信息 (如字段名称和说明) 的自定义数据表单。 如果整个应用程序中使用这些控件,到属性的名称、 说明或所需的状态 (在模型级别) 进行更改时的随时 UI 自动反映其依赖于数据模型由于更改。 这是行为的您所料开发数据驱动的应用程序时的类型。

验证

因为我们要重点数据驱动应用程序的开发,我们想使我们的业务逻辑和数据模型接近的验证。 使用.NET RIA 服务时可以表达验证逻辑,以两种方式: 数据批注和共享自定义 / 逻辑。

Microsoft.NET Framework 3.5 SP 1 发行引入了一组称为为了将元数据和验证规则附加到数据模型的数据批注的属性。 这些注释最初由 ASP.NET 动态数据和使用理解,忽略.NET RIA 服务和新的 Silverlight 3 数据控件。 与它们您可以表达为字符串长度、 范围、 数据类型和正则表达式约束类验证方面:

[Bindable(true, BindingDirection.TwoWay)]
[Display(Name = "Expense Amount", 
  Description = "The amount of the incurred expense.")]
[Range(0.0, 1000000.00)]
public object Amount;

[Bindable(true, BindingDirection.TwoWay)]
[Display(Name = "Category", 
  Description = "The category of expense, i.e., mileage.")]
[StringLength(10)]
public object Category;

.NET RIA 服务计划在处理运行时它将使流动到客户端的任何服务器端数据批注的一个顶点。 只要您利用这些泛型的数据批注来表示您的服务器端数据模型的验证规则的验证将在通过执行到 Silverlight 应用程序,并完全可用的了解控件 (DataForm、 DataGrid、 FieldLabel,等)。 这有效地使您客户端和服务器验证。

使用数据批注简单,但是它们不能 Express 每个可能的验证要求。 实际上,它们确实可以表示最常见的方案。 对于其他情况就很可能需要强制定义您的验证。 虽然这是易于执行,.NET RIA 服务计划过程不能只是流到客户端自定义逻辑该进程是限制为您的 DataContext 和实体代理类创建。

您可以在服务器上保留自定义的逻辑和使从 Silverlight 客户端调用一个服务但的会终止应用程序的响应,并禁止自动确定数据有效性数据控件。 复制和粘贴到客户端,逻辑并手动执行这两个层,验证但代码复制永远不会在好事并。 自动验证问题仍然存在 这是允许使用.NET RIA 服务共享的代码功能的方案。

共享的代码

用于支出报告应用程序,我需要确保两个自定义验证规则: 通过 ¥1,000 任何导出报表必须包含分级显示它们的用途的说明,并没有开支报告可以被归档将来购买。 这些条件都不可以满足使用数据批注,但是我可以轻松地表达这些命令的方式。

.NET RIA 服务包括一种称为共享的代码,可以将同步和以及在客户端应用程序中可用服务器项目中定义逻辑的功能。 在代码项目过程中将项目而不是使用的转换和代理之间复制标记为共享的任何代码。 若要利用此功能,首先创建一个新的代码文件服务器项目使用的.shared [语言扩展] 后缀中 (例如 ExpenseData.shared.cs)。 代码项目过程运行时, 它将只查找具有该后缀项目中的文件并将它处理为共享的代码。

没有新的数据批注在.NET Framework 4.0 中调用 CustomValidationAttribute,允许您自定义验证规则关联实体或属性级别的数据模型。 指定我的两个自定义验证规则可能如下所示:

[MetadataType(typeof(ExpenseReportDetailsMetadata))]
[CustomValidation(typeof(ExpenseReportValidation),
  "ValidateDescription")]
public partial class ExpenseReportDetails { }

public partial class ExpenseReportDetailsMetadata {
  [Bindable(true, BindingDirection.TwoWay)]
  [CustomValidation(typeof(ExpenseReportValidation), 
    "ValidateDateIncurred")]
  [Display(Name = "Date", 
    Description = "The date of when this expense was incurred.")]
  public object DateIncurred;

.NET RIA 服务项目过程已知道该 CustomValidationAttribute,并将环绕到您的客户端代理服务器。 由于验证类型 (的最有可能在服务器上定义) 中包含自定义验证,您可以利用处理其同步的共享代码。

自定义验证方法的签名必须遵循特定的模式:

[Shared]
public static class ExpenseReportValidation {
  public static bool ValidateDateIncurred(object property, 
    ValidationContext context, out ValidationResult validationResult) {

    validationResult = null;
    bool result =       DateTime.Compare((DateTime)property, DateTime.Now) < 0;

    if (!result)
      validationResult = new ValidationResult(context.DisplayName + 
        " must be today or in the past.");
      return result;
  }
}

请注意 ExpenseReportValidation 类上 SharedAttribute 的使用。这表示它不需要转换到客户端中,因为还将介绍的共享代码的一部分的项目过程。

总结

在过去会通过围绕费用报表数据的 CRUD 操作开发费用报告应用程序。新的 Silverlight 3 DataGrid、 DataForm、 DataPager 和 ObjectDataSource 允许快速创建用户界面,而不必投资基础结构部署或不使用内置控件的功能。此外,如果使用.NET RIA 服务可以定义的验证规则和数据访问的服务器端业务逻辑,让它中,则能轻松地使用感谢计划过程。

我的示例费用报表仍然需要一个报告详细信息部分以及导航和身份验证。为此,我需要使用 Silverlight 3,以及一组.NET RIA 服务所提供的应用程序服务中引入的一些其他控件。在以后的文章中,我将演示它的工作原理。

Jonathan Carter 在 Microsoft 技术的推广者。