.NETでサービス参照を使用した場合に、SOAPでCRLFがLFになることへの対策

# 解決したいこと

ClickOnceなどで、Webサービス(asmx)をサービス参照した場合に、改行コードが自動的にCRLFからLFに変わるという問題が発生します。

しかし、そのままでは次のような問題が出てきます。

* 複数行テキストボックスで改行が反映されない
* ファイル出力時に改行が反映されない

# 原因

詳細は検索いただければと思いますが、SOAPの仕様も踏まえ、クライアントまでは正しくたどり着いているが、自動生成されるプロキシクラスでは、既定でCRが消失するという動きとなるようです。

[Webメソッドのstring型の戻り値に含まれる改行コードが変換されてしまう](
https://social.msdn.microsoft.com/Forums/ja-JP/247cbb61-be68-48c9-a436-53f373de17f8/web1251312477124831248912398string2241112398251471242620516123952?forum=netfxgeneralja)

# 解決方法

WCFを使用しない.NET 2.0までの参照方法であるWeb参照なら、SoapHttpClientProtocolを継承していることもあり、Partialクラスを使用して、スマートに解決できます。

しかし、サービス参照を使用した場合のスマートな解決方法が見つかりませんでした（Partialすべきクラスがわかりませんでした）ので、とりあえずの解決方法を記載します。

**もしもっとスマートな解決法があれば、ぜひ教えていただけると助かります！**

## Webサービス(asmx)をWeb参照した場合（.NET 2.0。.NET 2.0以外では非推奨）

[WebMethod中の改行コード（CRLFがLFになっちゃう）](http://tomcatch.blog38.fc2.com/blog-entry-12.html)

具体的なコードを記載いただいている方がいらっしゃいましたので、上記ページより引用します。

> SoapHttpClientProtocol.GetReaderForMessageをオーバーライドすればいい、という情報を得て、
> .NET Reflectorで中身を確認したところ、XmlTextReaderのインスタンスをアップキャストして返してるのがわかったので、XmlTextReader.Normalizationをfalseにすることで解決。 
> コードにしたらこういう感じ。
> 自動生成のコードには手を入れたくないので、パーシャルクラス様様です。

```csharp:HogeService.cs
public partial class HogeService
{
	protected override XmlReader GetReaderForMessage(SoapClientMessage message, int bufferSize)
	{
		XmlTextReader reader = (XmlTextReader)base.GetReaderForMessage(message, bufferSize);
		reader.Normalization = false;
		return reader;
	}
}
```

## Webサービス(asmx)をサービス参照した場合（.NET 3.5～）

単純に、Webサービスから取得直後に処理するのみという方法です。

データの処理をクライアント側の使用箇所ごとにで行うため、修正箇所も増え、Document-View（[参考1](https://docs.microsoft.com/ja-jp/cpp/mfc/document-view-architecture?view=vs-2017)、[参考2](http://sayama-yuki.cocolog-nifty.com/blog/2008/11/mvcmodel-view-c.html)）のアーキテクチャ的にも、本来は望ましくない暫定的な方法です。

```vb:SoapNormalization.vb
Public Class SoapNormalization
    Public Shared Sub EditDataSet(inV As DataSet)
        For Each item As DataTable In inV.Tables
            EditDataTable(item)
        Next
    End Sub

    Public Shared Sub EditDataTable(inV As DataTable)
        ' 速度を考慮するなら、String列の判定は1回のみ行う
        inV.AsEnumerable.ToList.ForEach(
            Sub(row)
                For colIndex = 0 To inV.Columns.Count - 1
                    If inV.Columns(colIndex).DataType Is GetType(String) Then
                        ' 文字列のみ処理する
                        row(colIndex) = NormalizeString(row(colIndex))
                    End If
                Next
            End Sub)
    End Sub

    Private Shared Function NormalizeString(v As Object) As Object
        If IsDBNull(v) Then Return v
        If IsNothing(v) Then Return v

        Dim ev = $"{v}"
        ev = ev.Replace(vbLf, vbCrLf)

        Return ev
    End Function
End Class
```

```vb:WebService1.vb
<WebMethod()>
Public Function GetDataSet() As DataSet
    Dim ds = New DataSet("dummy")
    Dim dt = New DataTable

    dt.Columns.Add("c1", GetType(String))
    dt.Columns.Add("c2", GetType(Integer))
    dt.Columns.Add("c3", GetType(Decimal))
    dt.Columns.Add("c4", GetType(Date))

    dt.Rows.Add($"Hello World{vbNewLine}2行目{vbNewLine}3行目{vbNewLine}", 55, 678.9, Now.AddMonths(10))
    dt.Rows.Add($"Hello World", 1, 2.3, DBNull.Value)
    dt.Rows.Add(DBNull.Value, DBNull.Value, DBNull.Value, DBNull.Value)

    dt.TableName = "t1"
    ds.Tables.Add(dt)

    Return ds
End Function
```

```vb:Form1.vb
Dim sv = New WebService1.WebService1SoapClient
Dim ds = sv.GetDataSet

'改行コード対策
SoapNormalization.EditDataSet(ds)

Dim dt = ds.Tables(0)
TextBox1.DataBindings.Add("Text", dt, "c1")
```