ASP.NET CoreでHTTP SOAPリクエストを処理する

# 始めに

SOAPとは、簡単に言うとXMLベースのRPCプロトコルフォーマットの事である。
SOAP自体はもっと大きい枠組みであるWeb services protocol stackの一部らしいが、単体でも利用可能なものになっている。
本来ならば、様々な通信に乗せて使用することもできるらしいが、現在の所主に使用されているのはHTTP上である。
(主に、とは書いてあるが、現在のところ各所でほぼレガシー扱いされていると思うので今後増えるかどうかは不明)

* 参考URL: http://www.atmarkit.co.jp/aig/01xml/soap.html

以後、特に断りが無い限り、HTTP上で利用されるSOAP通信の事を書く。

## .NET Frameworkあるいはmonoでの利用

.NET Frameworkは、サーバー、クライアント双方のライブラリが用意されている。monoでも、ドキュメントを見る限り、基本的な部分は使用する分には問題なく使えそう(WCFは未検証、ASMXは機能的にはほぼ使える)

* monoのWCFについて: http://www.mono-project.com/docs/web/wcf/

### サーバー側

C#でSOAPリクエストのサービスを提供する場合、ASP.NET XML Web Service(ASMX)を使用するか、WCFを使用することになる。どちらかというと、ASMXの方が古い。

* [WCFについて](https://docs.microsoft.com/ja-jp/dotnet/framework/wcf/index)
* [ASP.NET XML Web Serviceについて](https://msdn.microsoft.com/ja-jp/library/ba0z6a33(v=vs.100).aspx)

### クライアント側

クライアントの場合は、[WCFのクライアントライブラリを使用する](https://www.nuget.org/packages?q=system.servicemodel)か、[WSDL.exeコマンド](https://msdn.microsoft.com/ja-jp/library/7h3ystb6(v=vs.100).aspx)でクライアントを生成することで、SOAP通信を利用することができる。
どちらもWindows SDKに入っているはず。

* [WCF クライアントの作成](https://docs.microsoft.com/ja-jp/dotnet/framework/wcf/how-to-create-a-wcf-client)

# 何が問題か

上で記述したのは.NET Frameworkの話である。では、.NET Coreはどうなのか、というと、クライアントは問題ない。
厳密に検証したわけではないが、WCFのnugetパッケージを利用すれば、クライアント部分はほぼ問題なく使える模様。

しかし、**サーバー部分の提供は無い**。SOAP自体がほぼレガシーになっている現在、ASP.NET Coreの方で正式サポートしないのは仕方ない話である。しようと思うとどうしても重厚長大になるだろうし。
では現状維持のままでいいかというと、ASP.NET Coreには既存のASP.NETにはない新機能や、パフォーマンスアップ等が推し進められており、サーバーサイドとしてはどうしても移行したい。
全体は新しいやり方に移行するにしても、古いクライアントを対象に互換レイヤーを提供しなければならない場合もあるので、どうしてもここが移行の障害となる。

# 解決策

元々SOAP自体は単なるプロトコルなので、そのアクセスを受け付けるようにすればいいという話となる。
そして、正式サポートされていないならば、自分で作るか、あるいはサードパーティーのライブラリを使えば良い。
ということで紹介するのが [SoapCore](https://github.com/DigDes/SoapCore)である。

# サービス実装方法

## 一から作る場合

asp.net coreプロジェクト自体の用意は [ASP.NET Coreのドキュメント](https://docs.microsoft.com/ja-jp/aspnet/core/getting-started)を参考にして、予め構築しているものとする。

1. `System.ServiceModel.ServiceContractAttribute`を追加したインターフェイスを作成する
2. メソッドに`System.ServiceModel.OperationContractAttribute`を追加する
    * refやoutも使用可能
3. インターフェイスの実装クラスを作成する
4. StartupクラスのConfigureServicesで、実装クラスを`TryAddSingleton`で追加する
5. StartupクラスのConfigureで、`UseSoapEndpoint`で、エントリポイントを追加する
    * [サンプル](https://github.com/DigDes/SoapCore/blob/master/samples/Server/Startup.cs)では拡張子を変えているが、特にその辺りは好きに設定しても問題ない
    * 3番目の引数に関して、WCFクライアントからの接続の場合は`DataContractSerializer`で問題ないが、他のクライアントからアクセスする場合は、要素が前後する可能性を考慮して、`XmlSerializer`を使用した方が良い
        * [DataContractSerializerは、要素の順番が前後すると、値が反映されない場合があるため](https://stackoverflow.com/questions/1513525/ignore-field-order-in-datacontractserializer)

* [ServiceContractAttribute](https://docs.microsoft.com/ja-jp/dotnet/api/system.servicemodel.servicecontractattribute?view=netframework-4.7.1)
* [OperationContractAttribute](https://docs.microsoft.com/ja-jp/dotnet/api/system.servicemodel.operationcontractattribute?view=netframework-4.7.1)

## WCFからの移行

定義等はほとんどそのままで移行可能。async Taskメソッドも使える。ただし、**IAsyncResultを利用した非同期メソッドは使えない**。基本的にWCFの機能はほとんど移行されていないため、どちらにしろ検証は必要。

## ASMXからの移行

ASMXから移行する場合は少々面倒な手順が必要。まず、`System.Web`に大きく依存しているため、WCFのように定義をそのまま移行というわけにはいかない。
メソッドの定義等は、WSDLから`svcutil.exe`でスケルトンを作ることは可能。(Windows SDKに入っている)
また、WSDL.exeからサーバーインターフェイスコードを生成し、そこから属性の付け替えを行うという手もある。
既にインターフェイスとして分離しているならば、そこから属性の付け替えでも移行が可能。(`WebServiceBindingAttribute`から`ServiceContractAttribute`、`SoapDocuemntMethodAttribute`と`WebMethodAttribute`から`OperationContractAttribute`)
なお、HttpContextは使えないので、実装クラスでは注意すること。

* [WebServiceBindingAttribute](https://docs.microsoft.com/ja-jp/dotnet/api/system.web.services.webservicebindingattribute?view=netframework-4.7.1)
* [SoapDocumentMethodAttribute](https://docs.microsoft.com/ja-jp/dotnet/api/system.web.services.protocols.soapdocumentmethodattribute?view=netframework-4.7.1)
* [WebMethodAttribute](https://docs.microsoft.com/ja-jp/dotnet/api/system.web.services.webmethodattribute?view=netframework-4.7.1)
* [ASMXからWCFへの移行ガイド](https://docs.microsoft.com/ja-jp/dotnet/framework/wcf/feature-details/migrate-asp-net-web-service-to-wcf)

# 注意点

## XML名前空間について

SOAPではXML名前空間を使用しているので、ここはクライアントとサーバー間で合わせる必要がある。具体的には `ServiceContractAttribute`の`Namespace`と、また、`OperationContractAttribute`の`Namespace`がそれになる。

## SOAPバージョンについて

現在使われているSOAPは1.1と1.2の2系統がある。1.1は問題なく処理可能だが、SoapCoreの0.9.6では、まだSOAP1.2メッセージは処理できないので注意。~~masterには修正がマージされているので、次のバージョンでは処理できるようになるかもしれない。~~
0.9.7よりパースできるようになりました。

SOAPバージョンについての参考URL: https://www.ibm.com/support/knowledgecenter/ja/SSAW57_8.5.5/com.ibm.websphere.nd.doc/ae/cwbs_soapverdiffs.html

# 終りに

SOAPリクエストを処理できるようになるというだけなので、**WCFやASMXのコードがそのまま移行できるわけではないということに注意**。あくまでも移行のための互換レイヤーとみて、過度な期待はしない方がいい。フォーマットにXMLを使用している以上、grpc等に処理速度では勝てないだろうし。
非機能面は厳密には計測していない。中身を見ると、リクエストの振り分け時にリフレクションを使用しているので、もしかしたら劣る可能性もなくはない。この辺りの計測は今後の宿題となりそう。