RESTful WCF服务在发送“原始”XML时返回400代码

时间:2010-01-06 21:51:35

标签: c# rest wcf

我一直在用我的头撞墙两天,希望有人可以帮我一把。我所拥有的是使用WCF编写的RESTful Web服务;实际上只有两个接受单个字符串参数并返回字符串的方法。参数和返回值都是直接的XML。

[ServiceContract]
public interface IService
{
    [OperationContract]
    [WebGet(UriTemplate = "/method1/{data}", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Xml, RequestFormat = WebMessageFormat.Xml)]
    string Method1(string data);

    [OperationContract]
    [WebGet(UriTemplate = "/method2/{data}", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Xml, RequestFormat = WebMessageFormat.Xml)]
    string Method2(string data);

}

为了论证,我们可以说这两种方法的实现如下:

public string Method1(string data)
{
    return string.Format("You entered: {0}", data);
}

如果我转到http://myuri.com/service.svc/method1/foo,则会向浏览器写入以下内容:

  <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You 
    entered: foo</string> 

这很有效但如果我将网址更改为:http://myuri.com/service.svc/method1/<foo&gt;我得到400(不良请求)。所以我使用以下代码启用了一些跟踪器来查看输出内容:

<system.diagnostics>
<sources>
  <source name="System.ServiceModel"
          switchValue="All">
    <listeners>
      <add name="traceListener"
          type="System.Diagnostics.XmlWriterTraceListener"
          initializeData= "c:\Traces.svclog" />
    </listeners>
  </source>
</sources>

正如您所看到的,我正在使用切换值'All'来捕获执行此服务期间发生的每个事件。我使用URL格式跑回了几次,这种格式可以验证跟踪器是否正常工作。然后我转到包含XML标记foo的URL并按预期收到400错误,但是当我回到日志文件时,没有附加信息附加到它的末尾。这使我相信在调用WCF服务之前显示400错误。

最后,我将方法从'GET'方法切换到'POST'方法,使用WebRequest / WebResponse写了一些代码,结果相同。现在我已经阅读了一些帖子,谈论在客户端使用XmlSerializer将数据发送到服务但是这违背了这项服务的目的。当我使用.NET编写服务时,很可能PHP或经典ASP脚本将连接到此服务,显然,他们无法访问XmlSerializer。

所以我的百万美元问题是这样的:是否可以向WCF中开发的RESTful Web服务发送“原始”XML请求,如果是,如何?

P.S。 进入和退出服务的XML不是基于任何有形对象,它只是我创建用于此服务的结构。进入的XML通过XPath进行解析,将值放入更大的XML字符串中,并传递给外部API。该API的结果将被处理,然后由我的RESTful服务返回。

非常感谢任何帮助!!

4 个答案:

答案 0 :(得分:3)

布雷特,谢谢你的代码。不幸的是,我在这个帖子中找到了它:WCF Rest parameters involving complex types并在此帖之前尝试过它。

无论如何,我已经解决了这个问题。现在我想说我有一个完整的'Eureka'时刻,所有事情都在一起,但事实是我刚刚开始向Google投放缩写词,其中一个SERP引导我进入这个链接:http://blogs.msdn.com/pedram/archive/2008/04/21/how-to-consume-rest-services-with-wcf.aspx

链接本身并没有直接解决手头的问题,但它使我对如何整理我的URI模板有不同的看法。我已经阅读了这篇关于如何组建RESTful服务的MSDN文章http://msdn.microsoft.com/en-us/library/dd203052.aspx。在该示例中,作者提供了几个不同的模板,其中一些模板使用典型的查询字符串参数模式,而另一些则不使用。无论出于何种原因,我选择的模板都没有典型的查询字符串参数,这可以在我的原帖中看到。所以我修改了我的代码并想出了这个:

[OperationContract] 
[WebGet(UriTemplate = "/method1/?xml={data}", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Xml, RequestFormat = WebMessageFormat.Xml)] 
string Method2(string data); 

请注意,URI模板是唯一改变的模板;从“/ method1 / {data}”更改为“/ method1 /?xml = {data}。然后我导航到http://myuri.com/service.svc/method1/?xml=和中提琴,一切都很美妙!

这也是POST的问题。无论出于何种原因,在内容体中传递XML,即使作为键/值对,也会导致400错误。使用上面显示的完全相同的URI模板,我打开了Fiddler,执行了一个POST,结果是200 OK。

感谢大家的帮助。

答案 1 :(得分:3)

原始代码不起作用的主要原因之一是因为如果在url路径或查询字符串中传递任何xml字符串数据,则需要对其进行url编码。

但是,在我看来,如果您希望客户端将数据作为xml发送到您的服务方法,则应该在网址中。该URL具有不确定的最大长度,具体取决于浏览器,iis的版本以及位于服务器上的客户端之间的任何Web代理。

这意味着在请求正文中发送数据,这意味着GET以外的动词。所以让我们使用POST。

如前所述在方法签名上声明'data'参数,但是将参数从UriTemplate中取出,并使其成为WebInvoke(默认动词是POST,如你所知)。

[WebInvoke(UriTemplate = "/method1", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Xml, RequestFormat = WebMessageFormat.Xml)]  
string Method2(string data);

然后你的POST正文请求的格式如下:

<data><![CDATA[data xml goes in here]]></data>

CDATA部分为何?想想目标参数类型 - 字符串。您想要在该字符串中传递XML。因此,您需要确保WCF序列化程序不会将数据视为要直接读取的复杂数据。如果您的请求格式是JSON并且您想要向服务发送JSON字符串,那么情况也是如此。

答案 2 :(得分:1)

我不相信您能够在URL中传递原始XML,但您可以在代码中执行此操作。我已经为在Compact Framework上运行的RESTful Web服务编写了客户端,可以将对象反序列化为原始XML并通过HttpWebRequest和HttpWebResponse将其发送到服务。

如果你正在进行GET,你只需在代码中建立URL。如果你正在进行POST,你可以将XML作为一个字节数组附加(下面的代码是.Net,但你肯定可以在PHP中做类似的事情)。

private HttpWebRequest DoInvokeRequest<T>(string uri, string method, T requestBody)
{
    string destinationUrl = _baseUrl + uri;
var invokeRequest = WebRequest.Create(destinationUrl) as HttpWebRequest;
if (invokeRequest == null)
    return null;

invokeRequest.Method = method;
invokeRequest.ContentType = "text/xml";

byte[] requestBodyBytes = ToByteArray(requestBody);
invokeRequest.ContentLength = requestBodyBytes.Length;
AddRequestHeaders(invokeRequest);

using (Stream postStream = invokeRequest.GetRequestStream())
    postStream.Write(requestBodyBytes, 0, requestBodyBytes.Length);


invokeRequest.Timeout = 60000;
return invokeRequest;
}

private static byte[] ToByteArray<T>(T requestBody)
{
    byte[] bytes;
using (var s = new MemoryStream())
{
    var serializer = new XmlSerializer(typeof (T));
    serializer.Serialize(s, requestBody);
    bytes = s.ToArray();
}
return bytes;
}

答案 3 :(得分:1)

据我所知,提供RESTful API的WCF服务不允许大型有效负载。实际上,默认情况下,WCF服务用于来回发送小消息。

在我测试的机器上,我无法从通过大于 2.7MB 的REST-ful Web端口公开的WCF服务返回XML有效负载(XML文件)。 / p>

从挖掘和搜索到大量的WCF文档,我得出的最终结论是,WCF是为缓冲模式下的小消息而设计的。当切换到流式模式时,可以来回传递大型消息。

但是,如果您以RESTful方式通过IIS公开WCF服务,则无法获得流式响应,因为不支持。或者至少我永远无法弄明白。

我希望我能为您提供一个很棒的代码示例答案,但是从我自己的实验中可以看出,无法从通过IIS端点公开的WCF服务返回大型XML有效负载

我的结论是,WCF实际上是一个网络解决方案,正在改进(很差)进入IIS以尝试提供Web服务。我鼓励使用ASP .NET MVC来创建RESTful webservices而不使用WCF。