REST、WCF 和流:摆脱命名空间






4.17/5 (5投票s)
本文详细介绍了使用 WCF 创建 RESTful Web 服务的方法,
引言
Web 由一些简单的动词和许多模型管理。任何浏览器都可以使用一些简单明确定义的动词访问 Web 上的资源。有人认为这种简单性是其成功的原因。为了将这种简单性引入 SOAP Web 服务世界,引入了 RESTful Web 服务的概念。
.NET Framework 3.5 引入了 WCF 的 REST 主题。在本文中,我们将讨论我们在开发 RESTful Web 服务时遇到的问题。
目标读者
讨论的受众预计将知道如何使用 WCF 开发 RESTful Web 服务。可以在这里找到大量相关资源。
问题陈述
Microsoft 提供了开箱即用的机制,通过这些机制,发布的 XML 可以通过序列化转换为资源对象。这需要使用特定命名空间填充的 XML 结构以供默认序列化使用。客户端(应用程序的客户端或支付者)很少同意具有此类依赖项。他们要求传递 XML,而我们如何处理它就取决于我们自己了。
<Person xmlns="http://schemas.datacontract.org/2004/07/"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<PersonId>55</PersonId>
<PersonName>Sample Name</PersonName>
</Person>
xmlns
标签对大多数客户端来说是不相关的。
本文重点关注任意结构的问题以及如何实现这一点。
使用流的解决方案
原始数据通过 Stream
传输。WCF 允许我们在支持 URL 模板的同时捕获 RESTful Web 服务的流。我们将通过一个 GET
和 POST
的示例演示 Stream
的使用,以及任意 XML 结构。
代码
此时,我们假设已经创建了一个 WCF 服务解决方案,并且其 web.config 已被修改为用作 RESTful 应用程序。我们将只讨论涉及 Stream
的代码片段。
资源结构
本次讨论,我们将假设我们感兴趣的资源是 Person
,其结构如下:
<Person> <!--The container for Person entity-->
<Id>23</Id> <!--The Id of the person -->
<Name>Gaurav Verma</Name> <!--The name of the person-->
</Person>
此资源通过 URL GET
访问: https://:Port/.../Person/{SessionId}/{PersonId}。
通过 POST
添加新人员资源: https://:Port/.../Person/{SessionId}/
附带必需的 Person
XML
<Person> <!--The container for Person entity-->
<Name>Gaurav Verma</Name> <!--The name of the person-->
</Person>
在此系统中,我们只能在获得会话 ID 后开始工作,该会话 ID 通过以下方式获取:
GET: https://:Port/.../Session
引入会话的概念是为了演示如何使用 URL 中的变量来使用 stream
。
接口
对于 GET
请求,该接口将类似于任何其他 RESTful 应用程序,但对于 POST
请求,第一个元素始终是 stream
。
[ServiceContract]
public interface IService
{
[OperationContract]
[WebGet(UriTemplate = "/Sessions", ResponseFormat = WebMessageFormat.Xml)]
Stream GetSession();
[OperationContract]
[WebGet(UriTemplate = "/Person/{sessionId}/{PersonId}",
ResponseFormat = WebMessageFormat.Xml)]
Stream GetPerson(string sessionId,string personId);
[OperationContract]
[WebInvoke(UriTemplate = "/Person/{sessionId}", BodyStyle =
WebMessageBodyStyle.Bare, Method = "POST")]
Stream SavePerson(Stream input,string sessionId);
}
此接口请求一个输入 stream
并返回一个输出 stream
。应注意,第一个元素始终是 stream
。
让我们详细说明每个接口。
[OperationContract]
[WebInvoke(UriTemplate = "/Person/{sessionId}", BodyStyle =
WebMessageBodyStyle.Bare, Method = "POST")]
Stream SavePerson(Stream input,string sessionId);
其他两个 Get
方法返回类型为 Stream
。我们使用 stream
是因为无论我们创建什么 string
并在 stream
中发送回,客户端都会收到一个完全相同的 string
。
[OperationContract]
[WebGet(UriTemplate = "/Sessions", ResponseFormat = WebMessageFormat.Xml)]
Stream GetSession();
[OperationContract]
[WebGet(UriTemplate = "/Person/{sessionId}/{PersonId}",
ResponseFormat = WebMessageFormat.Xml)]
Stream GetPerson(string sessionId,string personId);
方法体
方法体必须能够将 Stream
转换为 XML 结构,反之亦然。让我们稍微提前了解一下这些原始 stream
的结构。
在此示例中,我们将 Person
实体作为 XML 发送到输入参数字段,并收到原始文本,格式为 parameters=%3CPerson%3E%3CName%3ESample+Name%3C%2FName%3E%3C%2FPerson%3E。
string
已为传输进行编码。如果我们从中提取查询 string
参数并解码参数 string
,我们将获得以下格式的输入:
<Person><Name>Sample Name</Name></Person>
我们想要的结构。这就是我们将要处理的结构。
POST 方法:捕获流并将其转换为实体
Post
方法接收一个 Stream
输入和一个会话 ID。该方法将 stream
转换为可读的 XML 人员实体,并保存结果。
public Stream SavePerson(Stream input, string sessionId)
{
//We may validate the session id here
//The input is in a stream reader
StreamReader streamReader = new StreamReader(input);
// The raw string is read
// The raw string is of the format
//parameters=%3CPerson%3E%3CName%3ESample+Name%3C%2FName%3E%3C%2FPerson%3E
string rawString = streamReader.ReadToEnd();
streamReader.Dispose();
//Convert this into a more readable format
NameValueCollection queryString = HttpUtility.ParseQueryString(rawString);
//and then extract out the parameters
//<Person><Name>Sample Name</Name></Person>
string parameters = queryString["parameters"];
//The String is parsed and a Person entity is created
string personXML = String.Format(
"<Result><Status>Person Created with Id {0}</Status><Input>{1}</Input></Result>",
555, parameters);
//Convert the result back into a Stream
// A stream is not modified by the .NET Framework
Encoding encoding = Encoding.GetEncoding("ISO-8859-1");
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";
byte[] returnBytes = encoding.GetBytes(personXML);
return new MemoryStream(returnBytes);
}
GET 方法:将实体转换为 XML
就输入解析而言,Get
方法的工作很简单,但 Get
方法必须返回一个 Stream
以确保不发送不必要的命名空间。
public Stream GetPerson(string sessionId,string personId)
{
// We may validate the Session Id and then return a Person from the data store
// The required XML is converted into an Entity
string personXml = String.Format("<Person><Id>{0}</Id><Name>{1}</Name></Person>",
personId,"Name of "+personId);
Encoding encoding = Encoding.GetEncoding("ISO-8859-1");
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";
//The results are sent back.
byte[] returnBytes = encoding.GetBytes(personXml);
return new MemoryStream(returnBytes);
}
编码
请注意,已将编码设置为 text/plain 以确保结构保持不变。
测试客户端
在涉及 REST 并需要与第三方集成的各种应用程序中,我发现使用简单的 Web 浏览器进行测试非常有用。这证明即使是最简单的应用程序也可以访问 Web 服务。如果我们能够使用 Web 浏览器访问服务器资源,那么任何问题都必须出在网络访问或访问应用程序的客户端代码中。
本示例使用一组简单的网页来测试 REST API。
GET 客户端
这是一个简单的 Get
,所以我们只需要运行包含该服务的解决方案并浏览到 URL 即可。
https://:11227/RestWebService/Service.svc/Sessions
用于获取会话 ID,我们得到的结果如下:
<Session>ddd621f7-b535-4f48-b6da-52f6ad7bc0d1</Session>
或
https://:11227/RestWebService/Service.svc/
Person/ddd621f7-b535-4f48-b6da-52f6ad7bc0d1/35
用于获取 Person
。
我们得到结果
<Person><Id>35</Id><Name>Name of 35</Name></Person>
POST 客户端
Post 客户端是一个简单的 HTTP 表单,它向 URL https:///.../Person 提交,并在文本框中包含必要的输入。
客户端的 HTML 表单是:
<form
action='https://:11227/
RestWebService/Service.svc/Person/ddd621f7-b535-4f48-b6da-52f6ad7bc0d1'
method="POST" runat="server" >
Parameters :<input type ="TextBox" id = "parameters" name="parameters"
runat="server" Height="120px" Width="1000px" ></input>
<input type="submit"/>
</form>
我们在输入框中输入值,如下所示:
<Person><Name>Sample Name</Name></Person>
然后得到结果:
<Result><Status>Person Created with Id 555</Status></Result>
结论
REST 是一种旨在实现协议无关的机制。如果其资源结构可以任意定制,那将大有裨益。上述演示的机制在 .NET 环境中实现了 URL 映射和任意资源结构的优势。
代码缺少许多在企业应用程序中可能需要的完善之处,例如使用 XSD 对输入 XML 进行验证等。它甚至缺少诸如将资源保存到数据库之类的功能。这样做是为了确保我们将重点放在 REST 和 Stream
的代码上,而不是其他支持代码上。
历史
- 2009 年 4 月 28 日:初始发布