65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (5投票s)

2009年4月28日

CPOL

5分钟阅读

viewsIcon

55147

downloadIcon

746

本文详细介绍了使用 WCF 创建 RESTful Web 服务的方法, 在其中开发人员可以控制 XML 结构

引言

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 日:初始发布
© . All rights reserved.