Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RESTful WCF Service returns a 400 code when sending "raw" XML

Tags:

rest

c#

wcf

I have been banging my head of the wall for two days with this so, hopefully, someone can give me a hand. What I have is a RESTful Web Service that I wrote using WCF; nothing to it really just two methods that accept a single string parameter and also return a string. Both the parameter and return value are straight 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);

}

For the sake of argument lets say that the implementation of both of these methods looks like this:

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

If I goto http: //myuri.com/service.svc/method1/foo the following is wrote to the browser:

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

This works great but if I change the url to: http: //myuri.com/service.svc/method1/<foo > I get a 400 (Bad Request). So I enabled some tracers to see what was going using the following code:

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

As you can see I am using the switch value 'All' to capture every event that happens during the execution of this service. I ran back through a few times using the URL format that works to verify that the tracers were working and they were. I then went to the URL that contained the XML tag foo and recieved the 400 error as expected but when I went back to the log file there had been no additional information appened to the end of it. This leads me to believe that the 400 error is being displayed before the WCF service is ever invoked.

Lastly, I switched the the methods from 'GET' methods to 'POST' methods, wrote a bit of code using WebRequest / WebResponse with the same result. Now I have read some posts talking about using the XmlSerializer on the client side to send the data to the service but that defeats the purpose of this service. While I am using .NET to write the service it is likely that PHP or classic ASP scripts will be connecting to this service and they, obviously, do not have access to the XmlSerializer.

So my million dollar question is this: Is it possible to send a 'raw' XML request to a RESTful webservice developed in WCF and, if so, how?

P.S. The XML coming into and going out of the service is not based on any tangible object it is simply the structure I created to use with this service. The XML coming in is parsed via XPath, the values are placed into a larger XML string, and passed along to an external API. The results from that API are processed and then returned by my RESTful service.

Any help would be greatly appreciated!!

like image 394
dparsons Avatar asked Jan 06 '10 21:01

dparsons


2 Answers

Brett, thanks for the bit of code. Unfortunately I found it in this thread: WCF Rest parameters involving complex types and had attempted it prior to this posting.

In any event, I have solved this problem. Now I would love to say that I had a complete 'Eureka' moment and everything just came together but the fact of the matter is I just started throwing acronymns at Google and one of the SERPs led me to this link: http://blogs.msdn.com/pedram/archive/2008/04/21/how-to-consume-rest-services-with-wcf.aspx

The link itself doesn't directly address the question at hand but it made me think differently about how I was putting together my URI templates. I had read this MSDN article http://msdn.microsoft.com/en-us/library/dd203052.aspx on how to put together a RESTful service. In the example the author provides several different templatesL some which utilize a typical querystring parameter-esque template and some that don't. For whatever reason I choose the template that was void of a typical querystring parameter as can be seen in my original post. So I modified my code a bit and came up with this:

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

Notice that the URI template is the only thing that changed; changing from "/method1/{data}" to "/method1/?xml={data}. I then navigated to http://myuri.com/service.svc/method1/?xml= and viola, everything worked beautifully!

This also was the problem with POST. For whatever reason passing the XML along in the content body, even as a key/value pair, was causing the the 400 error. Using the exact same URI template shown above I opened Fiddler, executed a POST, and the result was 200 OK.

Thanks for the help everyone.

like image 109
dparsons Avatar answered Sep 28 '22 05:09

dparsons


one of main reasons why the original code didn't work was because any xml string data would need to be url encoded if being passed in the url path or query string.

in my opinion, however, if you want the client to be sending you data as xml in to your service method, then it should not be done in the url. The url has an indeterminate maximum length, depending on the browser, the version of iis and also any web proxies that are sitting between the client on the server.

this means sending the data in the body of the request, which means a verb other than GET. So let's use POST.

declare the 'data' parameter as before on the method signature, but take the parameter out of the UriTemplate, and make it WebInvoke (default verb is POST as you know).

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

Then your POST body reques should be formatted as follows:

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

Why the CDATA Section? Think about the target parameter type - string. You want to pass XML in that string. Therefore, you need to make sure that the WCF serializer doesn't think of the data as complex data that is to be read directly. The same would be true if your request format was JSON and you wanted to send a JSON string to the service.

like image 32
Andras Zoltan Avatar answered Sep 28 '22 06:09

Andras Zoltan