qouoww

质量管理+软件开发=聚焦管理软件的开发与应用

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

从服务器暴露数据

何为WCF RIA服务

RIA服务建立在WCF顶层,用于建立与服务器可沟通的数据驱动Silverlight应用程序.可以描述如下

  • 数据为中心的设计模式
  • 可提供高级数据管理,授权管理以及查询功能的框架
  • 通过代码生成器,通用代码可供多层调用
  • 基于WCF服务构建,可以充当中间层与显示层的通信媒介

image

RIA服务聚焦中间层,与EF框架协同,是SilverLight数据应用程序的最佳选择.RIA服务是一种端到端技术;因此,需要对服务器与客户端分别进行控件才能有效工作.RIA服务包括如下功能:

  • 数据源控件,可用于XAML与服务进行通信
  • 跟踪客户端对数据所做的修改,并将其更新到服务器
  • 可以在客户端使用LINQ查询,然后在服务器端执行这些查询
  • 随时可用的授权服务功能

WCF RIA服务的使用方法

1、链接SilverLight与Web项目:RIA服务需要项目结构遵循一定的模式。首先,服务器项目(web)和客户端项目(silverlight)必须进行连接,使RIA服务内置的代码生成器可以在客户端项目中生成代码。这就要求客户端与服务器的项目必须在同一解决方案里。

2、创建域服务:域服务更像是一个标准的WCF服务,但遵循一些给定的模式并提供了基本的功能。一般来说,对每个数据集都应配置一个相应的域服务,比如products,customers等;

3、在域服务中创建域操作:主要是实现CRUD功能,使用客户端与服务器端进行交互。例如,对Product域服务通常需要配置如下域操作:GetProducts,InsertProduct,UpdateProduct和DeleteProduct;

4、使用验证逻辑和其他特性标记修改实体:域服务所暴露的实体可以使用特性进行修饰(比如数据验证规则),也可以直接将属性设置到关联类上(称之为元数据类)。

5、设置可供服务器与客户端共享的代码:只需要将想要共享的代码放置在以.shared.cs扩展名的文件中即可;

6、在客户端项目调用域服务:RIA服务自动在客户端项目生成代码,这些代码可以与服务器上的域服务进行交互。RIA服务为每个域服务创建了一个域上下文,为每个域服务暴露的实体创建了相应的代理类。现在就可以使用域上下文(domain contexts)从服务器上获取数据,对数据进行处理,并将任何变更保存到服务器上。

 

WCF RIA服务器如何生成代码

EIA服务需要服务器与客户端项目必须处于同一解决方案中并彼此连接。服务器端包含暴露数据的服务,而客户端与服务器端进行通信并使用数据。

在服务器端创建域服务以后,RIA服务就创建了代码生成任务(一般称为投影),会生成相应代码和相关代码数据对象类(一般称这为实体,在客户端任何实体都是由服务所暴露的,可以应用特性,如验证逻辑;也可以将代码进行复制并标记为共享代码,供服务器端项目与客户端项目共享使用)。

 

如图所示:在SilverLight项目(客户端)有一个Generated_Code文件夹,这是在Silverlight项目编译时由RIA服务建立的。这可以命名客户端项目可以访问所有由RIA服务暴露的操作,数据,逻辑。注意生成的代码不要手工修改,重新编译后会被覆盖掉,如果想要更改,因为生成的代码是部分类,可以根据需要在其他位置编写代码。

 

创建RIA服务连接

如果使用SilverLight业务应程序项目模板,默认已经配置了RIA服务,提供了一些基本功能。

image

上图中项目中有一个Services文件夹与Models文件夹。Service文件夹已经包含了两个域服务(AuthenticationService和UserRegistrationService),用于对客户端的授权以及用户注册。Models文件夹包括两个数据类(User和RegistrationData),用于在服务器与客户端进行传递。在Models文件夹下还有一个Shared文件夹,有一个文件User.shared.cs,包含有可供服务器与客户端项目共享的代码。

RIA服务的连接配置方法如下:

image

如果想要在现有的Silverlight项目与Web项目间使用RIA服务,可以使用这一属性进行手工连接。

域服务

创建RIA服务连接后,就需要暴露来自于服务器的数据与操作。达成此目的,需要使用域服务,典型的域服务包括基本的CRUD操作和任何可在客户端调用的其他定制操作。

创建域服务最简单的方法是使用Domain Service Item模板和Add New Domain Service Class向导,

注:在运行向导之前已经通过EF框架生成了实体类和上下文类(ObjectContext);

 image

对这个对话框解释一下:

“可用的DataContext/ObjectContext”下拉列表:该下拉列表用于选择EF模型;如果模型未显示,请先生成项目;如果使用POCO类,应选择<空域服务类>选项,然后手工实现相关的域操作。

实休列表:列出了所有可供处理的实体。选定的实体在域服务中会创建操作,该操作返回相应实体的集合。如果想要修改实体,则勾选”Enable Editing“选项,然后向导会为每个实体创建insert,update和delete域操作。

Enabling Client Access:必须进行选定,这个选项会在域服务类上装饰EnableClientAccess特性标识,用来提示RIA服务的代码生成器应该在客户端项目中生成代码。

Exposing an OData Endpoint :这个选项可以用于SharePoint,由于对其研究不多,不再赘述;默认不需要选择此选项。

Generating Associated Classes for Metadata :为每个选定的实体生成元数据类,可以在该类中对实体的类、属性进行特性标记修饰,而无需修改实体本身。元数据类扩展名为.metadata.cs,例如ProductService域服务会创建ProductService.metadata.cs文件,包含有所有该域服务暴露的实体的所有元数据类,

 

域操作

域操作是域服务上的方法,可以在客户端进行调用。除了常规的CRUD以外,也可以设置定制的方法。Add New Domain Service Class wizard 创建了CRUD功能的方法,理解了这些方法,就可以用来创建自已的方法了。为了使用RIA服务能够正确地在客户端项目中生成相应的代码,默认情况下需要执行一些的规则,包括方法命名和方法签名等(当然也可以定制自已的方法命名和签名规则,但需要在在方法上加注特性标记以指明是哪种操作模式)。

下面是各类操作的默认方法:

查询:

命名规则/方法签名规则:查询操作无命名规则,可接受任何参数或无参数,因为可以被RIA服务隐匿识别为查询操作,但是最好返回IQueryable<T>, IEnumerable<T>,或单个实体,并对方法修饰Query标记;

返回集合的方法:

public IQueryable<Product> GetProducts() 
{ 
    return this.ObjectContext.Products; 
}
public IQueryable<Product> GetProducts(string nameFilter) 
{ 
    return this.ObjectContext.Products.Where(p =>  
                                             p.Name.StartsWith(nameFilter)); 
}

返回值实现IQueryable<T>接口,可以直接应用LINQ to Entities,并且可以实现RIA服务提供的很多有用的功能。

返回单个实体

public Product GetProduct(int productID) 
{ 
    return ObjectContext.Products 
        .Where(p => p.ProductID == productID) 
        .FirstOrDefault(); 
} 

 

Insert/Update/Delete操作

命名、方法签名规则:

insert/update/delete操作必须接受一个单个实体作为参数,并且无附加参数,无返回值;并且在命名方法时应注意:

  • Insert操作方法必须开始于Insert,Create,Add。否则需要在方法上修饰Insert标记;
  • Update操作方法必须开始于Update,Change或Modify。否则需要在方法上修饰Update标记;
  • Delete操作方法必须开始于Delete或Remove。否则需要在方法上修改Delete标记;

Insert/Update/Delete操作的域服务循环

  • authorize changeset阶段:检查每个对changeset调用的操作是否遵循安全相关的规则,这些规则在域服务的操作上定义,方法是在操作上修饰安全规则特性标识。
  • validate changeset 阶段:检查验证规则
  • execute changeset 阶段:执行
  • persist changeset 阶段:调用SubmitChanges方法,将数据持久化到数据库;

每个步骤都由域服务自动执行;但是,也有相应的方法可以覆写从而跳过某几个步骤进行定制,这些方法包括:

  • AuthorizeChangeSet
  • ValidateChangeSet
  • ExecuteChangeSet
  • PersistChangeSet

Insert/Update/Delete操作例子:

插入数据:

public void InsertProduct(Product product) 
{ 
    if (product.EntityState != EntityState.Added) 
    { 
        if (product.EntityState != EntityState.Detached) 
        { 
            this.ObjectContext.ObjectStateManager.ChangeObjectState(product,  
                                                           EntityState.Added); 
        } 
        else 
        { 
            this.ObjectContext.AddToProducts(product); 
        } 
    } 
}

 

更新数据:

public void UpdateProduct(Product currentProduct) 
{ 
    if (currentProduct.EntityState == EntityState.Detached) 
    { 
        this.ObjectContext.Products.AttachAsModified(currentProduct,  
                                this.ChangeSet.GetOriginal(currentProduct)); 
    } 
} 

删除操作

public void DeleteProduct(Product product) 
{ 
    if (product.EntityState == EntityState.Detached) 
    { 
        this.ObjectContext.Attach(product); 
    } 
    this.ObjectContext.DeleteObject(product); 
} 

 

调用操作

调用操作通常用于实例化服务器上的行为,比如请求服务器发送邮件,或者请求服务器返回一个简单的值(该值通常不需要对变更进行跟踪),比如汇率。与查询操作一样,异步调用操作也可以由客户端应用直接调用,这些调用是立即完成的,不像常规操作那样需要changeset提交到服务器上才进行调用。

 

尽管调用操作概念上来说与普通的WCF服务操作相同,但是它们不能返回复杂的类型,除了实体对象外。这可能是使用RIA服务所要面对的一个非常大的问题。这意味着要向客户端返回复杂的对象类型是不可能的,解决这个问题的唯一方法是用回到纯WCF通信,希望这个问题在将来能得以克服。

调用操作不需要命名或方法签名约定(惯例),实质上任何领域服务中的操作,只要不属于任何其他类型的操作分类(通过命名或方法签名)就会被RIA服务认为是一个调用操作。也可以通过为方法应用Invoke来显示将一个方法作为调用方法。调用操作的示例如下所示:

 

public decimal GetExchangeRate(string fromCurrency, string toCurrency) 
{ 
    return ExchangeRates.GetRate(fromCurrency, toCurrency); 
}

 

自定义操作

自定义操作用于在一个实体上完成一些行为的操作,比如,你可能有一个操作来停用一个产品,所有这些逻辑将在服务器端完成,但是当客户端使用调用操作时,会被立即执行。自定义操作不会立即执行,它会延迟执行到客户端向服务器端提交了一个变更集,可以将自定义操作看在是与插入/更新/删除相同的操作。

当客户端生成一个自定义操作时,它将作为实体的一个方法被创建,然后对那个实体发生作用,同时它也被创建在实体上下文级别,以便进行调用。

自定义方法通常是在插入或更新操作在服务器端被调用后才执行,如果一个自定义的操作在一个实体上被调用,但是随后那个实体被删除,那么自定义方法将不会被调用,而是被丢弃。

自定义操作没有命名惯例,但是它们必须没有返回值,必须接收一个实体对象作为其第1个参数(任意个数的其它参数也是被允许的)。没有什么显然的特性来标识一个自定义操作,但是相反你应该使用Update特性,设置其UsingCustomMethod属性值为True。自定义操作示例如下所示:

public void DiscontinueProduct(Product product) 
{ 
    // Logic to discontinue the product... 
} 

 

实体修饰

实体修饰可以达成三个目的:控制实体在客户端的显示,应用验证规则,指定某属性的数据特性(如是否为主键,是否执行并发检查等),需要在元数据类上进行设置。

[MetadataTypeAttribute(typeof(Product.ProductMetadata))] 
public partial class Product 
{ 
    internal sealed class ProductMetadata  
    { 
        // Field definitions (removed for the purposes of brevity) 
    } 
} 

上述代码是Product实体的元数据类,特点有:是Product的部分类,ProductMetadata是Product的嵌套类(尽管这不是必须的),通过MetadatTypeAttribute特性标记标识哪个类是Product的元数据类)。

通用特性的使用方法可参考http://msdn.microsoft.com/zh-cn/library/system.componentmodel.dataannotations.aspx以及

http://msdn.microsoft.com/zh-cn/library/cc490428(v=vs.91).aspx,需要特别认识的新概念包括复合层次结构,RoundtripOriginal,需要另行关注与了解。

 

 

自定义验证特性(以案例说明):

1、在Web项目里新建一个文件夹:ValidationRules。

2、添加一个新类到ValidationRules文件夹:ProductClassValidationAttribute.shared.cs,使用.shared意味着代码可以在服务器与客户端共享。注意该类以Attitude结尾,虽然不是必须的,但在实际使用时可以省略这个后缀;

3、取消对System.Web名称空间的引用,因为Silverlight并不支持该名称空间,当代码被复制到SilverLight项目时(RIA代码生成器生成),会出错;

4、添加对System。ComponentModel.DataAnnotations名称空间的引用;

5、ProductClassValidationAttribute类应继承自ValidationAttribute类;

6、覆写IsValid方法,虽然基类有两个IsValid方法,但是SIlverLight只支持以ValidationContext作为参数的方法;在此方法中可以编写验证逻辑代码。如果验证OK,该方法应返回ValidationResult.Success;如果验证失败,可以返回错误信息(信息内容必须作为ValidationResult类的构造器参数),代码如下:

using System; 
using System.ComponentModel.DataAnnotations; 
using System.Linq; 
namespace AdventureWorks.Web.ValidationRules 
{ 
    public class ProductClassValidationAttribute : ValidationAttribute 
    { 
        protected override ValidationResult IsValid(object value, 
                                                    ValidationContext validationContext) 
        { 
            bool isValid = true; 
 
            if (value != null) 
            { 
                string productClass = value.ToString(); 
 
               //验证参数值是否包含H,M,L等字母
                string[] validClasses = new string[] { "H", "M", "L", "" }; 
                isValid = validClasses.Contains(productClass.ToUpper()); 
            } 
 
            return isValid ? ValidationResult.Success : new ValidationResult( 
                                                "The class is invalid"); 
        } 
    } 
} 

8、创建完毕后就可以在属性中使用了。在Product实体的元数据类中添加如下引用:

using AdventureWorks.Web.ValidationRules;

找到相应属性:

[ProductClassValidation] 
public string Class; 

9、另一个例子:验证类中SellEndDate应大于SellStartDate,代码如下:

using System; 
using System.Linq; 
using System.ComponentModel.DataAnnotations; 
 
namespace AdventureWorks.Web.ValidationRules 
{ 
    public class SellDatesValidationAttribute : ValidationAttribute 
    { 
        protected override ValidationResult IsValid(object value,  
                                            ValidationContext validationContext) 
        { 
            Product product = value as Product; 
            return product.SellEndDate == null ||  
                   product.SellEndDate > product.SellStartDate ?  
                        ValidationResult.Success : new ValidationResult( 
                 "The sell end date must be greater than the sell start date"); 
        } 
    } 
} 
posted on 2012-05-07 11:52  qouoww  阅读(2489)  评论(0编辑  收藏  举报