12
votes

I'm missing a trick with the new webapi - I'm trying to submit an xml string through a post request and not having much luck.

The front end is using jQuery like this:

    $(document = function () {
    $("#buttonTestAPI").click(function () {

        var d = " <customer><customer_id>1234</customer_id></customer>";
        $.ajax({
            type: 'POST',
            contentType: "text/xml",
            url: "@Url.Content("~/api/Customer/")",
            data: d,
            success: function (result) {
                var str = result;
                $("#output").html(str);
            }
        });
    });
});

My controller is pretty simple at the moment - just the default for the post action - trying to return what was passed in:

    public string Post(string value)
    {
        return value;
    }

However, "value" is repeatedly null. The odd thing is, when I change my data in the jquery to be something like this:

d = "<customer_id>1234</customer_id>";

Then I get "value" in my controller as 1234.

How can I get access to the more complex xml string in my controller?

4

4 Answers

18
votes

The following will allow you to read a raw XML message via a POST to a Web API method:

public void PostRawXMLMessage(HttpRequestMessage request)
{
   var xmlDoc = new XmlDocument();
   xmlDoc.Load(request.Content.ReadAsStreamAsync().Result);   
}

You can debug and inspect the body, headers, etc. and will see the raw XML posted. I used Fiddler's Composer to make a HTTP POST and this works well.

13
votes

You are sending content type of text/xml but you have defined your parameter as string. Ideally your XML should be mapped to a class so that it can be deserialised.

So if you need raw xml then it is not supported yet. Web API currently is geared up for serialization MediaTypeFormatters and missing simple type formatters but they can easily built.

This one is a minimal implementation of such formatter supporting only read in your case and based on beta installer (and not nightly source code since it has substantially changed):

public class TextMediaTypeFormatter : MediaTypeFormatter
{
    public TextMediaTypeFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
    }

    protected override bool CanReadType(Type type)
    {
        return type == typeof (string);
    }

    protected override System.Threading.Tasks.Task<object> OnReadFromStreamAsync(Type type, Stream stream, 
        HttpContentHeaders contentHeaders, 
        FormatterContext formatterContext)
    {
        var taskCompletionSource = new TaskCompletionSource<object>();
        try
        {
            var memoryStream = new MemoryStream();
            stream.CopyTo(memoryStream);
            var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
            taskCompletionSource.SetResult(s);
        }
        catch (Exception e)
        {
            taskCompletionSource.SetException(e);           
        }
        return taskCompletionSource.Task;
    }
}

And to use it, just add it to formatters collection:

GlobalConfiguration.Configuration.Formatters.Insert(0, new TextMediaTypeFormatter());
6
votes

Anyone looking for an updated version of Aliostad's answer above from the beta release to the RC of asp.net mvc 4 web api (minor changes resulted in a slight rework for me).

public class TextMediaTypeFormatter : MediaTypeFormatter
{

    public TextMediaTypeFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
    }

    public override bool CanReadType(Type type)
    {
        if (type == typeof(String))
            return true;
        else
            return false;
    }

    public override bool CanWriteType(Type type)
    {
        if (type == typeof(String))
            return true;
        else
            return false;

    }

    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
    {
        var taskCompletionSource = new TaskCompletionSource<object>();
        try
        {
            var memoryStream = new MemoryStream();
            readStream.CopyTo(memoryStream);
            var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
            taskCompletionSource.SetResult(s);
        }
        catch (Exception e)
        {
            taskCompletionSource.SetException(e);
        }
        return taskCompletionSource.Task;
    }
}
0
votes

What solved the issue for me was adding this:

static SubscriberController()
{
    //Needed for xml deserialization to work
    var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
    xml.UseXmlSerializer = true;
}

(SubscriberController is my class which extends ApiController, and the above is a static constructor, so it will run once).

Not sure if it also necessary, but I added the [FromBody] attribute to my parameter, like so:

public async Task<HttpResponseMessage> SynchronizeImax( [FromBody] SynchronizeRequest synchronizeRequest )
{
    //...
}

The great thing about doing it this way is that you can seamlessly handle both XML and JSON input.