75

I am trying to fulfill this rest api:

public async Task<bool> AddTimetracking(Issue issue, int spentTime)
{
    // POST /rest/issue/{issue}/timetracking/workitem
    var workItem = new WorkItem(spentTime, DateTime.Now);
    var httpContent = new StringContent(workItem.XDocument.ToString());
    var requestUri = string.Format("{0}{1}issue/{2}/timetracking/workitem", url, YoutrackRestUrl, issue.Id);
    var respone = await httpClient.PostAsync(requestUri, httpContent);
    if (!respone.IsSuccessStatusCode)
    {
        throw new InvalidUriException(string.Format("Invalid uri: {0}", requestUri));
    }

    return respone.IsSuccessStatusCode;
}

workItem.XDocument contains the following elements:

<workItem>
  <date>1408566000</date>
  <duration>40</duration>
  <desciption>test</desciption>
</workItem>

I am getting an error from the API: Unsupported Media Type

I really have no idea how to resolve this, help is greatly appreciated. How do I marshall an XML file via a HTTP POST URI, using HttpClient?

Pooven
  • 1,744
  • 1
  • 25
  • 44
bas
  • 13,550
  • 20
  • 69
  • 146
  • You calculate the date as seconds but the example in your link uses msec. – L.B Aug 17 '14 at 19:04
  • @L.B, changed it to miliseconds, no luck. I guess I am doing the wrong thing with the HttpContent. Unfortunately google doesn't help me much yet – bas Aug 17 '14 at 19:08
  • P.S: when I set up firefox poster with the exact same parameters, it works. Not that much luck trying from C#. – bas Aug 17 '14 at 19:10
  • 1
    bas, download [fiddler](http://www.telerik.com/fiddler) and see the difference between firefox and your code.. – L.B Aug 17 '14 at 19:12
  • 1
    @L.B, thx for the tip, I'll give that a go tomorrow. – bas Aug 17 '14 at 19:14

4 Answers4

103

You might want to set the mediaType in StringContent like below:

var httpContent = new StringContent(workItem.XDocument.ToString(), Encoding.UTF8, "text/xml");

OR

var httpContent = new StringContent(workItem.XDocument.ToString(), Encoding.UTF8, "application/xml");
ziddarth
  • 1,796
  • 1
  • 14
  • 14
27

You could use

var respone = await httpClient.PostAsXmlAsync(requestUri, workItem);

https://msdn.microsoft.com/en-us/library/system.net.http.httpclientextensions_methods

Matt Frear
  • 52,283
  • 12
  • 78
  • 86
6

To use Matt Frear's solution you may need to add NuGet Package: Microsoft.AspNet.WebApi.Client

This brings in the extension methods so you can use:

var respone = await httpClient.PostAsXmlAsync<WorkItem>(requestUri, workItem);

This works for the newer .Net 5 so assume will work for asp.net core 3.1.

Saeed Nawaz
  • 69
  • 1
  • 1
0

The accepted answer doesn't include the XML declaration, which may or may not matter. Below is a simple extension method for XDocument that prepends the XML declaration. Otherwise, the technique is identical:

// Extension method
public static string ToStringWithDeclaration(this XDocument doc, string declaration = null)
{
    declaration ??= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
    return declaration + doc.ToString();
}

// Usage (XDocument -> string -> UTF-8 bytes)
var content = new StringContent(doc.ToStringWithDeclaration(), Encoding.UTF8, "text/xml");
var response = await httpClient.PostAsync("/someurl", content);

If you want, you can also skip the step of converting the XDocument to a string (which is UTF-16 encoded), and just go right to UTF-8 bytes:

// Extension method
public static ByteArrayContent ToByteArrayContent(
    this XDocument doc, XmlWriterSettings xmlWriterSettings = null)
{
    xmlWriterSettings ??= new XmlWriterSettings();
    using (var stream = new MemoryStream())
    {
        using (var writer = XmlWriter.Create(stream, xmlWriterSettings))
        {
            doc.Save(writer);
        }
        var content = new ByteArrayContent(stream.GetBuffer(), 0, (int)stream.Length);
        content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
        return content;
    }
}

// Usage (XDocument -> UTF-8 bytes)
var content = doc.ToByteArrayContent();
var response = await httpClient.PostAsync("/someurl", content);

// To view the serialized XML as a string (for debugging), you'd have to do something like:
using var reader = new StreamReader(content.ReadAsStream());
string xmlStr = reader.ReadToEnd();

For a third option, XDocument.Save/XmlWriter can also output to a string via StringBuilder (as shown in the documentation). That is useful if you want a string but need control over formatting (such as whether to use indentation). Just be aware that the XML declaration generated via this method will always say "utf-16" (regardless of XmlWriterSettings.Encoding), so you probably want to specify XmlWriterSettings.OmitXmlDeclaration = true and add the declaration manually afterward. See this question for some discussion of that issue.

MarredCheese
  • 17,541
  • 8
  • 92
  • 91