3

I have an ASP.NET MVC4 website implementing a REST API, which I'm consuming from a client application. My ApiController methods take and return complex objects, as XML.

I recently discovered RestSharp, and have begun moving my client project over to that. However, I'm having real problems with it. It seems to almost work - it's so close that I can almost taste success - but I just can't get it to work 100%.

The objects I'm passing across the wire look something like this:

// The object I'm passing across the wire
public class Example
{
    bool IsActive { get; set; }
    string Name { get; set; }
}

My ApiController method looks like this:

// My ApiController method
public HttpResponseMessage PostExample(Example example)
{
    if (ModelState.IsValid)
    {
        db.Examples.Add(example);

        db.SaveChanges();

        HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, example);

        return response;
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }
}

The problem occurs when I try to POST an object to my website, like this:

var example = new Example () { IsActive = true, Name = "foo" };

var request = new RestSharp.RestRequest("/api/example", RestSharp.Method.POST);

request.AddBody(example, XmlNamespace);

var client = new RestClient();

client.BaseUrl = "foo.com";

var response = client.Execute<Example>(request);

The code above does hit the PostExample method in my ApiController, and it has an Example object as the parameter. However the values of the properties of the Example object are not the same as I passed to the Execute method! In one case, the IsActive member was false instead of true, although I also saw a case where the Name member was null where it should have had a value.

I did some investigation using Fiddler, and it seems that the correct values are being created in the XML that RestSharp generates. However, the XML is not quite in the same format that the web server emits when doing a GET. The differences are subtle, but seem to make the difference between it working and not working. The framework at the web server end seems to be sensitive to these formatting differences, and is mis-interpreting the XML as a result.

Here's the XML I get from RestSharp:

<Example xmlns="http://schemas.datacontract.org/2004/07/ExampleNamespace">
  <Name>foo</Name>
  <IsActive>true</IsActive>
</Example>

This is what I get when doing a GET on the webserver (or when serializing using the DataContractSerializer, which is what I was previously doing):

<Example xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ExampleNamespace">
  <IsActive>true</IsActive>
  <Name>foo</Name>
</TagDto>

The RestSharp version has the following differences from the DataContractSerializer's version:

  1. Fields are in a different order
  2. RestSharp doesn't include the extra namespace XMLSchema-instance namespace
  3. DataContractSerializer doesn't include any spaces or line-breaks (I added those above for readability)

I'm surprised that any of those make much of a difference, but clearly they do. Note also that until I added an explicit namespace in the AddBody call, this was missing in the generated XML (obviously), and the Example object passed into my ApiController was null.

Anyway, I noticed that RestSharp allows you to override the serializer, and provides a way to use the .NET XML serializer. I tried using that (to no avail).

This is what I added before the call to AddBody:

request.XmlSerializer = new RestSharp.Serializers.DotNetXmlSerializer(XmlNamespace);

..and this is what I get out:

<?xml version="1.0" encoding="utf-8"?>
<Example>
  <Name>foo</Name>
  <IsActive>true</IsActive>
</Example>

This is clearly no good, not least because it starts with an XML declaration, which I imagine would cause problems. There's no way to turn that off, because the RestSharp derived class provides no way to do so. Also, there's no namespace - and I can't get one to appear in the output no matter how I try to set the namespace in RestSharp (in the constructor for the DotNetXmlSerializer, by setting the Namespace member, or by passing in a namespace to AddBody). To my eyes, this class is nothing more than a trap.

It looks like my only option is to create my own serializer class and use the DataContractSerializer internally. Is that right, or am I missing something?

(BTW, I can set the RequestFormat of the request to JSON and it just works - but I'd still like to know how to get this working with XML).

Gary McGill
  • 26,400
  • 25
  • 118
  • 202
  • Is it a must for you to use RestSharp? – cuongle Aug 16 '12 at 11:44
  • @CuongLe: ha! I originally started out rolling my own, using DataContractSerializer, and was then persuaded to use RestSharp by a colleague. I could switch to another solution, but I've now invested so much time in trying to get RestSharp to work that I'm reluctant to do so. As I hinted in my answer, I eventually switched to using JSON rather than XML, so this question remains mostly for the benefit of others. – Gary McGill Aug 16 '12 at 12:35
  • @Gary, I think JSON is where a library like RESTSharp really excels too and you'll get a small perf gain in terms of performance too. – leon.io Aug 20 '12 at 08:21

2 Answers2

2

I've had some issues with the AddBody calls not properly serializing JSON values, so there might be some similarity to your problem. Instead of AddBody, you could try:

request.AddParameter("text/xml", xmlAsString, ParameterType.RequestBody);

If that works, you could look to see about changing the second parameter to be the xml object and see if the serializer does what you want.

The other option could be the XmlMediaTypeFormatter.ReadFromStreamAsync isn't properly picking up a proper serializer; you could try overriding that function.

Chris
  • 772
  • 3
  • 8
1

The issue above is because WebAPI is using the DataContractSerializer (as opposed to the XmlSerializer which is what you're after). To switch this around modify Global.asax as follows.

 var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
 xml.UseXmlSerializer = true;

However, I suggest you use the RESTSharp formatters for WebAPI (instead of using the .Net formatters). This is particularly useful if you're DTO's have circular references (the .net fx serializers don't handle this too gracefully).

In Global.asax, modify the formatters by putting in

GlobalConfiguration.Configuration.Formatters.XmlFormatter = //RestSharp XML serializer here

A quick overview of serialization in WebAPI is here and worth a browse

leon.io
  • 2,779
  • 1
  • 18
  • 26