2

What is the proper way to post an XmlDocument to a web-server? Here is the skeleton function:

public static void PostXml(XmlDocument doc, String url)
{ 
   //TODO: write this
}

Right now i use:

//Warning: Do not use this PostXml implmentation
//It doesn't adjust the Xml to match the encoding used by WebClient
public static void PostXml(XmlDocument doc, String url)
{
   using (WebClient wc = new WebClient())
   {
      wc.UploadString(url, DocumentToStr(doc));
   }
}

Where DocumentToStr is a perfectly valid and correct method:

/// <summary>
/// Convert an XmlDocument to a String
/// </summary>
/// <param name="doc">The XmlDocument to be converted to a string</param>
/// <returns>The String version of the XmlDocument</returns>
private static String DocumentToStr(XmlDocument doc)
{
    using (StringWriter writer = new StringWriter())
    {
       doc.Save(writer);
       return writer.ToString();
    }
}

The problem with my implementation of PostXml is that it posts the String exactly as is. This means that (in my case) the http request is:

POST https://stackoverflow.com/upload.php HTTP/1.1
Host: stackoverflow.com
Content-Length: 557
Expect: 100-continue

<?xml version="1.0" encoding="utf-16"?>
<AccuSpeedData MACAddress="00252f21279e" Date="2010-10-07 10:49:41:768">
  <Secret SharedKey="1234567890abcdefghijklmnopqr" />
  <RegisterSet TimeStamp="2010-10-07 10:49:41:768">
    <Register Address="total:power" Type="Analog" Value="485" />
    <Register Address="total:voltage" Type="Analog" Value="121.4" />
    <Register Address="total:kVA" Type="Analog" Value="570" />
  </RegisterSet>
</AccuSpeedData>

You'll notice that the xml declaration has an incorrect encoding:

<?xml version="1.0" encoding="utf-16"?>

The WebClient is not sending the request in utf-16 unicode, that was how Strings in .NET are stored. i don't even know the encoding used by the WebClient.


The http post of the xml needs to be properly encoded, which normally happens during a call to:

Save(textWriter)

During a call to Save the XmlDocument object will adjust the xml-declaration based on Encoding of the TextWriter it is being asked to save to. Unfortunately WebClient doesn't expose a TextWriter that i can save the XmlDocument to.

See also

Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219

2 Answers2

0

There's this solution. Subclass StringWriter:

public class StringWriterWithEncoding : StringWriter
{
  Encoding encoding;

  public StringWriterWithEncoding (StringBuilder builder, Encoding encoding) :base(builder)
  {
    this.encoding = encoding;
  }

  public override Encoding Encoding
  {
    get { return encoding; }
  }
}
Bryce Fischer
  • 5,336
  • 9
  • 30
  • 36
  • That guy is intentionally mangling the string, then wondering why the string is invalid. Following the link the he includes, one of the responders is correct when he says, "Why do you need UTF-8 encoded string? In-memory strings are unicode (UTF-16); I don't know any other way to make them UTF-8 without writing them to file or a memory stream." A `String` *is* UTF-16, trying to stuff UTF-8 bytes into it is completely invalid. – Ian Boyd Oct 07 '10 at 20:19
0

Why not use an XmlWriter for which you've specified an encoding. XmlWriter requires an underlying stream, but you can just give it a MemoryStream.

As for which encoding to use, why not just pass the WebClient.Encoding? :

public static void PostXml(XmlDocument doc, String url)
{
    using (WebClient wc = new WebClient())
    {
        wc.UploadData(url, DocumentToData(doc, wc.Encoding));
    }
}

private static byte[] DocumentToData(XmlDocument doc, Encoding encoding)
{
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Encoding = encoding;
    settings.Indent = true;

    using (MemoryStream s = new System.IO.MemoryStream())
    {
        using (XmlWriter writer = XmlWriter.Create(s, settings))
        {
            doc.Save(writer);

            // You could make the return type string, but why make 
            // the web client round trip it back to bytes again?
            // return encoding.GetString(s.GetBuffer());

            return s.GetBuffer();
        }
    }
}

I haven't tested this in a real application, so let me know if I'm missing something here.

Andrew Brown
  • 4,086
  • 1
  • 24
  • 21