RESTful WCF - 第二部分





5.00/5 (4投票s)
RESTful WCF
很久以前(记不清具体是何时了),我曾说过我要写一些关于 RESTFul WCF 的内容,您可以参考我的第 1 篇博文 http://sachabarber.net/?p=460。在那篇文章中,我提到我可能会做以下事情:
博文的发布顺序可能如下:
- 新的 RESTful WCF 属性
- 序列化选项(本文)
- 托管
- 使用 RESTful WCF 的 CRUD 操作
好了,这基本上是第 2 部分。在使用 RESTful WCF 时,您的序列化选项如下。
在本文中,我们将介绍以下序列化选项。
Message
- DataContract
- XML
- 混合
- JSON
本文不涉及如何处理 RESTful WCF 服务提供的数据资源,我们假定读者知道如何处理公开的资源。
这里有一个提示,可以看看 XLINQ 或任何其他的 XML API。
辅助方法 (Helper Methods)
在我们开始之前,我想指出附带的服务看起来是这样的:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Runtime.Serialization;
5: using System.ServiceModel;
6: using System.ServiceModel.Channels;
7: using System.Text;
8: using System.ServiceModel.Web;
9:
10: namespace DataService
11: {
12: [ServiceContract(SessionMode =
13: SessionMode.NotAllowed)]
14: public interface ISomeService
15: {
16:
17: /// <summary>
18: /// The OperationContracts below show how to use different
19: /// Serialization techniques such as
20: /// <list type="Bullet">
21: /// <item>Message (the most customizable/flexibility)</item>
22: /// <item>DataContract</item>
23: /// <item>XML</item>
24: /// <item>Hybrid approach</item>
25: /// </list>
26: /// </summary>
27:
28: [OperationContract]
29: [WebGet(UriTemplate = "/")]
30: Message GetRoot();
31:
32: [OperationContract]
33: [WebGet(UriTemplate = "/DC/{firstName}/{lastName}")]
34: Person GetRootPersonUsingDataContract(String firstName,
35: String lastName);
36:
37: [OperationContract]
38: [WebGet(UriTemplate = "/DC/PeopleList")]
39: PersonList GetRootPersonListUsingDataContract();
40:
41: [OperationContract]
42: [XmlSerializerFormat()]
43: [WebGet(UriTemplate = "/XMLSer/{animalBreed}")]
44: Animal GetRootAnimalUsingXmlSerialization(String animalBreed);
45:
46: [OperationContract]
47: [XmlSerializerFormat()]
48: [WebGet(UriTemplate = "/XMLSer/AnimalList")]
49: AnimalList GetRootAnimalListUsingXmlSerialization();
50:
51:
52: [OperationContract]
53: [WebGet(UriTemplate = "/HYBRID/{firstName}/{lastName}")]
54: Message GetRootPersonUsingHybridStylee(String firstName,
55: String lastName);
56:
57: [OperationContract]
58: [WebGet(UriTemplate = "/HYBRID/PeopleList")]
59: Message GetRootPersonListUsingHybridStylee();
60:
61:
62: /// <summary>
63: /// The OperationContracts below show how to use different
64: /// ResponseFormats such as XML/JSON
65: /// </summary>
66:
67: [OperationContract]
68: [WebGet(UriTemplate = "/DC/XML/{firstName}/{lastName}",
69: ResponseFormat=WebMessageFormat.Xml)]
70: Person GetRootPersonUsingDataContractXML(String firstName,
71: String lastName);
72:
73: [OperationContract]
74: [WebGet(UriTemplate = "/DC/XML/PeopleList",
75: ResponseFormat = WebMessageFormat.Xml)]
76: PersonList GetRootPersonListUsingDataContractXML();
77:
78:
79:
80: [OperationContract]
81: [WebGet(UriTemplate = "/DC/JSON/PeopleList",
82: ResponseFormat = WebMessageFormat.Json)]
83: PersonList GetRootPersonListUsingDataContractJSON();
84:
85: }
86: }
服务实现使用了几个看起来是这样的辅助方法:
1: private Person GetPerson(
2: String firstName, String lastName)
3: {
4: return new Person { FirstName = firstName, LastName = lastName };
5: }
6:
7: private PersonList PersonList()
8: {
9: return new PersonList {
10: new Person { FirstName = "firstName1", LastName = "lastName1" },
11: new Person { FirstName = "firstName2", LastName = "lastName2" },
12: new Person { FirstName = "firstName3", LastName = "lastName3" },
13: new Person { FirstName = "firstName4", LastName = "lastName4" }
14: };
15: }
Message
Message
是 WCF 提供的所有不同序列化技术中最灵活的一种,因为它最接近用于传输 Message
的实际 Channel
,并且它在自定义序列化过程方面提供了最大的自由度。作为副作用,它也需要最多的工作。
例如,当我们的服务方法如下所示时:
1: [OperationContract]
2: [WebGet(UriTemplate = "/")]
3: Message GetRoot();
而实际的服务实现如下所示:
1: //Matches [WebGet(UriTemplate = "/")]
2: public Message GetRoot()
3: {
4:
5: var stream = new MemoryStream();
6: XmlDictionaryWriter writer =
7: XmlDictionaryWriter.CreateTextWriter(stream);
8: writer.WriteStartDocument();
9: writer.WriteStartElement("Root");
10: writer.WriteStartElement("Hello");
11: writer.WriteElementString("Name", "sacha");
12: writer.WriteEndElement();
13: writer.WriteEndElement();
14: writer.WriteEndDocument();
15: writer.Flush();
16: stream.Position = 0;
17:
18: XmlDictionaryReader reader =
19: XmlDictionaryReader.CreateTextReader(
20: stream, XmlDictionaryReaderQuotas.Max);
21: return Message.CreateMessage(
22: MessageVersion.None, "", reader);
23: }
我们会得到以下结果:
这很棒,但要获取这些资源需要做很多工作,我们本可以用其他方式获取。让我们考虑一些其他选项。
DataContract
这是迄今为止最简单的方法,因为我们只依赖现有的 WCF DataContractSerializer
,它为我们完成了所有繁重的工作。这种方法的问题在于,我们最终得到的 XML 可能并不总是我们想要的,而且我们对其控制力不大。所以,如果这是一个问题,您可以随时选择 Message
类型序列化或此处提供的其他选项之一。
为了演示 DataContract Serialization
,我们首先需要一个实际的 DataContract
类/DataContract
集合,在附带的演示代码中,它看起来是这样的:
1: [DataContract()]
2: public class Person
3: {
4: [DataMember]
5: public String FirstName;
6:
7: [DataMember]
8: public String LastName;
9:
10: }
11:
12:
13: [CollectionDataContract(Name = "People", Namespace = "")]
14: public class PersonList : List<Person>
15: {
16: }
这样我们就可以定义一些服务方法,如下所示:
1: [OperationContract]
2: [WebGet(UriTemplate = "/DC/{firstName}/{lastName}")]
3: Person GetRootPersonUsingDataContract(String firstName,
4: String lastName);
5:
6: [OperationContract]
7: [WebGet(UriTemplate = "/DC/PeopleList")]
8: PersonList GetRootPersonListUsingDataContract();
而实际的服务实现如下所示:
1: //Matches [WebGet(UriTemplate = "/DC/{firstName}/{lastName}")]
2: public Person GetRootPersonUsingDataContract(
3: String firstName, String lastName)
4: {
5: return GetPerson(firstName, lastName);
6: }
7:
8:
9: //Matches [WebGet(UriTemplate = "/DC/PeopleList")]
10: public PersonList GetRootPersonListUsingDataContract()
11: {
12: return PersonList();
13: }
这是通过浏览器运行的示例:
XML
XML 序列化已经存在了一段时间,为了使用它,我们需要用特殊属性来装饰我们的类,以辅助序列化过程。在示例代码中,我们使用了以下类:
1: [XmlRoot(Namespace = "", ElementName = "Animal")]
2: public class Animal
3: {
4: [XmlAttribute(AttributeName="breed")]
5: public String Breed;
6: }
7:
8:
9: [XmlRoot(Namespace = "", ElementName="Animals")]
10: public class AnimalList : List<Animal>
11: {
12: }
这样我们就可以定义一些服务方法,如下所示:
1: [OperationContract]
2: [XmlSerializerFormat()]
3: [WebGet(UriTemplate = "/XMLSer/{animalBreed}")]
4: Animal GetRootAnimalUsingXmlSerialization(String animalBreed);
5:
6: [OperationContract]
7: [XmlSerializerFormat()]
8: [WebGet(UriTemplate = "/XMLSer/AnimalList")]
9: AnimalList GetRootAnimalListUsingXmlSerialization();
而实际的服务实现如下所示:
1: //Matches[WebGet(UriTemplate = "/XMLSer/{animalBreed}")]
2: public Animal GetRootAnimalUsingXmlSerialization(String animalBreed)
3: {
4: return new Animal { Breed = animalBreed};
5: }
6:
7:
8: //Matches[WebGet(UriTemplate = "/XMLSer/AnimalList")]
9: public AnimalList GetRootAnimalListUsingXmlSerialization()
10: {
11: return new AnimalList {
12: new Animal { Breed = "breed1"},
13: new Animal { Breed = "breed2"},
14: new Animal { Breed = "breed3"},
15: new Animal { Breed = "breed4"}
16: };
17: }
这是通过浏览器运行的示例:
混合方法
如果您想使用强类型对象,但也想使用 Message
,因为在 Message
中您可以更好地控制序列化过程,那么您可以采用一种混合方法,其工作原理大致如下。我们可以定义一些服务方法,如下所示:
1: [OperationContract]
2: [WebGet(UriTemplate = "/HYBRID/{firstName}/{lastName}")]
3: Message GetRootPersonUsingHybridStylee(String firstName,
4: String lastName);
5:
6: [OperationContract]
7: [WebGet(UriTemplate = "/HYBRID/PeopleList")]
8: Message GetRootPersonListUsingHybridStylee();
而实际的服务实现如下所示:
1: //Matches [WebGet(UriTemplate = "/HYBRID/{firstName}/{lastName}")]
2: public Message GetRootPersonUsingHybridStylee(String firstName,
3: String lastName)
4: {
5: Person p = GetPerson(firstName, lastName);
6: return Message.CreateMessage(MessageVersion.None, "*", p);
7: }
8:
9:
10: //Matches [WebGet(UriTemplate = "/HYBRID/PeopleList")]
11: public Message GetRootPersonListUsingHybridStylee()
12: {
13: PersonList pl = PersonList();
14: return Message.CreateMessage(MessageVersion.None, "*", pl);
15: }
使用与之前相同的辅助方法。
这种方法使我们能够使用强类型的 DataContract
对象,但使用 Messages
,这使得我们可以更好地控制序列化过程,因为这是开发人员在控制,而不是由实际的 DataContractSerializers
控制。
这是通过浏览器运行的示例。
JSON
你们中的一些人可能习惯于 Web 开发,并且非常习惯(甚至喜欢,仅供参考,我不是 Web 开发人员)使用 JavaScript/手动 Ajax 调用。正如我所说,我绝对不是 Web 开发人员,但为了完整起见,我们来看看如何将一些 RESTful 资源结果序列化为 JSON。
和以前一样,大部分繁重的工作是通过一些 WCF 属性来完成的。
让我们首先看看 service interface
方法,它看起来是这样的,可以看到我们只使用了 WebMessageFormat.Json enum
来指定我们希望资源被序列化为 JSON。
1: [OperationContract]
2: [WebGet(UriTemplate = "/DC/JSON/PeopleList",
3: ResponseFormat = WebMessageFormat.Json)]
4: PersonList GetRootPersonListUsingDataContractJSON();
然后对于实际的服务实现,我们有这个:
1: //Matches [WebGet(UriTemplate = "/DC/JSON/PeopleList",
2: // ResponseFormat = WebMessageFormat.Json)]
3: public PersonList GetRootPersonListUsingDataContractJSON()
4: {
5: return PersonList();
6: }
使用与之前相同的辅助方法。
为了确保这能正常工作,我们可以使用一个小的示例 HTML 页面(包含在演示代码中),我只在 Internet Explorer 7 中进行了测试并使其工作。它应该能在 Firefox 中工作,但我没有花太多时间在这上面,因为这不是我想要说明的重点,我只是向你们展示 RESTful JSON 的工作原理。
1: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2: <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
3: <html>
4: <head>
5: <title>JSON Test</title>
6: <script type="text/javascript">
7:
8: function getXmlHttp()
9: {
10: var xmlHttp;
11: try {
12: xmlHttp = new XMLHttpRequest();
13: }
14: catch(e) {
15: try {
16: xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
17: }
18: catch (e) {
19: try {
20: xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
21: }
22: catch (e) {
23: alert("the sample only works in browsers with Ajax support");
24: return false;
25: }
26: }
27: }
28:
29: return xmlHttp;
30: }
31:
32:
33: var serviceURI = "https://:8085/SomeService/DC/JSON/PeopleList";
34:
35: function getStuff()
36: {
37:
38: var xmlHttp=getXmlHttp();
39: xmlHttp.onreadystatechange = function() {
40: if (xmlHttp.readyState == 4) {
41: alert("xmlHttp.readyState");
42: var result = (eval(xmlHttp.responseText));
43: var person = null;
44: alert(result);
45: for (var i = 0; i < result.length; i++) {
46: person = result[i];
47: alert("Person found, Firstaname : " +
48: person.FirstName + " LastName : " + person.LastName);
49: }
50: }
51:
52: }
53: xmlHttp.open("GET", serviceURI, true);
54: xmlHttp.setRequestHeader("Accept","application/json");
55: xmlHttp.send(null);
56:
57: }
58:
59: </script>
60:
61: </head>
62:
63: <body >
64: <form name="form1" action="javascript:getStuff()">
65: <input type="submit" value="Enter"> </form>
66: </body>
67: </html>
这是运行的演示(同样针对随附的演示代码中包含的简单 WCF 服务控制台宿主应用程序)。
正如我所说,我不是 Web 开发人员,但为了完整起见,我将在后续的文章中向您展示如何在 ASP .NET/IIS 中托管 RESTFul WCF 服务,以及如何使用 ASP .NET AJAX ScriptManager
对象。对我来说,如果我喜欢 Web 开发,这应该是您想要做的方式,因为这样您就可以避免编写所有这些糟糕的 JavaScript AJAX 代码。哎呀。那简直不酷。我知道 ASP .NET AJAX 很庞大,但拜托,那个 xmlHttp.onreadystatechange
太恶心了。