WCF中的REST架构二 (支持AJAX的WCF服务 - 创建服务)

我在昨天 的文章WCF中的 REST架构一(REST 概述)谈了REST的基本概要,并提出了从HI REST (高REST)到 LO REST (低REST) 的RESTFULness(REST度)的概念。在今天的文章中,我将详细介绍大家可能最为熟悉的REST风格的 WCF 服务:支持AJAX的服务。此类服务应属于LO REST的范畴。现在很多人直觉地将“好”等同于“高大全”,因而低估了这种LO REST实现的价值。本篇将告诉你这决非事实,支持AJAX的WCF服务是足够强大的。
解决方案的背景信息
我有一个底层工作已基本完整的初始解决方案,大部分代码是作为12集AJAX 客户库Webcast 系列的组成部分。提供这个初始解决方案的目的是为了让我更好地展现支持AJAX的WCF服务的强大能力,避免现场创建一个这样的服务然后再与AJAX 客户应用集成所需的冗长步骤(如何你认为我创建的范例应用乏善可陈,请试一下Supersefer,这是一个基于这个范例的希伯来文网上商店)。在这个初始解决方案中请特别注意两个ASP.Net AJAX 控件: Catalog.js和ShoppingCart.js. 下面的截图就是这两个控件的显示
image
Catalog控件是本篇的重要操作对象,它的任务是接受一个特定的数据结构,显示一页产品信息,作绑定用途,并在用户选中某样产品或者点击浏览控件的时候出发消息,至于该控件是如何与宿主页(Catalog.aspx)交互,大家可以在前面提到的Webcast系列中找到。就本篇而言,我们只要关注Catalog.aspx(宿主页)中的下列代码片段,里面有一个GetData() 方法。等我们实现了WCF服务之后,就在这个方法里调用它。我们会给WCF服务传入诸如startIndex(起始索引)和pageSize(页的大小)之类的参数(服务的返回数据将是指定页数的产品信息), 以及几个回调方法(服务调用成功或失败后被回调)。下面也给出了调用成功的回调方法。该方法直接获取了指向catalog控件的引用($get 在 asp.net的AJAX中用来指向一个控件)。然后它将设定当前页的索引值,再用传入回调函数的数据,也就是我们的WCF有效荷载,来设定productInfo属性。最后,该函数调用控件的dataBind方法,这个方法直接基于存储在productInfo属性中的数据做了一些DOM注入。
image

添加服务
添加一个支持AJAX的 WCF 服务相当简单。第一步就是使用'AJAX-enabled WCF Service'模板添加服务。过程如下:在Solution Explorer里面选择”Add New Item’ > 选择'AJAX-enabled WCF Service' > 将其命名为 CatalogService.cs > 点击 ‘Add’.
image
模板看起来相当神奇,其实它不过是添加了一些特定的文件,并更改了另一些文件(通常是*.config文件)而已。如果你是按照我的步骤,那么这个模板做了下列事情:
1. 在项目根目录下添加CatalogService.svc文件
2. 在app_code目录下添加CatalogService.cs文件
3. 在web.config内配置服务
分析一下生成的文件
我们来看一下上面模板生成的文件并稍加讨论:
CatalogService.svc
<%@ ServiceHost Language="C#"
Debug="true"
Service="CatalogService"
CodeBehind="~/App_Code/CatalogService.cs" %>
*.svc文件可以类比于*.asmx文件。如果该服务在IIS或者WAS上运行,这个文件就作为服务的可定位资源。换句话说,*.svc 就是你服务的的基址。这个文件里我们只需注意两个属性:首先是Service属性,该属性被设为实现该服务的CLR类型名(你可以在app_code目录下的CatalogService.cs中找到该类型)。第二个就是指向实现文件的CodeBehind属性。
你无需修改svc 文件的内容,我只是为了向你展示该文件就是作为服务的base地址。
web.config
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="CatalogServiceAspNetAjaxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
<service name="CatalogService">
<endpoint address=""
behaviorConfiguration="CatalogServiceAspNetAjaxBehavior"
binding="webHttpBinding"
contract="CatalogService" />
</service>
</services>
</system.serviceModel>
如果熟悉WCF,你一定知道WCF的配置是位于system.serviceModel节点下。你会看到3个子节点:behaviors, serviceHostingEnvironment和serivces:
1. Services: 文件内已经有一个名为CatalogService的服务,与实现服务的类名一致。在Service节点下,你可以看到Endpoint,它包含了服务的ABCs:Address(地址), binding (绑定)和contract (协定).
    a. Address: 你会发现地址是空的 ,不必担心,因为svc文件会作为基
    b. Binding: webHttpBinding是WCF3.5提供的可以让服务以REST方式发布的新型绑定。这个绑定有两种模式,你可以通过特定的终结点行为来指定模式(通过设定behaviorConfiguration)
    c. Contract: contract指定了WCF服务提供的功能。该属性被设为一个已经定义好的服务协定。要定义一个服务协定,你可以用ServiceContract修饰一个接口或者类。被ServiceContract修饰的接口或者类中,所有由OperationContract修饰的方法将被作为服务的操作暴露给客户。一般来说用接口做服务协定是更好的做法,这样做的好处是分离了协定与具体实现。 但在我们使用的模板中却使用了类的方法,也就是说,接口是从类自动推断出来的(接口推断)。
    d. BehaviorConfiguration: 对于webHttpBinding, 你需要将该属性设为一个拥有webHttp或enableWebScript子节点的终结点行为。
2. Behaviors: 将behaviors加入到服务或者服务终结点的目的在于改变runtime的默认行为或者加入定制的扩展。你会发现我们使用的模板已经声明了一个名为CatalogServiceAspNetAjaxBehavior的终结点行为. 该行为有一个enableWebScript 元素。enableWebScript是两种可能的终结点行为之一,另一种就是webHttp。事实上enableWebScript是webHttp用来提供AJAX 功能(比如生成客户端代理)的子类
3. ServiceHostingEnvironment: 跟ASP.Net运行的WCF服务的默认配置是让两者互不干扰 例如,ASP.NET runtime 不参与WCF请求的处理. WCF服务也不能使用ASP.NET context 和 session 这样的功能. 而我们这里的配置是将aspNetCompatibilityEnabled 设为true, 在这种设置下WCF请求就会加到ASP.Net 的请求流水线里了。
*要使WCF支持REST,只要在该配置文件中注意最重要的两点:1)binding应设为webHttpBinding 2) endpoint behavior要设定为webHttp或enableWebScript

CatalogService.cs
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
public class CatalogService
{
// Add [WebGet] attribute to use HTTP GET
[OperationContract]
public void DoWork()
{
// Add your operation implementation here
return;
}
// Add more operations here and mark them with [OperationContract]
}
在这里我们就真得做点儿事儿了。如果已经读了上面的内容,你应该已经注意到我们已经用接口推断的方法定义了的服务协定,就是说用ServiceContract属性修饰了一个类而不是一个接口。
无论如何,我们要开始做一点儿实现了。第一件事儿是设定ServiceContract属性的NameSpace参数。这一点非常重要。客户端用于调用WCF服务的代理的名字空间就是从这里拿的。我就将名字空间设为urn:shopping/services.[ServiceContract(Namespace = "urn:shopping/services")]
接下来,我要添加一个操作(就是一个用OperationContract修饰的方法)。这个操作使用LINQ to SQL到数据库获取一页产品信息。该实现返回一个包含ProductData类型的ProductGroupingData 类型。我这样做的原因在于我不仅需要返回产品信息(ProductData),还需要一些有助于分页的元数据(StartIndex, PageSizebut 和ProductGrouping的TotalCount)。 下面就是这些类型:
[DataContract]
public class ProductData
{
[DataMember]
public int ProductId;
[DataMember]
public string ProductName;
[DataMember]
public string Description;
[DataMember]
public decimal Price;
[DataMember]
public string ProductImage;
}
[DataContract]
public class ProductGroupingData
{
[DataMember]
public List<ProductData> Products;
[DataMember]
public int StartIndex;
[DataMember]
public int PageSize;
[DataMember]
public int TotalCount = 0;
}
以下是实现代码:
[OperationContract]
public ProductGroupingData GetProductGrouping(int startIndex, int pageSize)
{
using (CatalogDataContext catalogCtx = new CatalogDataContext())
{
// Set up the query
var products = from p in catalogCtx.Products
orderby p.ProductName
select new ProductData()
{
ProductId = p.ProductId,
ProductName = p.ProductName,
Description = p.ProductDescription,
Price = p.Price,
ProductImage = p.ProductImage
};
// Use skip and take extension methods for server side paging 
// and call ToList to execute the query
var productColl = products.Skip(startIndex).Take(pageSize).ToList();
// Add the metadata 
var package = new ProductGroupingData()
{
Products = productColl,
StartIndex = startIndex,
PageSize = pageSize,
TotalCount = catalogCtx.Products.Count()
};
return package;

}
到这里,我们已经创建好了一个支持AJAX的服务。一个服务支持AJAX,就是说该服务可以非常容易地被一个 AJAX 客户端调用。接下来的 WCF中的REST架构 三,我就讨论如何在客户端调用服务.