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

10,392

Solution 1

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.

Solution 2

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.

Solution 3

I don't believe you will be able to pass raw XML in the URL but you can do it in code. I have written clients for RESTful web services that run on the Compact Framework that have no problem deserializing objects to raw XML and sending it to the service via HttpWebRequest and HttpWebResponse.

If you're doing a GET, you would just build up the URL in code. If you are doing a POST, you can attach the XML as a byte array (code below is .Net but surely you can do something similar in 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;
}

Solution 4

From my understanding WCF services providing RESTful APIs do not allow large payloads. In fact, by default, WCF services are meant for small messages to be sent back and forth.

From the machine I was testing, I was unable to get an XML payload (XML file) returned from a WCF service exposed through a REST-ful web endpoint that was larger than 2.7MB.

From digging and hunting through LOTS of WCF documentation, the final conclusion I came to was that WCF is designed for small messages when in buffered mode. When switched to streaming mode then large messages can be passed back and forth.

However, if you are exposing WCF services through IIS in a RESTful manner, then you cannot have a streamed response, since that is not supported. Or at least I could never figure it out.

I wish I had a great code sample answer for you, but from what I could discern from my own experimentation was that there is no way to return large XML payloads from a WCF service exposed through an IIS endpoint.

My conclusion is that WCF is really a networking solution that is being retro-fitted (poorly) into IIS to try to provide webservices. I would encourage using ASP .NET MVC instead to create RESTful webservices without using WCF at all.

Share:
10,392
dparsons
Author by

dparsons

Updated on June 09, 2022

Comments

  • dparsons
    dparsons almost 2 years

    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!!

  • Laguna
    Laguna about 12 years
    I have been waiting for this answer for 2 years. the [CDATA] mention is invaluable. Previously I was forced to pass encoded xml data in JSON response format to work around this. Now this is much more elegant! what was I thinking?!
  • Laguna
    Laguna about 12 years
    Now my question is how do I vote up this twice? and why is this not marked as an answer?
  • Andras Zoltan
    Andras Zoltan about 12 years
    @Laguna - lol; the only issue you then have with this is if your posted XML has to have CDATA sections in it too! Wikipedia has details on how to handle this en.wikipedia.org/wiki/CDATA
  • Laguna
    Laguna about 12 years
    thanks again! your comment is just in time! would you take a look at my new question? stackoverflow.com/questions/9200300/…
  • Alexey Zimarev
    Alexey Zimarev over 11 years
    The thread you mentioned contains a description of all possible ways to invoke a WCF service and it is really great. Basically I implemented the plain XML request with RESTful via WebInvoke. WebGet is REST with GET and you also can support JSON response, more like Web API in MVC 4.
  • evilfish
    evilfish almost 11 years
    Since you are now sending XML directly in the request element, wouldn't that limit the size of the xml you can send?
  • dparsons
    dparsons almost 11 years
    I suppose but with the given solution I never ran into a situation where that was an issue. I imagine that if I had ran into a problem I could have probably tweaked the maxQueryStringLength in the config file to get around it.