2010年7月20日星期二

HTML 5 (1)

在网上发布文档或创建网络应用的最常见的格式就是HTML了。刚开始的时候,它还只是主要被设计成描述文档的简单语言,现在它已经成长并适应各种需要,例如从发布新闻和博客,到为邮件、地图、文字处理、电子数据表提供成熟的基础应用。
由于使用HTML的人越来越多,建立在此之上的需求也越来越多,并且它的局限性也越来越明显。HTML 5代表了HTML开发的下一个主要版本,引进了非常多的新特性。如果读者熟悉HTML的以前版本,可以阅读与HTML的不同

1 一个基本的文档
这个小节的目的是创建example01.html文档。

我们将会创建一个非常基本的HTML文档,这个文档可作为将来HTML文档的有用模板。这个文档只包含一个标题和一个简短的段落。

打开文档编辑器并创建一个新的、空的文件。保存为example01.html。

所有的HTML文档都需要由DOCTYPE开始。DOCTYPE是从网络时代很早的时候就有的。目的是保证浏览器能正确解释文档。

在文档编辑器中,写入如下语句,并存盘。

由于所有的文档都需要这句话,所以最好养成总是在新html文档中的第一行写下这个语句的习惯。

一个HTML文档被分成两个主要部分。头部,用来包含文档元数据,例如标题、样式表和脚本。主体,包含所有的页面内容。这个标记语言自己构成一个树状结构。

2 语义理解

通常,撰写和发表文档的目的是把信息传达给读者。可以是任何种类的信息,例如故事、报告新闻和时事或描述可用的产品和服务。无论是什么信息,都应该被读者很容易的理解。

一个典型的文档,例如书籍、新闻报纸、博客或信件,都被分组到不同的部分如标题、段落、列表、表格、注释和其他印刷结构。所有这些部分对更容易的传达信息给读者来说,都很重要。HTML提供了一个清楚标识每个部分的方法,来使这些部分能很容易地被用户表示。

2010年2月11日星期四

WCF RIA 服务 (三十五)-- 身份验证、角色、个性化 6

演练:在Silverlight Business应用程序中使用身份验证服务
Silverlight Business应用程序模板创建的解决方案自动允许身份验证(验证模式为Forms),角色和个性化功能。解决方案包含了数据表格来登录已经存在的用户和注册新用户。我们不用写额外的代码就可以使用这些特性。我们也可以通过定义角色、个性化属性来自定义解决方案。

在本演练中,我们将学习如何在Silverlight Business应用程序中使用身份验证、角色和个性化功能。我们将根据用户的凭证来限制对某些域操作的访问,并根据用户的偏好来定制用户界面。我们将使用ASP.NET网站管理工具来管理角色和用户。

创建网站、用户、角色
我们可以使用Silverlight Business应用程序模板提供的特性,来快速实施验证功能。在下面的章节,我们使用ASP.NET配置工具来创建用户和角色,并登陆此用户。我们还通过Silverlight Business应用程序提供的注册表格来注册新用户。

1. 在Visual Studio中,选择"文件->新建->项目"."新建项目"对话框打开。
2. 选择Silverlight项目类型。
3. 选择Silverlight Business Application模板,并命名为ExampleBusinessApplication.

4. 点击"OK".注意创建的项目结构。这个SL客户端项目中在Views文件夹中包含了SL页面。这些页面允许登陆用户和注册新用户。
5. 打开ASP.NET 网站管理工具。(首先,在资源管理器中,选择服务端项目,然后打开ASP.NET配置工具。
6. 在项目菜单中,选择ASP.NET Configuration.如果在项目菜单中,看不到ASP.NET Configuration选项,有可能是选择了客户端项目。

7.在ASP.NET网站管理工具中选择"安全"标签。

8. 在"角色"的部分,点击"创建或管理角色"链接。
9. 添加一个名为Managers的角色,并点击"添加角色"按钮。

10. 在右下角,点击"返回"按钮。
11. 在“用户"的部分,点击"创建用户"按钮。
12. 使用下面的值来创建新用户,并选择Managers角色复选框。
- User Name : CustomerManager
- Password : P@ssword
- E-mail : someone@example.com
- Security Question : Favorite color?
- Security Answer : Blue
- Managers role : selected

13. 点击"创建用户"按钮。
14. 关闭ASP.NET网站管理工具。
15. 运行解决方案。应用程序的首页将会显示在web浏览器中。
16. 在页面的右上角,点击”登陆"链接。登陆窗口将会出现。
17. 为用户名称输入CustomerManager,为密码输入p@ssword,并点击"OK"按钮。

现在我们就登陆这个用户了,注意到在页面右上角出现文本"Welcome CustomerManager"。
18. 点击"登出"按钮。这时我们就不再以CustomerManager登陆了。
下面的步骤,我们通过注册表格创建一个新用户。
19. 再次点击"登陆"链接。
20. 在登陆对话框中,点击"现在就注册"链接。注册表格就会出现了。
21. 用下面的信息填充注册表。
Username: SalesUser

Friendly name: SalesUser

Email: someone@example.com

Password: P@ssword

Security Question: What was the color of your first car?

Security Answer: Green

22. 点击"OK",创建一个新用户。注意,我们现在已经作为SalesUser登陆了。
23. 关闭浏览器。
24. 打开ASP.NET网站管理工具,点击"安全"标签。注意到已经有了两个用户和角色,即使我们只创建了一个角色。
25. 点击“创建或管理角色”,注意到Managers和Registered Users角色。Registered User角色是Business应用程序模板自动生成的。

26. 对"Registered Users",点击"管理"链接。注意,通过应用程序添加的名为SalesUser的用户已经在Registered Users角色中了。
27. 关闭ASP.NET网站管理工具。

定义访问权限和个性化属性

我们通过为域服务应用RequiresAuthenticationAttribute和RequiresRoleAttribute属性,来限制对域服务的访问。如果域操作没有属性,对所有用户有效。在域操作上添加属性,并不能阻止用户调用域操作。只是没有所需凭证的用户会收到一个异常。

根据角色限制显示数据
1. 在资源管理器中,在服务端项目中,点击App_Data文件夹,选择"添加->已存在项"。
2. 在"添加已存在项"对话框中,添加AdventureWorksLT示例数据库。
3. 在服务端,添加一个新项,并选择ADO.NET Entity Data Model模板。
4. 命名模型为AdventureWorksModel.edmx,并点击"添加"."实体数据模型向导"将会出现。
5. 选择"从数据库生成"选项,并点击"下一步".
6. 选择AdventureWorksLT数据库,并点击"下一步"。
7. 从数据库对象列表中,选择Customer,Product,以及SalesOrderHeader表,然后点击"完成"。实体数据模型将会出现在设计器中。
8. 生成解决方案。
9. 在服务端,添加一个新项,并选择Domain Service Class模板。
10. 命名为AdventureWorksDomainService,然后点击"添加"。
11. 在"添加新域服务类"对话框中,选择Customer,Product和SalesOrderHeader实体。

12. 点击"OK"以完成创建域服务。
13. 在AdventureWorksDomainService类中,对GetSalesOrderHeader方法添加RequiresAuthenticationAttribute属性。

[RequiresAuthentication()]
public IQueryable GetSalesOrderHeaders()
{
return this.ObjectContext.SalesOrderHeaders;
}

14. 对GetCustomers方法添加RequiresRoleAttribute属性,并设置所需的角色为"Managers"。

[RequiresRole("Managers")]
public IQueryable GetCustomers()
{
return this.ObjectContext.Customers;
}

GetProducts方法对所有用户都可用,GetSalesOrderHeader只对验证用户可用,GetCustomers方法只对属于Managers角色的用户可用。

[EnableClientAccess()]
public class AdventureWorksDomainService : LinqToEntitiesDomainService
{
[RequiresRole("Managers")]
public IQueryable GetCustomers()
{
return this.ObjectContext.Customers;
}

public IQueryable GetProducts()
{
return this.ObjectContext.Products;
}

[RequiresAuthentication()]
public IQueryable GetSalesOrderHeaders()
{
return this.ObjectContext.SalesOrderHeaders;
}
}


下面我们在Web.config文件中定义个性法属性。当我们把属性添加到服务端的用户类时,将会为客户端项目生成对应的属性。

1. 在服务端项目,打开Web.config文件。
2. 在元素内,添加名为DefaultRows的个性化属性。这个属性将保持用户对显示的数据数量的偏爱。








3. 保存Web.config文件。
4. 在服务端项目中,展开Models文件夹。
5. 打开User.cs或User.vb文件,添加名为DefaultRows属性。

namespace ExampleBusinessApplication.Web
{
using System.Runtime.Serialization;
using System.Web.Ria.ApplicationServices;

public partial class User : UserBase
{
[DataMember]
public string FriendlyName { get; set; }

public int DefaultRows { get; set; }
}
}


从客户端使用身份验证服务
在调用有限制权限的域操作时,我们应该对用户检查所需的凭证。否则,将会抛出一个异常。在下面的部分,我们将检查用户的凭证,并根据用户的凭证来填充一个到三个DataGrid控件。我们会根据用户个性化中的属性来得到显示的记录数量。对没有验证的用户,默认的值是10。这部分没有让用户更改DefaultRows个性化属性的方式,不过在以后的部分会添加这个方式。

添加一个Silverlight页面来显示数据
1. 在客户端,在Views文件夹中添加新项。
2. 选择Silverlight Page模板,并命名为Reports.xaml。
3. 打开MainPage.xaml文件,并名为Link2的超链接按钮下面添加一个指向Reports页面的链接。



NavigateUri="/Reports" TargetName="ContentFrame" Content="{Binding Path=ApplicationStrings.ReportsPageTitle, Source={StaticResource ResourceWrapper}}"/>

4. 在Assets\Resources文件夹内,打开ApplicationString.resx文件。
5. 添加一个新的名为ReportsPageTitle的字符串资源,并设置值为Reports。

6. 保存并关闭ApplicationString.resx文件。
7. 打开Reports.xaml文件,并添加如下XAML到Grid元素内。



Text="{Binding Path=ApplicationStrings.ReportsPageTitle, Source={StaticResource ResourceWrapper}}"/>
Text="Display reports based on user permissions"/>




8. 拖拽三个DataGrid控件到名为ContentStackPanel的stack面板内。当我们从工具栏中拖拽DataGrid控件时,会在项目中添加一个对System.Windows.Controls.Data程序集的应用,并在页面内添加System.Windows.Controls命名空间。
9. 命名DataGrid控件为ProductsGrid、SalesOrdersGrid、CustomerGrid。
10. 对每个DataGrid控件,设置Margin为5.

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="Reports Page" >



Text="{Binding Path=ApplicationStrings.ReportsPageTitle, Source={StaticResource ResourceWrapper}}"/>
Text="Display reports based on user permissions"/>








11. 打开Reports.xaml.cs或Reports.xaml.vb。
12. 对c#,用using添加System.Windows.Ria,System.Windows.Ria.ApplicationServices,ExampleBusinessApplication.Web,和ExampleBusinessApplication.Resources命名空间。
13. 创建AdventureWorksDomainService的名为context的实例,并创建一个名为numberOfRows的变量来保存检索的记录数量。

private AdventureWorksDomainContext context = new AdventureWorksDomainContext();
int numberOfRows = 10;

14. 添加一个名为LoadRestrictedReports的方法,这个方法调用GetSalesOrderHeaderQuery方法和GetCustomerQuery方法。如果用户属于Managers角色,就显示对应的带结果的数据表格。
如果没有所需凭证的用户调用一个域操作时,域操作会返回一个异常。我们可以通过在调用域操作之前检查凭证来避免这种情况。

private void LoadRestrictedReports()
{
LoadOperation loadSales = context.Load(context.GetSalesOrderHeadersQuery().Take(numberOfRows));
SalesOrdersGrid.ItemsSource = loadSales.Entities;
SalesOrdersGrid.Visibility = System.Windows.Visibility.Visible;

if (WebContext.Current.User.IsInRole("Managers"))
{
LoadOperation loadCustomers = context.Load(context.GetCustomersQuery().Take(numberOfRows));
CustomersGrid.ItemsSource = loadCustomers.Entities;
CustomersGrid.Visibility = System.Windows.Visibility.Visible;
}
else
{
CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
}
}

15. 添加名为LoadReports的方法来检测是否用户已验证,如果已验证,就调用LoadRestricteReports方法。它还检索名为DefaultRows的个性化属性,并对User对象的PropertyChanged事件添加事件处理程序。最后,对所有用户调用GetProductsQuery方法。

private void LoadReports()
{
if (WebContext.Current.User.IsAuthenticated)
{
numberOfRows = WebContext.Current.User.DefaultRows;
WebContext.Current.User.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(User_PropertyChanged);
LoadRestrictedReports();
}
else
{
CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
}

LoadOperation loadProducts = context.Load(context.GetProductsQuery().Take(numberOfRows));
ProductsGrid.ItemsSource = loadProducts.Entities;
}

16. 对PropertyChanged事件添加事件处理程序,保证在DefaultRows属性改变时调用LoadReports方法。

void User_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "DefaultRows")
{
LoadReports();
}
}

17. 为LoggedIn和LoggedOut事件添加事件处理方法,并根据用户凭证的状况装载或隐藏数据。

void Authentication_LoggedIn(object sender, AuthenticationEventArgs e)
{
LoadReports();
}

void Authentication_LoggedOut(object sender, AuthenticationEventArgs e)
{
CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
}

18. 在构造函数内添加下面的代码:

public Reports()
{
InitializeComponent();

this.Title = ApplicationStrings.ReportsPageTitle;

WebContext.Current.Authentication.LoggedIn += new System.EventHandler(Authentication_LoggedIn);
WebContext.Current.Authentication.LoggedOut += new System.EventHandler(Authentication_LoggedOut);

LoadReports();
}

以下是完整代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;
using System.Windows.Ria;
using System.Windows.Ria.ApplicationServices;
using ExampleBusinessApplication.Web;
using ExampleBusinessApplication.Resources;

namespace ExampleBusinessApplication.Views
{
public partial class Reports : Page
{
private AdventureWorksDomainContext context = new AdventureWorksDomainContext();
int numberOfRows = 10;

public Reports()
{
InitializeComponent();

this.Title = ApplicationStrings.ReportsPageTitle;

WebContext.Current.Authentication.LoggedIn += new System.EventHandler(Authentication_LoggedIn);
WebContext.Current.Authentication.LoggedOut += new System.EventHandler(Authentication_LoggedOut);

LoadReports();
}

private void LoadReports()
{
if (WebContext.Current.User.IsAuthenticated)
{
numberOfRows = WebContext.Current.User.DefaultRows;
WebContext.Current.User.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(User_PropertyChanged);
LoadRestrictedReports();
}
else
{
CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
}

LoadOperation loadProducts = context.Load(context.GetProductsQuery().Take(numberOfRows));
ProductsGrid.ItemsSource = loadProducts.Entities;
}

private void LoadRestrictedReports()
{
LoadOperation loadSales = context.Load(context.GetSalesOrderHeadersQuery().Take(numberOfRows));
SalesOrdersGrid.ItemsSource = loadSales.Entities;
SalesOrdersGrid.Visibility = System.Windows.Visibility.Visible;

if (WebContext.Current.User.IsInRole("Managers"))
{
LoadOperation loadCustomers = context.Load(context.GetCustomersQuery().Take(numberOfRows));
CustomersGrid.ItemsSource = loadCustomers.Entities;
CustomersGrid.Visibility = System.Windows.Visibility.Visible;
}
else
{
CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
}
}

void Authentication_LoggedIn(object sender, AuthenticationEventArgs e)
{
LoadReports();
}

void Authentication_LoggedOut(object sender, AuthenticationEventArgs e)
{
CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
}

void User_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "DefaultRows")
{
LoadReports();
}
}
}
}

19. 运行解决方案。
20. 点击Reports链接。注意到,当我们没登陆时,只有产品表显示在报告页面。
21. 点击"登陆"链接,并以SalesUser登陆。会发现,产品和销售订单表显示出来。

22. 登出并以CustomerManager登陆。会发现,三个表都显示了。
23. 关闭web浏览器。

添加一个窗口来设置个性化属性
我们可以通过添加一个子窗口来允许用户编辑DefaultRows个性属性。当值改变后,我们调用SaveUser方法来把值保存到数据源。我们通过当前WebContext实例的User对象上的属性来检索当前的值。
1. 在客户端,在Views文件夹中添加新项。
2. 选择Silverlight Child Window模板,并命名为ProfileWindow.xaml。

3. 点击"添加"按钮。
4. 在ProfileWindow.xaml文件内,在Grid.RowDefinitions元素后添加XAML,来添加一个下拉列表选择要在报表中显示的行数。



















5. 设置ChildWindow的Title属性为Select References。
6. 在ProfileWindow.xaml.cs中,添加下面的代码来检索和设置个性化属性。

public partial class ProfileWindow : ChildWindow
{
public ProfileWindow()
{
InitializeComponent();

string userDefaultRows = WebContext.Current.User.DefaultRows.ToString();
foreach (ComboBoxItem cbi in defaultRows.Items)
{
if (cbi.Content.ToString() == userDefaultRows)
{
defaultRows.SelectedItem = cbi;
break;
}
}
}

private void OKButton_Click(object sender, RoutedEventArgs e)
{
int newSelection = int.Parse(defaultRows.SelectionBoxItem.ToString());
if (newSelection != WebContext.Current.User.DefaultRows)
{
WebContext.Current.User.DefaultRows = newSelection;
WebContext.Current.Authentication.SaveUser(true);
}
this.DialogResult = true;
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
}
}

7. 如果是VB,添加Imports命令引用System.Windows.Controls和System.Windows命名空间。
8. 展开Views\Login文件夹,并打开LoginStatus.xaml文件。
9. 添加一个指向profile文件的设置链接,在Logout按钮前添加下面的XAML




10. 在LoginStatus.xaml.cs文件内,为设置链接添加点击事件处理方法。

private void SettingsButton_Click(object sender, RoutedEventArgs e)
{
ExampleBusinessApplication.Views.ProfileWindow settingsWindow = new ExampleBusinessApplication.Views.ProfileWindow();
settingsWindow.Show();
}

11. 运行解决方案。
12. 以CustomerManager或SalesUser登陆,注意到在登陆状态栏,现在包含一个"设置"的链接。

13. 点击"设置"链接,并设置默认的报表显示行数。

14. 打开报表页面,会注意到DataGrid现在包含我们所选的行数。

WCF RIA 服务 (三十四)-- 身份验证、角色、个性化 5

如何:创建自定义验证属性

WCF RIA Services框架提供了RequiresAuthenticationAttribute和RequiresRoleAttribute属性。这两个属性使我们很方便地指定哪个域操作是仅仅对验证用户或特定角色成员可用的。除了这两个属性,我们可以创建自制验证逻辑属性并对域操作应用这个属性。
这章我们演示如何为身份验证添加自制属性。我们通过创建一个派生于AuthorizationAttribute的类并重载Authorize方法用来提供我们自定义的逻辑,来创建一个自制验证属性。

1. 在服务端项目中,创建派生于AuthorizationAttribute的类。
2. 重载Authorize方法,并添加判断是否授权的逻辑。下面的例子演示一个名为CheckAttendeeName的定制属性,来检测用户的角色和用户名字的第一个字母。

public class CheckAttendeeNameAttribute : System.Web.DomainServices.AuthorizationAttribute
{
public override bool Authorize(System.Security.Principal.IPrincipal principal)
{
if (principal.IsInRole("Attendee") && principal.Identity.Name.StartsWith("A"))
{
return true;
}
else
{
return false;
}
}
}

3. 要执行自定义的验证逻辑,对域操作应用这个自定义的验证属性。

[CheckAttendeeName()]
public IQueryable GetCourses()
{
return this.ObjectContext.Courses;
}

WCF RIA 服务 (三十三)-- 身份验证、角色、个性化 4

如何:在RIA Services中允许个性化功能
使用个性化功能,我们可以为用户检索和保存属性。WCF RIA Services中的个性化功能建立在ASP.NET的个性化框架上。
我们只能在用户验证后检索或保存个性化属性。

配置服务端项目
1. 在服务端项目中,打开Web.config文件。
2. 在段内,添加元素。
3. 在元素内,添加个性化属性。下面示例如何创建个性化以及定义一个名为FriendlyName的属性。










4. 为验证服务打开包含User类的文件。
5. 在User类中,添加我们已在Web.config文件中添加过的个性化属性。

public partial class User : UserBase
{
public string FriendlyName { get; set; }
}


从客户端访问个性化属性
1. 在Silverlight客户端项目中,打开后台代码页面。
2. 在后台代码页面中,设置或检索当前WebContext实例的User对象上的个性化属性。

WebContext.Current.User.FriendlyName = "Mike";

3. 如果想让WebContext对象在XAML中可用,那么在创建RootVisual之前,在Application.Startup事件中把当前WebContext实例添加到应用程序资源中。

private void Application_Startup(object sender, StartupEventArgs e)
{
this.Resources.Add("WebContext", WebContext.Current);
this.RootVisual = new MainPage();
}

通过声明性语法,我们也可以检索个性化属性。示例如下:



WCF RIA 服务 (三十二)-- 身份验证、角色、个性化 3

如何:在RIA Services中允许角色功能
使用角色,我们可以指定哪个验证用户组可以访问某些资源。WCF RIA Services中的角色功能是建立在ASP.NET的角色功能上的。
我们只有在用户已经被验证后,才能检索用户的角色信息。通过在域操作中的方法上使用RequireRoleAttribute属性,我们就可以限制角色中的成员对域操作的访问。

配置服务端项目
1. 在服务端项目中,打开Web.config文件。
2. 在段中,添加元素。






3. 在成员数据库中,创建所需的角色并赋予用户所需的角色。更多详情,可看后面的章节。
4. 要确保只有指定角色中的成员才能访问域操作,我们需要对域操作应用RequireRoleAttribute属性。

[RequiresRole("Managers")]
public IQueryable GetCustomers()
{
return this.ObjectContext.Customers;
}


在客户端使用角色
1. 要检测是否用户属于要求的角色,使用Roles属性或调用WebContext.Current.User对象的IsInRole方法。下面示例了在调用域操作之前,检测是否用户属于Managers的角色。

private void LoadRestrictedReports()
{
LoadOperation loadSales = context.Load(context.GetSalesOrderHeadersQuery().Take(numberOfRows));
SalesOrdersGrid.ItemsSource = loadSales.Entities;
SalesOrdersGrid.Visibility = System.Windows.Visibility.Visible;

if (WebContext.Current.User.IsInRole("Managers"))
{
LoadOperation loadCustomers = context.Load(context.GetCustomersQuery().Take(numberOfRows));
CustomersGrid.ItemsSource = loadCustomers.Entities;
CustomersGrid.Visibility = System.Windows.Visibility.Visible;
}
else
{
CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
}
}

2. 如果想让WebContext对象在XAML中可用,那么在创建RootVisual之前,在Application.Startup事件中把当前WebContext实例添加到应用程序资源中。

private void Application_Startup(object sender, StartupEventArgs e)
{
this.Resources.Add("WebContext", WebContext.Current);
this.RootVisual = new MainPage();
}

WCF RIA 服务 (三十一)-- 身份验证、角色、个性化 2

如何:在RIA Services中允许进行身份验证
WCF RIA Services中的身份验证是建立在ASP.NET验证框架之上的。
本章节展示如后在我们的应用程序中通过RIA Services来允许用户身份验证。我们必须在服务端和客户端添加代码,来使身份验证可行。这个验证对客户端就如同一个服务。我们可以通过对域操作应用RequiresAuthenticationAttribute属性,来保证只有验证用户可以访问域操作。

配置服务端项目

1. 在服务端项目中,打开Web.config文件。
2. 在元素中,添加元素。
3. 设置mode属性为我们想在项目中使用的验证模式。下面的代码展示了mode属性设置为Forms的元素。当然,我们的Web.config文件可能包含其他元素。





4. 保存Web.config文件。
5. 在资源管理器中,右键点击服务端项目,选择"添加->新项"。出现"添加新项"对话框。
6. 选择Authentication Domain Service模板,并制定一个名字。

7. 点击"添加"。
8. 如果想只有验证用户可以访问域操作,对域操作应用RequiresAuthenticationAttribute属性。下面代码展示了只有验证用户才可以访问GetSalesOrderHeaders方法。

[RequiresAuthentication()]
public IQueryable GetSalesOrderHeaders()
{
return this.ObjectContext.SalesOrderHeaders;
}

9. 生成解决方案。

在客户端配置身份验证服务

1. 在客户端项目中,打开App.xaml文件。
2. 在application lifetime 对象集合中添加当前的WebContext对象。下面的app前缀引用包含生成的WebContext对象的命名空间。通常,这个WebContext用的是客户端项目的基命名空间。






3. 在WebContext元素内,添加验证模式。
我们可以添加WindowsAuthentication或者FormsAuthentication。然而,我们添加的类型应该与服务端的验证模式一致。下面展示了如何添加FormsAuthentication。

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:ExampleApplication"
xmlns:appsvc="clr-namespace:System.Windows.Ria.ApplicationServices;assembly=System.Windows.Ria"
x:Class="ExampleApplication.App">











4. 如果必要的话,在客户端添加一个页面来收集用户凭证。
5. 在登录页面的后台代码文件中,调用Login方法来登录用户。下面的例子展示了如何从登录按钮的事件处理中调用Login方法。包含了一个回调函数来对登录操作的结果做反应。

private void LoginButton_Click(object sender, RoutedEventArgs e)
{
LoginParameters lp = new LoginParameters(UserName.Text, Password.Password);
WebContext.Current.Authentication.Login(lp, this.LoginOperation_Completed, null);
LoginButton.IsEnabled = false;
LoginResult.Text = "";
}

private void LoginOperation_Completed(LoginOperation lo)
{
if (lo.HasError)
{
LoginResult.Text = lo.Error.Message;
LoginResult.Visibility = System.Windows.Visibility.Visible;
lo.MarkErrorAsHandled();
}
else if (lo.LoginSuccess == false)
{
LoginResult.Text = "Login failed. Please check user name and password.";
LoginResult.Visibility = System.Windows.Visibility.Visible;
}
else if (lo.LoginSuccess == true)
{
SetControlVisibility(true);
}
LoginButton.IsEnabled = true;
}

6. 调用Logout方法来登出用户。

private void LogoutButton_Click(object sender, RoutedEventArgs e)
{
WebContext.Current.Authentication.Logout(this.LogoutOperation_Completed, null);
}

private void LogoutOperation_Completed(LogoutOperation lo)
{

if (!lo.HasError)
{
SetControlVisibility(false);
}
else
{
ErrorWindow ew = new ErrorWindow("Logout failed.", "Please try logging out again.");
ew.Show();
lo.MarkErrorAsHandled();
}
}

7. 要检测是否用户已登录,检索生成的User实体的IsAuthentication属性。下面的代码展示了在检索个性化信息和调用域操作之前,先检测是否当前用户已经验证了。

private void LoadReports()
{
if (WebContext.Current.User.IsAuthenticated)
{
numberOfRows = WebContext.Current.User.DefaultRows;
WebContext.Current.User.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(User_PropertyChanged);
LoadRestrictedReports();
}
else
{
CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
}

LoadOperation loadProducts = context.Load(context.GetProductsQuery().Take(numberOfRows));
ProductsGrid.ItemsSource = loadProducts.Entities;
}

8. 如果想让WebContext对象在XAML中可用,那么在创建RootVisual之前,在Application.Startup事件中把当前WebContext实例添加到应用程序资源中。

private void Application_Startup(object sender, StartupEventArgs e)
{
this.Resources.Add("WebContext", WebContext.Current);
this.RootVisual = new MainPage();
}

2010年2月10日星期三

WCF RIA 服务 (三十)-- 身份验证、角色、个性化

在Web应用中,我们经常会限制某些特定用户使用一些功能,并对每个用户保持性能。ASP.NET提供了身份验证、角色、个性化实现这些功能。身份验证允许我们核实用户的凭证并标记用户为已登录。角色允许我们根据职责来把用户分组,并对组中成员授予资源许可。个性化允许我们保持验证用户的属相并在web应用中检索这些属性。

WCF RIA Services框架在表现层和中间层都提供这些功能。这节就介绍RIA Services中的身份验证、角色、和个性化。

身份验证域服务
RIA Services提供身份验证域服务模板来简化在表示层中使用身份验证、角色和个性化。我们把验证域服务添加到服务端项目中。

当我们添加一个身份验证域服务时,RIA Services框架自动在服务端添加两个类。代表身份验证服务的类派生于AuthenticationBase(T)类。代表用户的类派生于UserBase类。用户类为已经验证的用户包含个性化属性。
通过客户端代码,我们调用身份验证域服务来请求我们需要的验证、角色或个性化信息。

WebContext
当我们添加一个身份验证域服务时,RIA Services自动在客户端生成一个WebContext类。这个类派生于WebContextBase类,并允许我们在客户端访问身份验证域服务和用户。用Current属性来获取WebContext的当前实例。

身份验证
要想在RIA Services解决方案中使用身份验证,我们必须在服务端和客户端来配置验证。
通过在客户端使用WebContext,我们可以从Silverlight应用程序同步登陆用户。当我们实施了身份验证时,我们通常会用到下面的方法和属性。
Authentication属性(WebContext.Current.Authentication): 提供对身份验证服务的访问。
User属性(WebContext.Current.User): 提供对包含用户状态的对象的访问。
Login(String,String)方法(WebContext.Current.Authentication.Login(String,String):同步验证用户凭证。
Logout(Boolean)方法(WebContext.Current.Authentication.Logout(boolean):同步登出一个已验证用户。

角色
在我们实施了身份验证后,我们就可以配置我们的解决方案来使用角色功能。当我们实施角色功能时,通常会用到下面的属性和方法。
WebContext.Current.User.Roles : 提供用户被赋予的角色的集合。
WebContext.Current.User.IsInRole(String) : 判断已验证用户是否是指定角色的成员。

个性化
要想使用个性化功能,我们必须为个性化功能配置我们的解决方案。当我们实施了个性化功能时,通常会用到下面的属性和方法。
WebContext.Current.User : 包含所有我们已经添加到User类中的属性。例如,User.PhoneNumber。
LoadUser()方法(WebContext.Current.Authentication.LoadUser()) : 刷新用户状态。
SaveUser(Boolean)方法(WebContext.Current.Authentication.SaveUser(boolean)) : 把所有的更改保存到用户状态中。

在客户端处理身份验证错误
我们可以处理在登录、登出、装载或保存用户时产生的错误。思路是通过在调用这些方法时提供回调参数。在回调方法中,我们添加处理错误的代码,并调用MarkErrorAsHandled方法来指定framwork不要抛出异常。AuthenticationService类允许我们在调用下面的方法时提供回调参数。
-- LoadUser
-- Login
-- Logout
-- SaveUser

限制对域服务的访问
在我们实施了身份验证和角色功能后,我们就可以限制特定用户对域服务的访问。可以对整个域服务或服务中个别的操作应用下面的属性。如果我们对整个服务使用了属性,它会应用到所有的操作。
- RequiresAuthenticationAttribute : 指定只有具有验证凭证的用户可以访问操作。
- RequiresRoleAttribute : 指定只有属于特定角色的验证用户可以访问操作。
我们也可以创建自己的验证属性,更多相关信息可以看后面的章节。

WCF RIA 服务 (二十九)-- Silverlight 客户端 10

如何:在客户端添加计算特性
我们可以在客户端添加成员属性,这些属性是有实体类中的成员属性计算而来。局部方法被用来引发事件,来通知用户界面元素数值已被更改。当我们添加计算的成员属性时,这些属性只存在于客户端项目中。

1. 在客户端项目中,添加一个类文件。
2. 声明一个局部类,这个类与我们想要修改的实体代理类具有相同的名字和命名空间。
3. 添加一个属性,创建一个基于一个或多个实体代理类中值的新值。
4. 对每一个用于计算新值的成员属性都实施On[CustomProperty]Changed局部方法,并且调用RaisePropertyChanged方法来通知框架计算的属性已经更改。

下面的示例演示了如何为一个员工计算总的不工作的有效时间,这个时间是基于休假时间和生病请假的时间。对休假时间或生病请假的时间的改动,都将会对总时间产生一个改动。

using System.Windows.Ria;

namespace RIAServicesExample.Web
{
public partial class Employee : Entity
{
public int TotalOffHours
{
get { return this.SickLeaveHours + this.VacationHours; }
}
partial void OnSickLeaveHoursChanged()
{
this.RaisePropertyChanged("TotalOffHours");
}
partial void OnVacationHoursChanged()
{
this.RaisePropertyChanged("TotalOffHours");
}
}
}


可以用下面的代码来对计算的属性进行数据绑定:




WCF RIA 服务 (二十八)-- Silverlight 客户端 9

自定义生成代码
对于WCF RIA Services,在某些情况下,我们想在客户端生成的代码中添加些东西。然而,我们不能直接定制生成代码,因为在下次中间层重新编译的时候会被覆盖掉。RIA Services在生成代码中提供了局部方法,这样我们可以在分开的代码文件中定制客户端代码。这些局部方法就如同“钩子”,通过它我们可以把自己的代码附加在生成代码上。只有当我们已经创建了对应的局部方法时,这些方法才能被调用。
局部方法
WCF RIA Services框架为域上下文类和实体类生成局部方法。
对于域上下文类,提供了下面的局部方法。
OnCreated():当DomainContext对象实例化时执行。

对于实体类,提供了下面的局部方法。
OnCreated():当实体对象实例化时执行。

OnLoaded(boolean):在第一次装载实体并反串行化时,或当实体从服务端反串行化但已经在客户端存在时执行。

On[PropertyName]Changing: 在验证之后,设定值之前,被调用。

On[PropertyName]Changed: 在设定值之后,调用RaiseDataMemberChanged方法之前,被调用。

On[CustomMethodName]Invoking: 调用定制方法之后,实际执行代码之前,被调用。

On[CustomMethodName]Invoked: 定制方法调用并执行代码之后,被调用。

实施局部方法
要使用这些方法,我们添加一个与想定制的生成类相同名字和命名空间的局部类。应为自动生成的客户端代码与服务端项目的代码有一样的命名空间,所以我们局部类的命名空间通常是projectname.Web的格式。然后,在客户端代码必须执行的情况下,我们实施想要执行的方法。例如在一个域上下文被创建并装载时,我们添加如下代码:

using System.Windows.Ria;

namespace RIAServiceExample.Web
{
public partial class EmployeeDomainContext : DomainContext
{
partial void OnCreated()
{
this.Load(this.GetEmployeesQuery());
}
}
}


我们还可以在生成的实体类的局部方法中设置成员属性。例如,如果数据库中的员工表中包含一个名为CreatedBy字段,那么我们可以通过OnCreated()局部方法来设置这个属性的值。

using System.Windows.Ria;

namespace RIAServiceExample.Web
{
public partial class Employee : Entity
{
partial void OnCreated()
{
this.CreatedBy = WebContext.Current.User.Name;
}
}
}

2010年2月7日星期日

WCF RIA 服务 (二十七)-- Silverlight 客户端 8

演练:在Silverlight商业应用程序中显示数据
在本演示中,我们将创建一个显示数据的Silverlight商业应用程序。Visual Studio提供了几个设计时工具来帮助我们创建SL商业应用程序。这个演练将展现如何使用DataSources窗口在RIA中创建与数据一起工作的用户界面。
演示将会满足下面的任务:
1. 创建SL商业应用程序,它包含SL客户端和ASP.NET Web应用两个项目。
2. 通过更改应用程序名字来修改应用程序资源,它存贮为资源字符。
3. 创建一个AdventureWorksClassLibrary示例数据库的实体数据模型。
4. 创建一个向客户端公开实体数据模型中数据的域服务。
5. 在域服务中添加和修改自定义查询。
6. 创建额外的SL页面来向用户显示数据。
7. 在默认的导航栏中添加按钮来访问SL中的页面。
8. 通过从Data Sources窗口向Silverlight设计器中拖拽条目来配置SL页面显示数据。
9. 排序和分页数据。
10. 配置用户界面来接受查询参数。

这个演练是在VS2010中进行的,如果是其他版本会有不同。

创建SL商业应用程序

1. 打开文件->新建->项目
2. 展开Visual c#或Visual Basic,并选择Silverlight。
3. 选择Silverlight Business Application
4. 在名字文本框内,输入AdventureWorksApp并点击OK。
这个解决方案包含两个项目:一个AdventureWorksApp客户端项目和一个AdventureWorksApp.Web Web应用项目。

命名和测试应用程序
Silverlight Business Application有内置功能。默认的,它有一个主页,一个关于页,一个导航栏,以及注册功能。提供了一个资源字符来作为默认的应用程序名字,我们可以更改它。
1. 在客户端解决方案资源管理器中,展开Resources文件夹。(VS2008中打开Assets文件夹下的Resources文件夹)
2. 双击ApplicationStrings.resx,打开资源编辑器。
3. 把资源字符串ApplicationName的值改为Adventure Works Application.
4. 保存并关闭ApplicationStrings.resx文件。
5. 运行应用程序。
主页会打开并显示默认的设计,包括已经更改过的应用程序名称。

为应用程序创建一个数据模型

1. 在解决方案资源管理器中,右键点击AdventureWorksApp.Web,并添加一个新项。
2. 在"添加新项"对话框中,选择ADO.NET Entity Data Model项。
3. 命名为AdventureWorksEDM.edmx,然后点击添加。实体数据模型向导会打开。
4. 在"选择模型内容"的页面上,点击"从数据库生成",然后点击"下一步"。
5. 在"选择数据连接"的页面上,选择或创建对AdventureWorks数据库的链接。
6. 确保选择了"Save entity connection settings in Web.Config as"选项,然后点击"下一步"。
7. 在"选择数据库对象"的页面上,重新命名模型命名空间Model为AdventureWorksDataModel。
8. 展开"表"节点,选择Customer表。
9. 点击"完成"。
10. 生成解决方案。

创建域服务

一个域服务会把数据模型中的数据实体和操作公开给客户端。它是添加在服务端项目中的。
1. 在"解决方案资源管理器"中,右键点击AdventureWorksApp.Web,并添加一个新项。
2. 在"添加新项"对话框中,选择"Domain Service Class"。
3. 命名为AdventureWorksService。
4. 点击"添加"。 "添加新项"对话框出现。
5. 选择下面的复选框:
. Enable client access
. Customer 和 Enable editing
. Generate associated classes for metadata.
6. 点击OK
7. 生成解决方案。

更改域服务的查询

域服务提供默认的操作,我们应该为我们特定的应用而修改它们。这个演练中,我们更改默认的查询,来返回按CustomerID排序的客户。

1. 在"解决方案资源管理器"中,双击AdventureWorksSercice.cs或AdventureWorksService.vb.
2. 更改GetCustomers方法,如下:

public IQueryable GetCustomer()
{
return this.ObjectContext.Customer.OrderBy(c=>c.CustomerID);
}

3. 生成解决方案。

创建Silverlight页面来显示数据
从Customer表返回的数据显示在自己的页面上,不显示在应用程序的首页上。

1. 在"解决方案资源管理器"中,右键点击客户端项目中的Views文件夹,并添加新项。
2. 在"添加新项"对话框中,选择Silverlight Page项。
3. 更改名字为CustomerList.xaml,并点击"添加"。
4. 从工具栏拖拽一个TextBlock放在CustomerList.xaml页面的顶部。
5. 把Text属性改为Customer List。
6. 保存CustomerList.xaml页面。

在首页上添加导航按钮
我们在应用程序的首页上添加一个导航到CustomerList页面的按钮。

1. 在"解决方案资源管理器"中,双击MainPage.xaml.
2. 在XAML视图里,在行下面添加如下代码:



NavigateUri="/CustomerList" TargetName="ContentFrame"/>

3. 运行应用程序,确认Customers按钮显示在导航栏上,并点击的时候显示CustomerList页面。

在CustomerList页面上显示客户数据
在DataGrid中显示客户数据。下面,我们就创建和配置一个DataGrid来显示客户数据,需要从Data Sources窗口拖拽一个Customer实体到设计窗口内。

1. 双击CustomerList.xaml。
2. 打开Data Sources窗口。可以在Data菜单中,选择Show Data Sources。注意Data Sources窗口已经包含了可用的实体。
3. 从Data Sources窗口中把Customer节点拖到设计器中,在TextBlock下面。
4. 运行应用程序并点击导航栏上的Customers按钮。
5. 核实在CustomerList页面上显示了Customer数据。
(我在VS2008中使用Data source有问题,生成不了数据节点。好像在Silverlight3中没有了ComponentModel.dll,所以直接使用域上下文来查询数据)

在域服务中添加自定义查询

1. 双击AdventureWorksService.cs。
2. 在类中添加如下代码:

public IQueryable GetCustomersByTitle()
{
return this.ObjectContext.Customer.Where(c => c.Title == "Mr.").OrderBy(c => c.CustomerID);
}

public IQueryable GetCustomersByLastName(string lastName)
{
return this.ObjectContext.Customer.Where(c => c.LastName==lastName).OrderBy(c=>c.CustomerID);
}

3. 生成解决方案。

显示自定义查询返回的数据

1. 双击CustomerList.xaml。
2. 在"数据源"窗口,选择Customer并点击下拉菜单。
3. 在查询列表中选择GetCustomersByTitle。
4. 把Customer节点拖拽到已经存在于设计器中的DataGrid。
5. 运行应用程序,并验证只有Title等于Mr.的顾客显示在页面上。

添加排序功能

VS2008中实现如下:
1. 双击CustomerList.xaml。
2. 在XAML视图中,在DomainDataSource控件中,添加SortDescriptors。
3. 设置PropertyPath属性为"LastName"。设置Direction属性为Ascending。
4. 运行应用程序,注意到顾客数据已经按LastName排序了。

添加分页功能

VS2008中实现如下:
1. 双击CustomerList.xaml。
2. 在XAML视图中,在DomainDataSource控件中,添加PageSize属性,并设置为15.添加LoadSize属性,并设置为30.
3. 添加DataPager控件。



4. 运行应用程序,注意到页面装载的速度已经大大加快了。并且可以通过DataPager控件浏览数据。

创建一个页面来根据LastName进行查询

1. 在AdventureWorksApp中的Views文件夹下,添加新项。
2. 在"添加新项"对话框中,选择Silverlight Page项。
3. 命名为CustomerSearchByLastName.xaml,并点击"添加"。
4. 保存这个页面。

在首页中添加一个导航按钮

1. 双击MainPage.xaml。
2. 在XAML视图中,在Link3的HyperlinkButton下面添加如下代码。






创建用户界面来运行GetCustomerByLastName查询
VS2008中实现如下:

1. 双击CustomerSearchByLastName.xaml
2. 添加命名空间引用

xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Ria"
xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Controls.Ria"
xmlns:domain ="clr-namespace:AdventureWorksApp.Web"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

3. 然后在Grid中添加数据源、DataGrid、文本框和按钮(实际代码中不需SL:前缀),如下代码:






















测试应用程序

1. 生成解决方案。
2. 运行应用程序。
3. 点击"Customer Search"。
4. 在文本框中输入想要查询的LastName,例如"liu"。
5. 点击Load按钮。
6. 将会显示名为"liu"的客户。

WCF RIA 服务 (二十六)-- Silverlight 客户端 7

演练:编辑来自域服务的数据
当我们在域服务中添加了更新、插入或删除方法时,我们就可以在Silverlight客户端创建一个接口来让用火修改数据。EntityChangesSet对象跟踪所有的改变,并且这些改变在我们调用SubmitChanges方法时一起提交。
在这个演练中,我们将学习如何创建一个让用户修改所显示数据的接口,并且将这些修改保存到数据库。

演练

1. 打开RIAServicesExample项目。(参见 WCF RIA 服务 三)
2. 在MainPage.xaml中,更改界面让用户可以保存或拒绝DataGrid中的更改。下面的XAML添加了一个Save Changes按钮和Reject Changes按钮,以及一个更改文本框。















3. 在MainPage.xaml的后台代码文件中,添加按钮的点击事件处理方法、RowEditEnded事件的处理方法、一个名为OnSubmitCompleted的回调方法,和一个处理行将发生的更改的方法。

private void SaveButton_Click(object sender, RoutedEventArgs e)
{
_customerContext.SubmitChanges(OnSubmitCompleted, null);
}

private void RejectButton_Click(object sender, RoutedEventArgs e)
{
_customerContext.RejectChanges();
CheckChanges();
}

private void CustomerGrid_RowEditEnded(object sender, DataGridRowEditEndedEventArgs e)
{
CheckChanges();
}

private void CheckChanges()
{
EntityChangeSet changeSet = _customerContext.EntityContainer.GetChanges();
ChangeText.Text = changeSet.ToString();

bool hasChanges = _customerContext.HasChanges;
SaveButton.IsEnabled = hasChanges;
RejectButton.IsEnabled = hasChanges;
}

private void OnSubmitCompleted(SubmitOperation so)
{
if (so.HasError)
{
MessageBox.Show(string.Format("Submit Failed: {0}", so.Error.Message));
so.MarkErrorAsHandled();
}
CheckChanges();
}


为要更改的实体设置元数据

1. 在服务端项目中,为Customer实体手动添加一个名为Customer.metadata.cs的元数据类。
2. 在元数据类中,对CustomerID和ModifiedData成员属性添加EditableAttribute属性并设置AllowEdit属性为false。我们对成员属性应用EditableAttribute来指定是否想让这个成员属性在客户端让用户编辑。当我们将AllowEdit为false时,这个成员属性在客户端是只读的。在这个例子中,我们只想显示CustomeId和ModifiedDate成员属性而不想用户修改它们。
3. 添加ExcludeAttribute属性给PasswordHash,PasswordSalt和rowguid成员属性。我们对不想包含客户端生成代码中的成员属性使用ExcludeAttribute。在这个例子中,我们没有必要把PasswordHash,PasswordSalt和rowguid成员属性向客户端公开。

[MetadataTypeAttribute(typeof(Customer.CustomerMetadata))]
public partial class Customer
{
internal sealed class CustomerMetadata
{

// Metadata classes are not meant to be instantiated.
private CustomerMetadata()
{
}

[Editable(false)]
public int CustomerID;

[Editable(false)]
public DateTime ModifiedDate;

[Exclude]
public string PasswordHash;

[Exclude]
public string PasswordSalt;

[Exclude]
public Guid rowguid;

}
}

4. 用using添加所需的命名空间,例如 System.ComponentModel.DataAnnotations和System.Web.DomainServices.
5. 运行应用程序。
注意我们可以在DataGrid中编辑值了。当我们离开所编辑的行时,ChangeText的值将会变为行将发生的更改的描述。当我们点击Save Changes按钮时,数据更改将会保存到数据库中。当点击Reject Changes按钮时,所有的行将发生的更改将恢复原样。

WCF RIA 服务 (二十五)-- Silverlight 客户端 6

演练:检索和显示来自域服务的数据
想要在Silverlight应用程序中检索数据,我们调用域上下文中的方法,这些方法对应着域服务中我们想要使用的查询方法。例如,在域服务中有个名为GetProducts的方法,那么在与上下文中有个名为GetProductsQuery的方法。在SL应用程序中,我们调用GetProductsQuery方法,此方法返回一个EntityQuery(TEntity)对象。
在SL应用程序中,我们可以对查询应用额外的过滤来限制返回的实体。虽然一个查询方法可以返回产品表的所有记录,但可能我们只想显示价格低于100的产品。我们使用LINQ和LINQ运算符的子集来改变从查询返回的结果。下面列出了可用的查询运算符:
1. Where
2. OrderBy
3. ThenBy
4. Skip
5. Take
当应用了额外的过滤时,我们把EntityQuery(TEntity)对象传递给Load方法的一个参数,来运行查询和得到结果。如果查询有一个IsComposable属性设为false的QueryAttribute([Query(IsComposable = false)])。我们不可以在查询上应用额外的过滤。通常,只有返回单一实体的查询把IsComposable设置为false。
我们可以把数据绑定到任何显示数据的SL控件。DataGrid控件可以以表格的格式来显示数据。

演练
1. 打开RIAServicesExample解决方案。(WCF RIA 服务 三
2. 在Silverlight应用程序中,打开MainPage.xaml的代码文件。
3. 调用GetCustomersQuery方法来创建EntityQuery(TEntity)实例。
4. 使用可用的查询运算符来过滤客户。
5. 把查询对象传递给DomainContext的Load方法,并报返回结果赋给LoadOperation(TEntity).
下面的代码演示如何从域服务检索客户信息。并且过滤电话号码以583开始的客户,同时以LastName按字母排序。结果显示在DataGrid控件中。

public partial class MainPage : UserControl
{
private CustomerDomainContext _customerContext = new CustomerDomainContext();

public MainPage()
{
InitializeComponent();
EntityQuery query =
from c in _customerContext.GetCustomersQuery()
where c.Phone.StartsWith("583")
orderby c.LastName
select c;
LoadOperation loadOp = this._customerContext.Load(query);
CustomerGrid.ItemsSource = loadOp.Entities;
}
}

结果显示如下:

2010年2月6日星期六

WCF RIA 服务 (二十四)-- Silverlight 客户端 5

在客户端处理错误
当我们在客户端检索或修改数据时,我们通常需要处理错误和对错误做出反应。通过WCF RIA Services,我们为数据操作提供一个回调方法来处理错误,并且在回调方法里检查错误。使用回调方法是必需的,因为调用数据操作都是异步的,比且异常也是异步抛出的。默认下,对域操作中的所有错误都抛出一个异常。RIA Services为我们提供了处理错误的方式,并且可以指定框架不抛出异常。

当装载数据时处理错误
当从一个查询装载数据时,我们可以选择处理错误或忽略之。明确地,我们从如下选项中选择:

1. 使用带有回调方法做为参数的Load方法。在回调方法内,处理错误,并调用MarkErrorAsHandled方法来指定不要抛出错误。

2. 使用带一个名为thowOnError的Boolean参数的Load方法。当调用Load方法时,设置throwOnError为false来指定我们不想为查询错误抛出异常。

3. 当Load方法没有回调参数和布尔参数时,任何查询错误都会导致一个未处理的异常。
下面的示例演示了如何从查询载入数据,并指定一个回调方法来检测装载操作中的错误。

private CustomerDomainContext _customerContext = new CustomerDomainContext();

public MainPage()
{
InitializeComponent();

LoadOperation loadOp = this._customerContext.Load(this._customerContext.GetCustomersQuery(), OnLoadCompleted, null);
CustomerGrid.ItemsSource = loadOp.Entities;
}

private void OnLoadCompleted(LoadOperation lo)
{
if (lo.HasError)
{
MessageBox.Show(string.Format("Retrieving data failed: {0}", lo.Error.Message));
lo.MarkErrorAsHandled();
}
}


提交数据时处理错误
当提交数据时,我们不能像Load方法那样选择关闭异常。所有提交数据时发生的错误都会导致一个异常。明确地,我们可以选择如下选项:

1. 使用带回调参数的SubmitChanges方法。在回调方法里,处理错误并调用MarkErrorAsHandled方法来指定不要抛出异常。

2. 使用SubmitChanges方法,所有在提交数据时发生的错误都导致一个异常。

下面的示例演示了如何调用带有回调参数的SubmitChanges方法。

private void SaveButton_Click(object sender, RoutedEventArgs e)
{
_customerContext.SubmitChanges(OnSubmitCompleted, null);
}

private void RejectButton_Click(object sender, RoutedEventArgs e)
{
_customerContext.RejectChanges();
CheckChanges();
}

private void CustomerGrid_RowEditEnded(object sender, DataGridRowEditEndedEventArgs e)
{
CheckChanges();
}

private void CheckChanges()
{
EntityChangeSet changeSet = _customerContext.EntityContainer.GetChanges();
ChangeText.Text = changeSet.ToString();

bool hasChanges = _customerContext.HasChanges;
SaveButton.IsEnabled = hasChanges;
RejectButton.IsEnabled = hasChanges;
}

private void OnSubmitCompleted(SubmitOperation so)
{
if (so.HasError)
{
MessageBox.Show(string.Format("Submit Failed: {0}", so.Error.Message));
so.MarkErrorAsHandled();
}
CheckChanges();
}


调用操作时处理错误
当调用一个操作时,我们有像提交数据时一样的选择。如下:

1. 当调用操作时包含一个回调参数。在回调参数内处理错误,并调用MarkErrorAsHandled方法来指定不要抛出异常。

2. 调用不包含回调参数的操作。所有在调用操作时发生的错误都会抛出异常。


InvokeOperation invokeOp = customerContext.GetLocalTemperature(selectedPostalCode, OnInvokeCompleted, null);

private void OnInvokeCompleted(InvokeOperation invOp)
{
if (invOp.HasError)
{
MessageBox.Show(string.Format("Method Failed: {0}", invOp.Error.Message));
invOp.MarkErrorAsHandled();
}
else
{
result = invokeOp.Value;
}
}


验证服务处理错误
AuthenticationService类允许我们在调用下面的方法时提供回调参数:
1.LoadUser 2.Login 3.Logout 4.SaveUser
在回调方法中,我们可以提供代码来处置从验证服务中来的错误。 下面的例子演示如何从登陆按钮的事件处理中调用Login方法。

private void LoginButton_Click(object sender, RoutedEventArgs e)
{
LoginParameters lp = new LoginParameters(UserName.Text, Password.Password);
WebContext.Current.Authentication.Login(lp, this.LoginOperation_Completed, null);
LoginButton.IsEnabled = false;
LoginResult.Text = "";
}

private void LoginOperation_Completed(LoginOperation lo)
{
if (lo.HasError)
{
LoginResult.Text = lo.Error.Message;
LoginResult.Visibility = System.Windows.Visibility.Visible;
lo.MarkErrorAsHandled();
}
else if (lo.LoginSuccess == false)
{
LoginResult.Text = "Login failed. Please check user name and password.";
LoginResult.Visibility = System.Windows.Visibility.Visible;
}
else if (lo.LoginSuccess == true)
{
SetControlVisibility(true);
}
LoginButton.IsEnabled = true;
}

2010年2月5日星期五

WCF RIA 服务 (二十三)-- Silverlight 客户端 4

DomainDataSource
WCF RIA Services提供DomainDataSource控件来简化用户界面和域上下文中数据的交互。通过DomainDataSource,我们可以只是用声明性语法来检索、编辑数据。我们指定域上下文与DomainDataSource一起使用,然后通过这个上下文来调用操作。
配置Silverlight应用程序的DomainDataSource
为了使用DomainDataSource控件,我们必须在包含DomainDataSource的SL控件中添加一个程序集的引用和命名空间。
在SL项目中,我们必须添加一个对System.Windows.Controls.Ria程序集的引用。如果选择DataGrid与DomainDataSource一起使用,我们还要添加对System.Windows.Controls.Data的引用。
在宿主控件内,例如UserControl,我们必须添加下面的命名空间引用:

xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Ria"
xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Controls.Ria"
xmlns:domain="clr-namespace:SilverlightApplication17.Web"

如果选择使用DataGrid控件,还需添加下面的命名空间:

xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

检索和显示数据
我们为DomainDataSource指定一个域上下文,并向用户提供方法的名字来装载数据。然后我们绑定表示控件,例如DataGrid对DomainDataSource。下面的例子演示了DomainDataSource检索从名为ProductDomainContext的域上下文而来的数据。在域服务中应该存在一个名为GetProduct()的查询方法。

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Ria"
xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Controls.Ria"
xmlns:domain="clr-namespace:SilverlightApplication17.Web"
xmlns:datac="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d">













对查询添加参数
某些情况下,查询方法需要参数值。通常,一个查询方法需要一个参数值来过滤返回的数据。下面的示例演示如何添加参数值,这个值可通过声明性文本来指定。













还可以使用来自用户的值来为查询添加参数。我们使用ControlParameter对象来把来自用户输入控件的值传递到查询。下面的例子展示了如何指定来自下拉菜单中的值做为参数值。











ParameterName="color"
ControlName="colorCombo"
PropertyName="SelectedItem.Content"
RefreshEventName="SelectionChanged" />









排序
DomainDataSource提供SortDescriptors集合来简化数据的排序。在SortDescriptors集合中,我们提供SortDescriptor实例来向用户描述参数值。我们可以添加多个SortDescriptor实例。还可以指定数据排序的升降序。下面的示例演示DomainDataSource中的排序描述符,从查询中检索的数据按来自StandardPrice属性中的值排序。













分组
DomainDataSource提供了GroupDescriptors集合来通过属性值来简化分组数据。在GroupDescriptors集合中,我们提供GroupDescriptor示例来定义用来分组的值。可以添加多个GroupDescriptors实例。













过滤
DomainDataSource控件提供FilterDescriptors集合来允许我们过滤查询返回的数据。通过添加过滤,我们可以指定只装载满足条件的域上下文实体。在FIlterDescriptorCollection对象上设置LogicalOperator属性,我们可以在不同的过滤之间定义逻辑关系。
过滤描述符通过FilterOperator枚举器来支持操作。
当定义基于用户输入的过滤时,我们可以提供ControlParameter实例。下面的示例演示了两个通过逻辑AND连接的过滤描述符。一个过滤依赖于用户的输入,另一个过滤通过声明性语句来指定。














ControlName="MaxPrice"
PropertyName="SelectedItem.Content"
RefreshEventName="SelectionChanged" />












分页
当显示大量实体的时候,我们会希望在用户界面上提供分页功能。DomainDataSource控件允许我们指定在一个页面上装载和显现的实体数量。新纪录只有在用户导航到了还没有装载实体的页面时才装载。我们设置PageSize和LoadSize属性来指定分页的参数。然后,我们把一个DataPage实例绑定到DomainDataSource来实施分页的接口。
注意:如果在应用程序中DataPager与实体框架数据存贮一起使用,我们必须对为DataPager从查询返回的数据进行排序。因为实体框架不支持没有OrderBy子句或在SL客户端没指定排序的分页。


















编辑
进行数据更改,需要调用DomainDataSource对象的SubmitChanges方法。要取消更改,调用RejectChanges方法。

WCF RIA 服务 (二十二)-- Silverlight 客户端 3

DomainContext
从客户端项目内部,我们不直接与域服务交互。相反,会对服务端中的每个域服务在客户端生成一个域上下文类。我们在域上下文类上调用对应于域服务上想使用的方法。这个生成的域上下文类派生于DomainContext类。
查询
域上下文中的查询方法通常与域服务中的查询方法有相同的名字,并有后缀Query。例如,一个域上下文中的GetCustomersQuery方法生成于域服务中的GetCustomers方法。这个查询方法返回一个EntityQuery对象,我们可以在其他操作中应用这个对象。
域上下文中的所有查询方法都是异步执行的。为了执行这个查询,我们在Load方法中把EntityQuery对象作为参数传递。
修改数据
当域服务中包含更新、插入、删除实体的方法时,在域上下文中不会生成这些方法。反之,我们在与上下文中使用SubmitChanges方法,会调用域服务中的正确操作。直到我们调用SubmitChanges时,才会在数据源中进行更改。可以通过调用RejectChanges方法来取消更改。
DomainContext类还提供HasChanges和EntityContainer属性来允许我们评估行将发生的更改。对Domain context的EntityContainer对象跟踪行将发生的更改。行将发生的更改不包括对域服务中的操作的调用,因为这些操作会在被调用时立即执行。当调用SubmitChanges时,所有的行将发生的更改都一起发到域服务。
定制方法
对域服务中的那些有公共访问修饰符而且没有标记着IgnoreOperationAttribute属性的自定义方法,域上下文都会包含这些方法。域上下文中的这些方法的名字与域服务中的方法名字一样。在客户端,我们调用一个方法,直到SubmitChanges被调用时,这个方法才会被实际执行。EntityContainer会将所有对自定义方法的调用作为行将发生的更改来跟踪。当调用SubmitChanges时,方法是异步处理的。
在客户端项目中对实体也会生成同样的自定义方法,这些实体在自定义方法中做为参数传递。因此,可以通过一个域上下文的实例或实体的实例来调用定制的方法。如果打开生成的代码文件,会注意到域上下文中生成的方法只是简单的调用实体内的生成方法。在这两种情况下,我们还是需要调用Submitchanges方法来执行这些方法。
下面的示例演示如何调用一个名为ResetPassword的自定义方法,OnSubmitCompleted是我们想实施处理数据操作结果的回调函数。

selectedCustomer.ResetPassword();
customerContext.SubmitChanges(OnSubmitCompleted, null);

Invoke Operations 调用操作
对域服务上的每个服务操作,域上下文都包含一个方法。与域操作不同,服务操作都会立即执行。不用调用SubmitChanges方法,服务操作会异步执行。服务操作返回一个InvokeOperation对象。我们检索Value属性的值来得到从服务操作中返回的结果。
下面的示例演示了如果调用一个名为GetLocalTemperature的服务操作。

InvokeOperation invokeOp = customerContext.GetLocalTemperature(selectedPostalCode, OnInvokeCompleted, null);

private void OnInvokeCompleted(InvokeOperation invOp)
{
if (invOp.HasError)
{
MessageBox.Show(string.Format("Method Failed: {0}", invOp.Error.Message));
invOp.MarkErrorAsHandled();
}
else
{
result = invokeOp.Value;
}
}

处理错误
当我们检索或修改数据时,我们必须决定如果处理在这些操作中可能出现的错误。当我们在域上下文上调用检索或修改数据的方法时,我们包含了指定处理错误的步骤的参数。当装载数据时,我们可以指定忽略错误。但在修改数据时,我们必须处理返回的异常。更多详情,请看Silverlight 客户端 5.

WCF RIA 服务 (二十一)-- Silverlight 客户端 2

客户端代码生成
当我们使用RIA Services连接中间层和表示层时,RIA Services为客户端项目生成了客户端代理类,这些类是以中间层公开的实体和操作为基础的。因为RIA Services生成了这些类,所以我们不必再复制这些中间层和表示层中的应用逻辑。因为我们对中间层所做的任何修改,在重新生成客户端项目时都会自动与表示层同步的。

生成的代码位于客户端项目的Generated_Code文件夹内。想要看到这个文件夹,必须在客户端项目的解决方案资源管理器的窗口下,选择“显示所有文件”。我们不应直接改动此文件夹内的类,因为在客户端项目重新生成时,文件会被覆盖。然而,我们可以打开生成的文件,查看里面可被客户端项目利用的代码。


生成客户端代码的算法遵循以下规则:

1. 分析所有的程序集,包括生成的,或中间层为Domain service classes,Entity classes,shared code所引用的。

2. 对每个带有EnableClientAccessAttribute属性注释的域服务,都生成一个从DomainContext类派生的类。

3. 对每个域服务类中的查询方法、命名的更新方法或调用的操作,都在域上下文类中生成一个方法。

4. 对每个域服务公开的实体类,生成一个实体代理类。

5. 拷贝标记为共享的代码到客户端项目。

下图显示了一个中间层项目生成的客户端代码


DomainService 和 DomainContext

为每个域服务生成的派生于Domaincontext的类,遵循下面的规则:

1. Domain Context类须与Domain service具有一样的命名空间。

2. domain context类包含3个构造函数:

a. 默认的构造函数,嵌入了必要的URI,使用WebDomainClient(TContract)类在http上与域服务沟通

b. 允许用户指定一个可交替的URI的构造函数。

c. 允许用户提供一个自定义实现的DomainClient的构造函数。

3. 对每个与服务类中的查询方法,在domain context中都会生成一个EntityQuery(TEntity)方法。

4. 对每个命名的更新方法(named update method)或调用的操作,在域上下文中都会生成对应的方法。

5. 在domain service中执行插入、更新、删除的公共方法,会使域上下文中的EntityContainer在生成时带有EntitySetOperations标记,这个标记指定哪个操作在客户端是允许的。

实体类和实体代理类

生成实体代理类时,应遵循如下规则:

1. 代理类应该与中间层中的实体类具有一样的名字和命名空间。

2. 实体类中所有公共成员属性都会在代理类中生成,除非某些类型或成员属性已经在客户端项目中存在。

3. 每个属性设置器都包含执行验证和通知客户属性正在改变和已经改变的代码。

4. 元数据属性是与生成的代码中的实体类连在一起的。客户端不会存在元数据类。

5. 如果可能,定制的属性会传递到代理类。详情参阅下面的内容

定制的属性

如果定制的属性在客户端项目的编译中没有出现错误,那么这个定制的属性就可以传递到代理类中。为了保证属性能被传递,应满足如下条件:

1. 在客户端提供定制的属性的类型。

2. 在定制属性中指定的所有类型,在客户端都应该提供。

3. 这个定制属性必须对它所有的成员属性公开公共设置器,或者公开一个可以设置那些没有公共设置器的成员属性的构造函数。

如果所需的定制属性没有传递到客户端,我们可能需要在客户端中添加一个程序集引用。

共享代码

当在中间层和表示层之间共享代码时,代码被原封不动的拷到客户端项目中。通过命名文件为*.shared.cs来共享文件。

避免成员重复

当生成实体代理类时,有可能在客户端项目中已经定义了相同的类型和成员。我们可能已经在共享代码或只存在于客户端项目中的代码中定义了成员。RIA Service会在生成代理类之前检查已经存在的成员。所有已经存在的成员都不在代理类中生成。

WCF RIA 服务 (二十)-- Silverlight 客户端

Silverlight客户端
使用WCF RIA Services,我们可以创建一个当数据交互时知道中间层应用逻辑的Silverlight客户端。还可以对可见的和可编辑的数据提供用户接口,来在提交数据修改之前应用验证规则。我们的SL控件将会使用从中间层代码自动生成类。次章节介绍SL客户端如何使用domain context, 如何与数据一起工作,以及如何自定义生成的代码.
使用DomainContext
在中间层项目中会对每个domain service都生成一个DomainContext类来公开实体对象。在域上下文中包含着查询和修改等方法,这些方法与在域服务中对应的域操作进行沟通。当我们在SL应用程序中调用域上下文类上的一个查询方法时,这个查询方法会调用返回所需数据的域服务上的对应方法。这些域上下文上的方法都是异步执行的,所以在装载数据的时候,用户界面不会被锁定。
呈现和修改数据
我们使用SL控件,例如DataGrid控件,来呈现通过域上下文检索到的数据。我们把控件和查询结果绑定在一起。
我们也可以通过SL控件更新、插入和修改数据,当这些操作在域服务中公开时。当我们调用域服务上的数据修改操作时,中间层逻辑会处理来自SL客户端的数据,以确保商业规则应用到了修改操作上。
我们还可以使用DomainDataSource控件与来自域服务的数据交互。DomainDataSource控件允许我们使用声明语法来指定分页、排序、分组、和过滤数据。
自定义生成的代码
要想自定义生成的代码,我们不应该修改Generated_Code文件夹下的文件。因为当客户端项目重新生成时,这些文件将会被覆盖。反之,通过提供在域上下文中的局部方法和实体代理类,RIA Services允许我们为客户端自定义生成的代码。通过这些局部方法,我们可以在客户端添加计算性能,或添加当特定动作执行时需要的自定义逻辑。只有在我们已经实施了局部方法的情况下,生成的局部方法在会在运行时被调用。

2010年2月4日星期四

WCF RIA 服务 (十九)-- 共享代码 2

如何:通过源文件来共享代码

我们可以通过源文件来在中间层和表示层之间共享代码。当在中间层修改源文件,并重新生成应用程序时,在客户端会自动同步更新了的代码。当代码不属于domain Service或实体类,而且我们不想代码经历客户端代码生成步骤时,我们把代码放在共享的源文件中。

通过使用共享命名规范或文件链接来共享源文件。下面示例使用这两种方法来共享源文件。

使用共享命名规范


  1. 在中间层的解决方案资源管理器中,创建想放置共享文件的目录结构。这个目录结构将会在客户端的Generated_Code文件夹内重新创建。

  2. 在目录结构中添加一个C#文件。

  3. 使用共享命名规范。

  4. 在共享文件中添加代码。

  5. 生成解决方案。

  6. 在客户端项目中,打开Generated_Code文件夹,注意到代码文件已经被拷贝了。



  7. 打开代码文件,注意到在编译期间没有对代码做改动。

使用文件链接



  1. 在中间层项目中,添加一个新的代码文件。

  2. 在文件中添加代码。

  3. 在表示层项目中,右键点击项目。选择添加->已存在项

  4. 选择刚添加到中间层项目中的代码文件。

  5. 在添加按钮上,点击下拉菜单并选择Add As Link。

当编译这个项目时,会返现没有文件被拷贝到客户端项目中。相反,添加了一个文件引用。

WCF RIA 服务 (十八)-- 共享代码 1

共享代码
WCF RIA Services允许我们编写在中间层和表示层之间共享的应用逻辑。可以共享源文件或程序集中的代码。和在客户端代码生成主题中描述的自动代码生成方法不同,共享代码在编译时时不能改动的。相反,代码在层间是逐字地拷贝或共享的。

共享源文件

可以在中间层添加源文件,然后明确指定这些文件在表示层中共享。有两种方法来在层之间共享源文件。第一种方式是根据命名规范来命名源文件:*.shared.cs或*.shared.vb。第二种方法是使用VS中的文件链接特性。

共享命名规范

当对源文件使用共享命名规范(*.shared.cs)时,对共享的源代码文件实施了"push"模型。在编译时,源文件将会从中间层拷贝到客户端。只有在服务端和客户端存在RIA Services 连接时,共享命名规范才会对源文件起作用。



共享命名规范有以下优点

1.内置的支持:不需要程序员来保证共享文件同步。

2.透明性:名字清楚指明了文件想要共享。

3.自我维护:如果添加了一个共享文件,当解决方案编译时,所有与这个中间层联系的客户端项目都会自动更新。

4.易理解的调试经验:可以在服务端或客户端的文件中设置断点。

但它也有如下缺点:

1.新概念: 程序员必须了解共享命名规范。

2.文件被宝贝:共享文件是物理性地拷贝到项目中的,就可能造成程序员编辑了拷贝的文件,却在下次编译时丢失了所做的更改(因为源文件没有更改)。

链接文件

Linked files是VS中已有的特性,并不是只针对RIA Services的。在项目之间可以存在RIA Services链接,但不必使用链接文件。当使用链接文件方法时,就对共享源文件实施了"pull"模型。在客户端没有文件的拷贝。相反,客户端引用服务端的文件。



我们还可以让服务端和客户端都连接到其他项目中的文件。



链接文件方式有以下优点:

1. 已存在的VS特性:不必学习新的规范。

2. 文件不是拷贝的:文件仅存在服务端项目中。因此不可以修改拷贝的文件,在下次解决方案编译时也不会丢失更改。

链接文件方式的缺点:

1. 需要用户明确的指令 : 必须链接每个共享文件。

2. 不能自我维护 :当添加或移除共享文件时,客户端需要更新。

3. 缺乏透明性 : 程序员需要检查文件来确定哪个文件是共享的。

4. 不好的调试体验 : 不容易确定哪个层引起了中断。

共享程序集 - Shared Assemblies

与在项目中共享源文件相反,我们可以报代码编译到类库中,然后再通过程序集引用来共享这个类库。使用WCF RIA Services 类库来确保程序集是兼容的。

下面的图示例了一个使用WCF RIA Services类库的n层结构的应用程序如何共享代码。中间层和客户层对类库使用程序集引用。

2010年1月24日星期日

WCF RIA 服务 (十七)- 数据 7

数据模型中的继承
WCF RIA Services允许我们使用做为继承体系中的一部分的实体。一个继承模型包含了一个从其他数据类派生的数据类。例如,一个多态继承模型可以包含一个Customer实体和两个从Customer派生的实体(PublicSectorCustomer和PrivateSectorCustomer)。通过RIA Services,我们可以在domain Services中写一个返回一个根类型的集合和从根类型派生的类型的查询方法。或者,可以写一个仅返回派生类型集合的查询方法。还可以写修改根类型或任何派生类的操作方法。
注:只在VS2010和SL4中使用RIA Services时支持继承,在VS2008和SL3中不支持。

数据模型
在服务端项目中,我们可以像定义其他数据类那样来为继承模型定义数据类。使用的这个对象模型既可以是从数据层中自动生成的类也可以是手动创建的数据类。
我们不必非要通过domain service来公开整个层次。相反,在domain service中公开的层次中最后派生的类,是与客户端交互的根类。从根类型派生出的类型可以向客户端公开,但父类型不必被公开。在根类中,必须把想要公开的的任意派生类型包含在KnownTypeAttribute属性中。如果想要忽略派生类型,可以不把它包含在KnownTypeAttribute属性中。下面示例了一个包含基类Customer,和两个派生类PrivateSectorCustomer,PublicSectorCustomer的手工创建的数据模型。两个派生类会包含在Customer类的KnownTypeAttribute属性下,因为Customer是数据操作的根类型。

[KnownType(typeof(PublicSectorCustomer)), KnownType(typeof(PrivateSectorCustomer))]
public class Customer
{
[Key]
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string StateProvince { get; set; }
public string PostalCode { get; set; }
[Association("CustomerOrders", "CustomerID", "CustomerID")]
public List Orders { get; set; }
}

public class PublicSectorCustomer : Customer
{
public string GSARegion { get; set; }
}

public class PrivateSectorCustomer : Customer
{
public string CompanyName { get; set; }
}

多态查询
定义了数据模型后,我们创建一个对客户端公开类型的domain service。当在一个查询方法中公开一个类型时,可以返回这个类型和任意派生的类型。例如,一个返回Customer实体集合的查询可以包含一个PrivateSectorCustomer对象和PublicSectorCustomer对象。还可以指定一个只返回一个派生类型的查询方法。下面示例了返回不同类型的查询方法。

public IQueryable GetCustomers()
{
return context.Customers;
}

public IQueryable GetCustomersByState(string state)
{
return context.Customers.Where(c => c.StateProvince == state);
}

public IQueryable GetCustomersByGSARegion(string region)
{
return context.Customers.OfType().Where(c => c.GSARegion == region);
}

public IQueryable GetPrivateSectorByPostalCode(string postalcode)
{
return context.Customers.OfType().Where(c => c.PostalCode == postalcode);
}

为客户端项目生成代码
当生成解决方案时,在客户端会为已经在domain service中公开的继承体系生成代码。体系的根类被生成并派生于Entity类。每个派生的类都是生成和派生于根类。在DomainContext类中,只生成一个Entity(TEntity)成员属性,并且它需要根类型的对象。为每一个查询都生成了EntityQuery对象,它返回在domain service操作中指定的类型。
下面示例了一个在客户端为查询方法生成的简单版本的代码。它并没有包含所有生成类中的代码,只是想强调一些重要的成员属性和方法。

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
[KnownType(typeof(PrivateSectorCustomer))]
[KnownType(typeof(PublicSectorCustomer))]
public partial class Customer : Entity
{
public string Address { get; set; }
public string City { get; set; }
[Key()]
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PostalCode { get; set; }
public string StateProvince { get; set; }

public override object GetIdentity();

}

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
public sealed partial class PrivateSectorCustomer : Customer
{
public string CompanyName { get; set; }
}

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
public sealed partial class PublicSectorCustomer : Customer
{
public string GSARegion { get; set; }
}

public sealed partial class CustomerDomainContext : DomainContext
{
public CustomerDomainContext();
public CustomerDomainContext(Uri serviceUri);
public CustomerDomainContext(DomainClient domainClient);

public EntitySet Customers { get; }

public EntityQuery GetCustomersQuery();
public EntityQuery GetCustomersByGSARegionQuery(string region);
public EntityQuery GetCustomersByStateQuery(string state);
public EntityQuery GetPrivateSectorByPostalCodeQuery(string postalcode);
}

数据修改
可以为继承体系中的更新、插入和删除对象添加domain service方法。如同查询方法,可以为操作指定一个根类型或一个派生类型。然而,任何对派生类的更新、插入或删除操作也都应该可以在根类型中执行。可以在体系中为任何类型添加named updat
方法。会为在方法中指定的类型在客户端生成相应的named update方法。
下面的代码示例了两个更新方法和一个named update方法的签名。

public void UpdateCustomer(Customer customer) { /* implement */ }
public void UpdatePublicSectorCustomer(PublicSectorCustomer customer) { /* implement */ }
public void EnrollInRewardsProgram(PrivateSectorCustomer customer) { /* implement */ }

关联
可以在根类或派生类中定义关联。我们在练歌数据类之间应用AssociationAttribute属性来定义一个关联。在数据模型的例子中,在Customer和Order之间定义了一个关联。如果对根类型应用了关联,那么所有的派生类型也包含这个关联。
使用继承的基本规则


  • 仅对实体类型支持继承,非实体类型被当做在domain service操作签名中指定的类型。
  • 在domain service操作中的返回值和参数,不支持接口类型。
  • 在代码生成的时候必须知道继承体系中的类型集。在生成代码的时候没有指定返回类型的行为是未指明和实现相关的。
  • 允许对实体类型的公共成员属性或字段使用virtual或new。但在客户端对应生成的实体类型中会被忽略。
  • 不允许对domain service操作方法重载。
  • 与继承相关的LINQ查询功能不能用来运行domain service方法。特别地,不支持OfType>T<,is,as和GetType()操作符和方法。然而,在LINQ to Objects查询中的EntitySet或EntityCollection(TEntity)上直接使用这些操作符。

实体继承体系

定义继承体系使用下面的规则:

  • 使用System.Runtime.Serialization.KnownTypeAttribute属性来指定已知的类型。对RIA Services来说,需要使用包含一个System.Type参数并具有KnownTypeAttribute的构造函数。
  • 体系中的已知类型,必须在domain service公开的体系中的根类型中指定。RIA Services 不支持在派生类型上声明类型为已知。
  • 每个在已知类型集中的类都必须是public。
  • 在体系中的一个或多个类可以是abstract的。
  • 当声明已知类型时,可以在体系中省略一个或多个类。
  • 必须在根类中指定关键成员。
  • 关联的声明和使用是不可改变的。

DomainService操作

在继承体系中使用下面规则定义domain service 操作

  • 在体系中至少有一个对应于根类型的查询方法。其他的查询操作可以使用派生类型作为返回值。
  • 对返回多态结果的方法,查询方法可以返回一个根类型。
  • 如果对系统内的类型定义了更新、插入、或删除操作,那么对根类型也要定义相同的操作。不可能只对某些类型选择操作。
  • 自定义操作可以使用根类型或派生类型作为实体参数。当实例的实际类型是从自定义操作中的类型派生的时候,操作是可行的。
  • DomainServiceDescription类返回对给定类型最适用的更新、插入、删除和查询方法。

TpyeDescriptionProvider

下面的规则应用于TypeDescriptionProvider(TDP)

  • 当体系中的根类型通过一个查询方法或IncludeAttribute属性公开时,会对LINQ to SQL应用TpyeDescriptionProvider,并且实体框架会自动对实体使用KnownType属性声明。而对派生类型则不会如此。
  • 在Add New Domain Service Class对话框中不会提供任何继承体系的功能。用户可以在体系中选择一些、全部或不选择类型。

生成的代码

对体系中的实体,以下的规则应用于客户端的生成代码

  • 对每个继承体系,只生成一个EntitySet(TEntity)类。在已知的体系中EntitySet(TEntity)的类型参数是最基本的类型。
  • 对体系中每个已知的类型,都对应生成一个实体类型。即使没有domain service方法公开这个类型,这个实体类型也会生成。
  • 对指定的类型可生成自定义方法,并这些方法可用于任何派生类型。
  • 构造函数是根据继承体系来绑定的。每个类型都可以调用OnCreated方法并可以自定义此方法。
  • 如果服务端的实体体系中的类被跳过,那么在客户端生成的实体体系中也会被跳过。

WCF RIA 服务 (十六)- 表示模型

WCF RIA Services允许我们创建数据模型来综合从数据访问层得到的不同实体数据。这个模型就是表示模型。当我们不想把数据层的数据直接公开给客户端时,会使用这个特性。当使用表示模型时,可以只修改表示模型而不是客户端来回应数据访问层中的改动。还可以设计一个综合那些仅与客户端用户相关的字段的模型,来简化客户端代码。
创建表示模型
需要用来维护数据完整性的数据库结构可能会比在客户端应用中需要的那些实体更复杂。我们可以通过把那些与应用相关的字段组合进一个表示模型,来简化这个数据结构。例如,在AdventureWorksLT示例数据库中,我们通过Customer,CustomerAddress,Address表来检索客户和地址数据。
通过在服务端项目中创建一个类来创建表示模型,并定义需要的成员属性。这些定义的成员属性对应着你想从实体公开的成员属性。例如下面的示例,在服务端创建一个类,来表示那些从Customer、CustomerAddress、Address表中仅仅需要的字段。
在表示模型中查询和修改数据
在创建了表示模型后,通过添加一个和这个表示类型交互的domain service来向客户端公开。下面的示例展示了一个从DomainService类派生的域服务。

[EnableClientAccess()]
public class CustomerDomainService : DomainService
{
AdventureWorksLT_DataEntities context = new AdventureWorksLT_DataEntities();
}

为了检索数据,我们在Domain service中添加了一个query方法。在query方法中,从数据访问层中的实体检索相关的数据,并把这些值赋值给新的表示模型实例中对应的成员属性。从这个query方法,要么返回一个这个表示模型类型的实例,要么返回一个表示模型类型的IQueryable。下面示例了Customer表示模型的查询方法。

public IQueryable GetCustomersWithMainOffice()
{
return from c in context.Customers
join ca in context.CustomerAddresses on c.CustomerID equals ca.CustomerID
join a in context.Addresses on ca.AddressID equals a.AddressID
where ca.AddressType == "Main Office"
select new CustomerPresentationModel()
{
CustomerID = c.CustomerID,
FirstName = c.FirstName,
LastName = c.LastName,
EmailAddress = c.EmailAddress,
Phone = c.Phone,
AddressType = ca.AddressType,
AddressLine1 = a.AddressLine1,
AddressLine2 = a.AddressLine2,
City = a.City,
StateProvince = a.StateProvince,
PostalCode = a.PostalCode,
AddressID = a.AddressID,
AddressModifiedDate = a.ModifiedDate,
CustomerModifiedDate = c.ModifiedDate
};
}

由于在数据访问层中并没有通过domain service公开实体(Customer、CustomerAddress、Address),所以不会在客户端生成这些类型。相反,在客户端仅生成表示模型类型。
如果想通过表示模型更新数据,我们需创建一个更新方法,并定义从表示模型向实体存贮值的逻辑。可以参考本节最后的示例。
把值返回给客户端
提交了更改后,我们可能需要把存贮到中间层逻辑或数据源中的值传回客户端。WCF RIA Services提供了Associate(TEntity,TStoreEntity)方法来映射从实体回到表示模型的值。在这个方法中,我们提供一个在提交后调用的回调方法。在这个回调方法中,我们把在中间层已改动的值赋值给表示模型。通过执行这个步骤来使客户端持有当前值。
下面是示例演示了如何更新实体中的值,并如何把修改后的数据映射回表示模型。

[Update]
public void UpdateCustomer(CustomerPresentationModel customerPM)
{
Customer customerEntity = context.Customers.Where(c => c.CustomerID == customerPM.CustomerID).FirstOrDefault();
CustomerAddress customerAddressEntity = context.CustomerAddresses.Where(ca => ca.CustomerID == customerPM.CustomerID && ca.AddressID == customerPM.AddressID).FirstOrDefault();
Address addressEntity = context.Addresses.Where(a => a.AddressID == customerPM.AddressID).FirstOrDefault();

customerEntity.FirstName = customerPM.FirstName;
customerEntity.LastName = customerPM.LastName;
customerEntity.EmailAddress = customerPM.EmailAddress;
customerEntity.Phone = customerPM.Phone;
customerAddressEntity.AddressType = customerPM.AddressType;
addressEntity.AddressLine1 = customerPM.AddressLine1;
addressEntity.AddressLine2 = customerPM.AddressLine2;
addressEntity.City = customerPM.City;
addressEntity.StateProvince = customerPM.StateProvince;
addressEntity.PostalCode = customerPM.PostalCode;

CustomerPresentationModel originalValues = this.ChangeSet.GetOriginal(customerPM);

if (originalValues.FirstName != customerPM.FirstName ||
originalValues.LastName != customerPM.LastName ||
originalValues.EmailAddress != customerPM.EmailAddress ||
originalValues.Phone != customerPM.Phone)
{
customerEntity.ModifiedDate = DateTime.Now;
}

if (originalValues.AddressLine1 != customerPM.AddressLine1 ||
originalValues.AddressLine2 != customerPM.AddressLine2 ||
originalValues.City != customerPM.City ||
originalValues.StateProvince != customerPM.StateProvince ||
originalValues.PostalCode != customerPM.PostalCode)
{
addressEntity.ModifiedDate = DateTime.Now;
}

context.SaveChanges();

this.ChangeSet.Associate(customerPM, customerEntity, MapCustomerToCustomerPM);
this.ChangeSet.Associate(customerPM, addressEntity, MapAddressToCustomerPM);
}

private void MapCustomerToCustomerPM(CustomerPresentationModel customerPM, Customer customerEntity)
{
customerPM.CustomerModifiedDate = customerEntity.ModifiedDate;
}

private void MapAddressToCustomerPM(CustomerPresentationModel customerPM, Address addressEntity)
{
customerPM.AddressModifiedDate = addressEntity.ModifiedDate;
}

2010年1月22日星期五

WCF RIA 服务 (十五)- 数据 5

层的组成
WCF RIA Services允许我们为具有层次概念的数据类创建应用逻辑,例如SalesOrderHeader实体和SalesOrderDetail实体。这样相关实体就组成了所谓的层次。定义了类之间的组成关系后,就可以像操作一个单一个体一样来操作对实体的数据修改,而不是像操作独立实体那样。这就会简化中间层的逻辑,因为我们可以对整个实体层来写应用逻辑,而不是把逻辑拆分对应每个实体并在数据操作时企图协调这些拆分的逻辑。
了解层的概念
在实体的层级概念中,一个实体被称为父实体,其他关联的实体被称为子实体。父实体是表示数据的类,是那些子实体数据的的根。例如,SalesOrderHeader实体是父实体,SalesOrderDetail是子实体。SalesOrderHeader实体内的一个记录可以和SalesOrderDetail实体内的多个记录连接起来。
作为层次关系一部分的数据类通常具有如下特征:


  • 这些实体间的关系可以表示为一个有子实体和一个父实体的树型结构。子实体可以扩展为任何数量的级别。
  • 子实体的生命周期是包含在父实体的生命周期内。
  • 子实体如果离开了父实体的上下文,就没有了有意义的身份。
  • 需要把实体看做单一个体来对实体进行数据操作。例如,添加、删除、或更新子实体内的一个记录,需要在父实体内也有相应的改动。

定义一个组成关系

要在实体间定义层次关系,可以对在实体间表示关联的成员属性应用CompositionAttribut属性。下面的示例展示了如何通过元数据类来在SalesOrderHeader和SalesOrderDetail之间定义层次关系。CompositionAttribute属性在System.Web.Ria.Data命名空间内。需要用using来引用这个命名空间。


[MetadataTypeAttribute(typeof(SalesOrderHeader.SalesOrderHeaderMetadata))]
public partial class SalesOrderHeader
{
internal sealed class SalesOrderHeaderMetadata
{
private SalesOrderHeaderMetadata()
{
}

[Include]
[Composition]
public EntitySet SalesOrderDetails;

}
}

当对成员属性应用CompositionAttribute属性时,子实体内的数据不能从父实体自动寻回。为了在查询结果中包含子实体,应该对表示子实体的成员属性应用IncludeAttribute属性,并在查询方法中包括子实体。可以参考最后的例子。

Domain Service操作组合的层次关系

当定义了组合层次后,需要改变父实体和子实体交互的方式。包含在Domain Service中的逻辑必须解释实体间的联系。通常,通过对父实体的Domain Service方法来定义层次的逻辑。在对父实体的Domain Service操作里,处理对父实体和子实体的更改。

下面的规则应用于Domain Service操作来操作有层次关系的实体。

  • 允许对父和子实体使用查询方法,但推荐在父实体的上下文内查询子实体。如果修改没有通过父实体装载的子实体会抛出一个异常
  • 可以添加对子实体的数据修改操作,但这些操作可以被父实体内的操作影响。

    • 如果允许对父实体更改,那么更改、插入、和删除操作被允许在子实体上。


    • 如果父实体有一个命名的更新named updated方法,那么所有子实体都必须允许更新操作。


    • 如果在父实体允许插入、删除,那么在子实体也应该允许对应的操纵。


在客户端,对有层次关系的实体应用下面的规则。

  • 当子实体包含一个改动,这个改动要通知给父实体。父实体上的HasChanges应设置为true。
  • 当一个父实体被改动,所有的子实体(包括没有改动的子实体)都被包括在改动集合中。
  • 在客户端的子实体中不会在Domain上下文中生成public的EntitySet,必须通过父实体来访问子实体。
  • 一个子实体可以在相同的级别上有多个始祖,但必须确认它只能在一个始祖的上下文中装载。

数据更改的操作按以下的规则运行。

  • 在递归执行任何子实体上的数据修改操作之前,更新、插入、或删除首先在父实体上执行。
  • 如果需要的数据操作没有出现在子实体中,那递归执行会停止。
  • 当更新一个父实体时,没有指定子实体数据操作的执行顺序。

下面的示例展示了SalesOrderHeader实体的查询、更改、删除方法。这些方法包含了在子实体内的处理改变的逻辑。



[EnableClientAccess()]
public class OrderDomainService : LinqToEntitiesDomainService
{

public IQueryable GetSalesOrders()
{
return this.ObjectContext.SalesOrderHeaders.Include("SalesOrderDetails");
}

public void UpdateSalesOrder(SalesOrderHeader currentSalesOrderHeader)
{
SalesOrderHeader originalOrder = this.ChangeSet.GetOriginal(currentSalesOrderHeader);

if ((currentSalesOrderHeader.EntityState == EntityState.Detached))
{
if (originalOrder != null)
{
this.ObjectContext.AttachAsModified(currentSalesOrderHeader, this.ChangeSet.GetOriginal(currentSalesOrderHeader));
}
else
{
this.ObjectContext.Attach(currentSalesOrderHeader);
}
}

foreach (SalesOrderDetail detail in this.ChangeSet.GetAssociatedChanges(currentSalesOrderHeader, o => o.SalesOrderDetails))
{
ChangeOperation op = this.ChangeSet.GetChangeOperation(detail);
switch (op)
{
case ChangeOperation.Insert:
if ((detail.EntityState != EntityState.Added))
{
if ((detail.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(detail, EntityState.Added);
}
else
{
this.ObjectContext.AddToSalesOrderDetails(detail);
}
}
break;
case ChangeOperation.Update:
this.ObjectContext.AttachAsModified(detail, this.ChangeSet.GetOriginal(detail));
break;
case ChangeOperation.Delete:
if (detail.EntityState == EntityState.Detached)
{
this.ObjectContext.Attach(detail);
}
this.ObjectContext.DeleteObject(detail);
break;
case ChangeOperation.None:
break;
default:
break;
}
}
}

public void DeleteSalesOrder(SalesOrderHeader salesOrderHeader)
{
if ((salesOrderHeader.EntityState == EntityState.Detached))
{
this.ObjectContext.Attach(salesOrderHeader);
}

switch (salesOrderHeader.Status)
{
case 1: // in process
this.ObjectContext.DeleteObject(salesOrderHeader);
break;
case 2: // approved
case 3: // backordered
case 4: // rejected
salesOrderHeader.Status = 6;
break;
case 5: // shipped
throw new ValidationException("The order has been shipped and cannot be deleted.");
default:
break;
}

}
}

2010年1月21日星期四

WCF RIA 服务 (十四)- 数据 4

如何自定义解决数据并发性冲突
WCF RIA Services框架把原值和更改后的值一起传递给数据访问层。这就允许数据访问层在提交数据更改之前检测数据并发性冲突。数据访问层是通过检测数据源中的当前值是否与Domain操作中检索到的值一致来判断冲突的。
我们可以为一个特定的实体自定义如何解决数据并发性冲突,这个方法在冲突确认时被调用。在这个方法中,可以指定步骤来解决冲突。这个方法必须匹配作为解决冲突的方法所需的签名。格式应如下:

  • 返回一个Boolean值。
  • 有个Resolve名字前缀或有ResolveAttribute。
  • 接受四个参数(current entity, original entity, store entity, 和一个指定是否本操作是个删除操作的值)

自定义解决数据并发性冲突

  1. 在domain service中,添加一个匹配做为解决方法所需签名的方法。
  2. 在这个方法中添加解决冲突的代码。下面的代码示例了一个名为ResolveProduct的方法,这个方法在冲突发生时会从domain操作中合并数据。 这个自定义的冲突解决代码,通过使用ResolveOption枚举值来指定合并数据。

    public bool ResolveProduct(Product currentProduct, Product originalProduct, Product storeProduct, Boolean deleteOperation)
    {
    return base.Resolve(currentProduct, originalProduct, storeProduct, ResolveOption.MergeIntoCurrent);
    }


如何向Domain service添加显式事务
可以在Domain Service中添加显式事务,来执行一组变化。
创建一个显式事务
  1. 在项目中,添加对System.Transactions程序集的引用。也可以从[program files]\Reference Assemblies\Microsoft\Framework\.NETFramework\[version]\System.Transactions.dll 找打。
  2. 在Domain Service中,重载Submit方法。
  3. 创建一个TransactionScope,它打包了对基类中Submit方法的调用。
  4. 在完成事务之前,检查ChangeSet对象的HasError成员属性。示例如下:

    [EnableClientAccess()]
    public class DomainService1 : DomainService
    {
    public override bool Submit(ChangeSet changeSet)
    {
    bool result = false;

    using (var tx = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })
    )
    {
    result = base.Submit(changeSet);
    if (!this.ChangeSet.HasError)
    {
    tx.Complete();
    }
    }

    return result;
    }
    }

WCF RIA 服务 (十三)- 数据 3

如何验证数据
我们对实体和成员属性添加验证属性来实施验证规则。WCF RIA Service提供了几个验证属性来执行常用的验证检测,还提供了CustomValidationAttribute属性来执行自定义的验证检测。
在RIA Service中包含了如下的验证属性:

  • DataTypeAttribute
  • RangeAttribute
  • RegularExpressionAttribute
  • RequiredAttribute
  • StringLengthAttribute

我们在服务端添加验证属性,这些属性会传递给生成的客户端实体。在运行时,这些验证规则会应用到客户输入的数据上。我们必须通过添加元数据类来添加这些验证属性。

添加一个验证属性

  1. 为实体类添加一个元数据类,可以参考上一节内容。
  2. 对想要验证的实体或成员属性,添加验证属性来执行验证。下面的例子示例如何对一个名字为Address1的成员属性添加验证。

    [Required]
    [StringLength(60)]
    public string AddressLine1;

  3. 生成解决方案。
  4. 在silverlight的应用项目中,打开Generated_Code文件下的代码文件,注意到在客户端代码中也应用了验证属性。

添加自定义验证属性

  1. 对实体类添加一个元数据类。
  2. 添加一个共享代码文件,以*.shared.cs命名。这个文件将会包含一个自定义验证对象。
  3. 添加一个方法来判断是否数据有效。这个方法必须是public和static,还必须返回一个ValidationResult来表示验证的结果。下面示例是一个有名为IsProductValid方法的ProductValidator类,这个方法验证一个Product实体。

    public class ProductValidator
    {
    public static ValidationResult IsProductValid(Product productToValidate, ValidationContext context)
    {
    if (productToValidate.ListPrice < ((decimal).8 * productToValidate.StandardCost))
    {
    return new ValidationResult("ListPrice is below 80 percent of StandardCost.");
    }
    else
    {
    return ValidationResult.Success;
    }
    }
    }

  4. 对象要验证的实体或成员属性,添加[CustomValidationAttribute]批注属性,并传递验证对象和执行验证的方法的名字。下面的示例显示了对一个实体应用[CustomValidation]属性,验证对象的类型是 ProductValidator,验证方法的名字是 IsProductValid。

    [CustomValidation(typeof(RIAServicesExample.Web.SharedCode.ProductValidator), "IsProductValid")]
    [MetadataTypeAttribute(typeof(Product.ProductMetadata))]
    public partial class Product
    {

    internal sealed class ProductMetadata
    {

    // Metadata classes are not meant to be instantiated.
    private ProductMetadata()
    {
    }

    public string Color;

    public Nullable DiscontinuedDate;

    public decimal ListPrice;

    public DateTime ModifiedDate;

    public string Name;

    public Nullable ProductCategoryID;

    public int ProductID;

    public Nullable ProductModelID;

    public string ProductNumber;

    public Guid rowguid;

    public Nullable SellEndDate;

    public DateTime SellStartDate;

    [Required()]
    [StringLength(20)]
    public string Size;

    public decimal StandardCost;

    public byte[] ThumbNailPhoto;

    public string ThumbnailPhotoFileName;

    public Nullable Weight;
    }
    }

  5. 生成解决方案。
  6. 在Silverlight客户端,打开Generated_Code文件夹下的文件。注意到在共享代码中CustomValidationAttribute应用到了实体上。