可以说WebHttpBinding和WebHttpBehavior是整个Web HTTP编程模型最为核心的两个类型,前者主要解决消息编码问题,而余下的工作基本上落在了终结点行为WebHttpBehavior上。WebHttpBehavior属性HelpEnabled和AutomaticFormatSelectionEnabled是“帮助页面”与“自动消息格式选择”这两个特性的总开关。[“自动消息格式(JSON/XML)选择”源代码从这里下载]
1: public class WebHttpBehavior : IEndpointBehavior, ...
2: {
3: //其他成员
4: public virtual bool HelpEnabled { get; set; }
5: public virtual bool AutomaticFormatSelectionEnabled { get; set; }
6: }
一、 帮助页面
WCF 4.0为REST服务提供了帮助页面功能,我们可以通过浏览器访问服务帮助页面的地址得到所有操作的基本信息。但是这个功能在默认的情况下是关闭的,我们需要通过应用在终结点上的WebHttpBehavior行为的HelpEnabled属性开启该功能。
1: <configuration>
2: <system.serviceModel>
3: <behaviors>
4: <endpointBehaviors>
5: <behavior>
6: <webHttp helpEnabled="true" />
7: </behavior>
8: </endpointBehaviors>
9: </behaviors>
10: <services>
11: <service name="Artech.WcfServices.Service.EmployeesService">
12: <endpoint address="http://127.0.0.1:3721/employees"
13: binding="webHttpBinding"
14: contract="Artech.WcfServices.Service.Interface.IEmployees"/>
15: </service>
16: </services>
17: </system.serviceModel>
18: </configuration>
同样以之前演示的EmployeesService为例,我们通过如上的配置将终结点行为WebHttpHehavior应用在服务唯一的终结点上(默认终结点),并将HelpEnabled属性设置为True。那么基于终结点的帮助页面将以地址{终结点地址}/Help发布出来,我们通过浏览器访问这个地址就会得到如下图所示帮助页面。
如上图所示,帮助页面列出了包括相对地址、HTTP方法和基本描述在内的所有操作的基本信息。我们通过点击HTTP方法对应的链接可以获得包括基于相应格式(Xml和Json)消息结构(Schema)和实例。在默认的情况下,帮助页面中表示操作描述信息的格式为“Service at {操作地址}”,我们可以在定义服务契约的时候再操作方法上应用特性DescriptionAttribute来定义出现在帮助页面中的描述信息。
1: [ServiceContract]
2: public interface IEmployees
3: {
4: [WebGet(UriTemplate = "all")]
5: [Description("获取所有员工列表")]
6: IEnumerable<Employee> GetAll();
7:
8: [WebGet(UriTemplate = "{id}")]
9: [Description("获取指定ID的员工")]
10: Employee Get(string id);
11:
12: [WebInvoke(UriTemplate = "/", Method = "POST")]
13: [Description("创建一个新的员工")]
14: Employee Create(Employee employee);
15:
16: [WebInvoke(UriTemplate = "/", Method = "PUT")]
17: [Description("修改现有员工信息")]
18: void Update(Employee employee);
19:
20: [WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
21: [Description("删除指定ID的员工")]
22: void Delete(string id);
23: }
如上面的代码片断所示,我们在契约接口IEmployees中的所有操作方法上应用了DescriptionAttribute特性并指定了相应的描述信息。这些描述信息就是出现在如下图所示的帮助页面中。
二、 自动消息格式选择
REST服务具有两种基本的消息格式(Xml和Json)。在定义服务契约的时候,我们可以通过应用在操作方法上的WebGetAttribute和WebInvokeAttribute指定回复消息的格式。如果没有通过这种方式对消息格式进行显式设置,我们还可以通过终结点行为WebHttpBehavior为回复消息设置一个默认的消息格式。除了这种显示设置方式之外,WCF还提供一种自动消息格式选择机制。
所谓消息格式的自动选择,就是服务根据请求消息来选择一种适合的格式进行消息的序列化。在默认的情况下,这种自动选择机制是关闭的,我们需要通过WebHttpBehavior的AutomaticFormatSelectionEnabled属性开启该机制。具体的消息格式选择机制策略(顺序)如下:
- 如果作为请求的HTTP消息具有Accept报头,则根据该报头决定回复消息的格式;
- 如果作为请求的HTTP消息具有Content-Type报头,则根据该报头决定回复消息的格式;
- 如果在定义服务契约时通过WebGetAttribute或者WebInvokeAttribute对回复消息的格式进行了显式设置,则采用该消息格式;
- 如果通过终结点行为WebHttpBehavior设置了对回复消息的格式进行了显式设置,则采用该消息格式;
- 采用默认消息格式Xml(WebMessageFormat枚举的默认值)。
我们同样通过之前创建的EmployeesService的实例来演示消息格式的自动选择机制。如下面的配置片断所示,我们将WebHttpBehavior行为应用到了寄宿服务的唯一终结点上,并且将AutomaticFormatSelectionEnabled属性设置为True。
1: <configuration>
2: <system.serviceModel>
3: <behaviors>
4: <endpointBehaviors>
5: <behavior name="webHttp">
6: <webHttp automaticFormatSelectionEnabled="true" />
7: </behavior>
8: </endpointBehaviors>
9: </behaviors>
10: <services>
11: <service name="Artech.WcfServices.Service.EmployeesService">
12: <endpoint address="http://127.0.0.1:3721/employees"
13: behaviorConfiguration="webHttp"
14: binding="webHttpBinding"
15: contract="Artech.WcfServices.Service.Interface.IEmployees"/>
16: </service>
17: </services>
18: </system.serviceModel>
19: </configuration>
对于契约接口IEmployees来说,我们通过WebGetAttribute特性用于返回所有员工列表的GetAll操作的回复消息格式设置为Xml。
1: [ServiceContract]
2: public interface IEmployees
3: {
4: //其他成员
5: [WebGet(UriTemplate = "all",ResponseFormat = WebMessageFormat.Xml)]
6: IEnumerable<Employee> GetAll();
7: }
对于REST服务调用来说,其本质就是一种普通的HTTP请求,与针对某个网页的访问并没有什么本质的不同,所以我们完全可以手工生成HTTP请求来进行服务的访问。为此我们创建了如下一个静态方法GetAllEmployees方法通过WebClient对服务的GetAll操作进行调用,并将整个回复消息打印出来,该方法的两个参数分别是作为请求的HTTP消息的Content-Type和Accept报头值。
1: static void GetAllEmployees(string contentType, string accept)
2: {
3: WebClient webClient = new WebClient();
4: if (!string.IsNullOrEmpty(contentType))
5: {
6: webClient.Headers.Add("Content-Type", contentType);
7: }
8:
9: if (!string.IsNullOrEmpty(accept))
10: {
11: webClient.Headers.Add("Accept", accept);
12: }
13: using (StreamReader reader = new StreamReader(webClient.OpenRead(
14: "http://127.0.0.1:3721/employees/all")))
15: {
16: Console.WriteLine(reader.ReadToEnd());
17: }
18: }
然后我们通过调用GetAllEmployees方法进行三次服务调用。第一次调用既没有指定Content-Type报头也没有指定Accept报头,第二次和第三次调用在分别将这两个报头指定为“application/json”。
1: string contentType = "application/json";
2:
3: Console.WriteLine("Content-Type = N/A; Accept = N/A:");
4: GetAllEmployees("", "");
5: Console.WriteLine();
6:
7: Console.WriteLine("Content-Type = application/json; Accept = N/A:");
8: GetAllEmployees(contentType, "");
9: Console.WriteLine();
10:
11: Console.WriteLine("Content-Type = N/A, Accept = application/json:");
12: GetAllEmployees("", contentType);
从如下所示的输出结果我们可以看出:由于服务调用请求没有指定任何媒体类型相关的报头,所以回复消息采用的是契约接口中设置的消息格式Xml。对于后两次服务调用中,由于请求消息中分别通过Content-Type和Accept报头将“期望”的媒体类型设置为application/json,所以Json最终作为回复消息的格式。(S1006)
1: Content-Type = N/A; Accept = N/A:
2: <ArrayOfEmployee xmlns="http://www.artech.com/" xmlns:i="http://www.w3.org/2001/
3: XMLSchema-instance"><Employee><Department>开发部</Department><Grade>G7</Grade><I
4: d>001</Id><Name>张三</Name></Employee><Employee><Department>人事部</Department><
5: Grade>G6</Grade><Id>002</Id><Name>李四</Name></Employee></ArrayOfEmployee>
6:
7: Content-Type = application/json; Accept = N/A:
8: [{"Department":"开发部","Grade":"G7","Id":"001","Name":"张三"},{"Department":"人
9: 事部","Grade":"G6","Id":"002","Name":"李四"}]
10:
11: Content-Type = N/A, Accept = application/json:
12: [{"Department":"开发部","Grade":"G7","Id":"001","Name":"张三"},{"Department":"人
13: 事部","Grade":"G6","Id":"002","Name":"李四"}]