XmlSerializerの使い方を誤って、stringにゴミ(BOM)が混入した

# 前提

UTF-8の先頭にBOM 3バイト(0xEFBBBF)が付くこと自体は仕様に存在するが、
HTTPレスポンスの場合、ヘッダに文字コードは書かれているため、BOMが付いていると問題になることが多い

# 経緯

ASP.NET Web APIを使ってXMLのレスポンスを返すAPIを作成していたところ、レスポンスのボディにBOMが混入し、受け手のシステムが処理できないという問題が起きました。

# 最初はStringContentを疑ったが、それは間違いだった

問題のソースでは、StringContentを使ってレスポンスのBODYを指定していました。

```csharp
// responseStringはstring
var content = new StringContent(responseString, Encoding.UTF8, "application/xml");
```

テキストファイルを扱っていたときの知識から、第2引数が悪さをしているのではないかと思い、次のように書き換えてみました。

```csharp
var content = new StringContent(responseString, new UTF8Encoding(false), "application/xml");
```

ところが、デバッグしてみると変更前後のどちらのレスポンスにもBOMが混入していました。

# 問題はその前のXmlSerializer

色々調べてみると、実はresponseStringの中身に問題があることが判明しました。

responseStringには下記のようなメソッドを使って、エンティティをXML文字列にシリアライズした値を代入していました。

```csharp
        public static string Serialize<T>(Object obj, bool isEmptyNamespace = true) where T : class
        {
            using (MemoryStream stream = new MemoryStream())
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));

                // ヘッダに文字コードを追加する
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Encoding =  Encoding.UTF8;

                using (XmlWriter writer = XmlWriter.Create(stream, settings))
                {
                    if (isEmptyNamespace)
                    {
                        var xmlNamespace = new XmlSerializerNamespaces();
                        // XMLの名前空間を空白にする
                        xmlNamespace.Add(string.Empty, string.Empty);
                        xmlSerializer.Serialize(writer, obj as T, xmlNamespace);
                    }
                    else
                    {
                        xmlSerializer.Serialize(writer, obj);
                    }
                }

                // ストリームを開始位置に戻す
                stream.Position = 0;
                return Encoding.UTF8.GetString(stream.ToArray());
            }
        }
```

問題になるのはXmlWriterSettingsの部分です。ここで文字コードをUTF8にセットしていますが、この状態だと __MemoryStreamにはBOMも書き込まれてしまう__ のです。
結果、stream.ToArray()したとき、GetString()にはBOM 3バイトを含んだバイト配列が渡されます。

string型に変換されたあと、クイックウォッチで見てもBOMは表示されないため、一見正常な文字列になっているように見えます。しかし、実際には3バイトのゴミがstring型の先頭に混入した状態となります。

これが巡り巡って、レスポンスのボディに乗ってきてしまい、不具合の原因となっていました。

# 直接的な解決：XMLのシリアライズ・メソッドを修正

XmlWriterSettingsで文字コードをBOMなしUTF8と指定すれば、ひとまず解決します。

```csharp
        public static string Serialize<T>(Object obj, bool isEmptyNamespace = true) where T : class
        {
            using (MemoryStream stream = new MemoryStream())
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));

                // ヘッダに文字コードを追加する
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Encoding =  new UTF8Encoding(false);

                using (XmlWriter writer = XmlWriter.Create(stream, settings))
                {
                    if (isEmptyNamespace)
                    {
                        var xmlNamespace = new XmlSerializerNamespaces();
                        // XMLの名前空間を空白にする
                        xmlNamespace.Add(string.Empty, string.Empty);
                        xmlSerializer.Serialize(writer, obj as T, xmlNamespace);
                    }
                    else
                    {
                        xmlSerializer.Serialize(writer, obj);
                    }
                }

                // ストリームを開始位置に戻す
                stream.Position = 0;
                return new UTF8Encoding(false).GetString(stream.ToArray());
            }
        }
```

#そもそもの話、わざわざXmlSerializerを使う必要はない

せっかくWeb API使ってるんで、StringContentとか介さずに、こうしてれば問題は起きなかったですよね。

```csharp
// responseはXMLへシリアライズしたいモデル
Request.CreateResponse(HttpStatusCode.OK, response, "application/xml");
```