23

I am using a routine that serializes <T>. It works, but when downloaded to the browser I see a blank page. I can view the page source or open the download in a text editor and I see the xml, but it is in UTF-16 which I think is why browser pages show blank?

How do I modify my serializer routine to return UTF-8 instead of UTF-16?

The XML source returned:

<?xml version="1.0" encoding="utf-16"?>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <string>January</string>
  <string>February</string>
  <string>March</string>
  <string>April</string>
  <string>May</string>
  <string>June</string>
  <string>July</string>
  <string>August</string>
  <string>September</string>
  <string>October</string>
  <string>November</string>
  <string>December</string>
  <string />
</ArrayOfString>

An example call to the serializer:

DateTimeFormatInfo dateTimeFormatInfo = new DateTimeFormatInfo();
var months = dateTimeFormatInfo.MonthNames.ToList();

string SelectionId = "1234567890";

return new XmlResult<List<string>>(SelectionId)
{
    Data = months
};

The Serializer:

public class XmlResult<T> : ActionResult
{
    private string filename = DateTime.Now.ToString("ddmmyyyyhhss");

    public T Data { private get; set; }

    public XmlResult(string selectionId = "")
    {
        if (selectionId != "")
        {
            filename = selectionId;
        }
    }

    public override void ExecuteResult(ControllerContext context)
    {
        HttpContextBase httpContextBase = context.HttpContext;
        httpContextBase.Response.Buffer = true;
        httpContextBase.Response.Clear();

        httpContextBase.Response.AddHeader("content-disposition", "attachment; filename=" + filename + ".xml");
        httpContextBase.Response.ContentType = "text/xml";

        using (StringWriter writer = new StringWriter())
        {
            XmlSerializer xml = new XmlSerializer(typeof(T));
            xml.Serialize(writer, Data);
            httpContextBase.Response.Write(writer);
        }
    }
}
rwkiii
  • 5,716
  • 18
  • 65
  • 114
  • I think this article gives you what you are looking for: http://stackoverflow.com/questions/22453036/xmlserializer-change-encoding – jtm001 Sep 08 '14 at 19:35
  • _"it is in UTF-16 which I think is why browser pages show blank?"_ I see no reason to think that. Investigate your file, what encoding is it actually? Any BOM code at the start? etc. – H H Sep 08 '14 at 19:35

3 Answers3

33

You can use a StringWriter that will force UTF8. Here is one way to do it:

public class Utf8StringWriter : StringWriter
{
    // Use UTF8 encoding but write no BOM to the wire
    public override Encoding Encoding
    {
         get { return new UTF8Encoding(false); } // in real code I'll cache this encoding.
    }
}

and then use the Utf8StringWriter writer in your code.

using (StringWriter writer = new Utf8StringWriter())
{
    XmlSerializer xml = new XmlSerializer(typeof(T));
    xml.Serialize(writer, Data);
    httpContextBase.Response.Write(writer);
}

answer is inspired by Serializing an object as UTF-8 XML in .NET

Community
  • 1
  • 1
Yishai Galatzer
  • 8,791
  • 2
  • 32
  • 41
  • Is overriding the Encoding free of unwanted side effects? I am not aware of any negative implications this might have, but I'd have a bad feeling about it... – NobodysNightmare Sep 08 '14 at 19:51
  • None that I'm aware of, i've used it in the past in many scenarios. But for a server we don't use a stringwriter for this scenario at all, because it will double buffer unnecessarily. This is what we do in MVC vNext (and similarly in Web API) https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs#L58 https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs#L72 – Yishai Galatzer Sep 08 '14 at 19:54
  • Yishai, NobodysNightmare's answer works to do what I needed. I tried his answer before seeing yours. Maybe you pointed me in the right direction also. Thank you for taking the time to try to help. – rwkiii Sep 08 '14 at 20:02
  • When using this technique, you need to implement a default constructor as well otherwise you will see errors. – ITExpert Aug 16 '19 at 07:46
  • @ITExpert thanks for the pointers, It likely be even more helpful for other users if you can expand on the previous comment as to why that is necessary, or what the error is. – Yishai Galatzer Aug 21 '19 at 23:20
11

Encoding of the Response

I am not quite familiar with this part of the framework. But according to the MSDN you can set the content encoding of an HttpResponse like this:

httpContextBase.Response.ContentEncoding = Encoding.UTF8;

Encoding as seen by the XmlSerializer

After reading your question again I see that this is the tough part. The problem lies within the use of the StringWriter. Because .NET Strings are always stored as UTF-16 (citation needed ^^) the StringWriter returns this as its encoding. Thus the XmlSerializer writes the XML-Declaration as

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

To work around that you can write into an MemoryStream like this:

using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
{
    XmlSerializer xml = new XmlSerializer(typeof(T));
    xml.Serialize(writer, Data);

    // I am not 100% sure if this can be optimized
    httpContextBase.Response.BinaryWrite(stream.ToArray());
}

Other approaches

Another edit: I just noticed this SO answer linked by jtm001. Condensed the solution there is to provide the XmlSerializer with a custom XmlWriter that is configured to use UTF8 as encoding.

Athari proposes to derive from the StringWriter and advertise the encoding as UTF8.

To my understanding both solutions should work as well. I think the take-away here is that you will need one kind of boilerplate code or another...

Community
  • 1
  • 1
NobodysNightmare
  • 2,992
  • 2
  • 22
  • 33
  • 1
    The downside of this answer is that for large XML responses, you are now writing them all into memory which will leads to potentially unwanted large memory consumption, and if you exceed 85KB your response will go into the large object heap. When that happens often your app might start freezing during garbage collection. – Yishai Galatzer Jan 21 '16 at 17:59
  • [".NET uses the UTF-16 encoding ... to represent characters and strings"](https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-encoding) (the citation) – mlhDev Oct 21 '19 at 20:06
4

To serialize as UTF8 string:

    private string Serialize(MyData data)
    {
        XmlSerializer ser = new XmlSerializer(typeof(MyData));
        // Using a MemoryStream to store the serialized string as a byte array, 
        // which is "encoding-agnostic"
        using (MemoryStream ms = new MemoryStream())
            // Few options here, but remember to use a signature that allows you to 
            // specify the encoding  
            using (XmlTextWriter tw = new XmlTextWriter(ms, Encoding.UTF8)) 
            {
                tw.Formatting = Formatting.Indented;
                ser.Serialize(tw, data);
                // Now we get the serialized data as a string in the desired encoding
                return Encoding.UTF8.GetString(ms.ToArray());
            }
    }

To return it as XML on a web response, don't forget to set the response encoding:

    string xml = Serialize(data);
    Response.ContentType = "application/xml";
    Response.ContentEncoding = System.Text.Encoding.UTF8;
    Response.Output.Write(xml);
GBU
  • 184
  • 6