如何将XML返回为UTF-8而不是UTF-16

24

我正在使用一个序列化<T>的例程。它可以工作,但是当下载到浏览器时,我看到一个空白页面。我可以查看页面源代码或在文本编辑器中打开下载文件,我可以看到xml,但它是UTF-16编码,这可能是为什么浏览器页面显示为空白的原因?

我该如何修改我的序列化程序以返回UTF-8而不是UTF-16?

返回的XML源代码:

<?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>

一个调用序列化器的例子:

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

string SelectionId = "1234567890";

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

序列化器:

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);
        }
    }
}

我认为这篇文章会给你想要的:https://dev59.com/XWEh5IYBdhLWcg3wZyzG - jtm001
“它是UTF-16编码,我认为这就是为什么浏览器页面显示为空白的原因?”我不认为有这样的理由。请调查您的文件,它实际上是什么编码?开头有任何BOM代码吗?等等。 - H H
3个回答

33
您可以使用StringWriter来强制使用UTF8编码。以下是一种方法:
您可以使用StringWriter来强制使用UTF8编码。以下是一种方法:
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.
    }
}

接着在您的代码中使用Utf8StringWriter编写器。

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

本答案受Serializing an object as UTF-8 XML in .NET启发。


覆盖编码是否没有不良副作用?我不知道这可能会有什么负面影响,但我对此感到不安... - NobodysNightmare
据我所知,我在许多情况下都使用过它。但是对于服务器,我们根本不会在这种情况下使用StringWriter,因为它会不必要地双重缓冲。这就是我们在MVC vNext中所做的(以及在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
Yishai,NobodysNightmare的回答可以实现我需要的功能。在看到你的回答之前,我已经尝试了他的回答。也许你也指引了我正确的方向。感谢你花时间尝试帮助我。 - rwkiii
当使用这种技术时,您还需要实现一个默认构造函数,否则会出现错误。 - ITExpert
@ITExpert 感谢您的指引,如果您能进一步解释为什么需要这样做或者错误是什么,那对其他用户来说可能会更有帮助。 - Yishai Galatzer

11

响应的编码

我对框架的这部分不是很熟悉。但根据MSDN的说明,您可以像下面这样设置HttpResponse的内容编码

httpContextBase.Response.ContentEncoding = Encoding.UTF8;

XmlSerializer视角下的编码

再次阅读您的问题后,我认为这是困难的部分。问题在于使用StringWriter时出现了问题。因为.NET字符串始终存储为UTF-16(需要引证^^),所以StringWriter将其作为编码返回。因此XmlSerializer将XML声明写为

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

为了解决这个问题,您可以像这样写入到MemoryStream中:
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());
}

其他方法

另一种编辑方式:我刚刚注意到jtm001链接了这个SO答案。在那里,压缩解决方案的方法是为XmlSerializer提供一个自定义的XmlWriter,该写入器被配置为使用UTF8作为编码。

Athari 建议StringWriter派生,并将编码广告为UTF8。

据我理解,这两种解决方案都应该同样有效。我认为这里的要点是你需要一个或另一个样板代码...


1
这个答案的缺点是,对于大型XML响应,您现在正在将它们全部写入内存中,这可能导致不必要的大内存消耗,并且如果超过85KB,则响应将进入大对象堆。当发生这种情况时,您的应用程序经常会在垃圾回收期间开始冻结。 - Yishai Galatzer
".NET使用UTF-16编码来表示字符和字符串。" - mlhDev

4

将对象序列化为UTF8字符串:

    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());
            }
    }

为了在Web响应中将其作为XML返回,请不要忘记设置响应编码:
    string xml = Serialize(data);
    Response.ContentType = "application/xml";
    Response.ContentEncoding = System.Text.Encoding.UTF8;
    Response.Output.Write(xml);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接