SOAP通信をしてみた（F#）

# Summary

F#でSOAP通信をしてみました。その時のメモ

## wsdlの読込

[FSharp.Data.WsdlProvider](https://github.com/fsprojects/FSharp.Data.WsdlProvider)を使うと手軽に行けるが、うまくいかない場合があったので、Visual Studio よりC#クラスを自動作成（プロジェクト > 接続済みサービス > 接続済サービスの管理 > サービス参照の追加 > WCF Web Service）。その分を使用すると確実

## 基本形

下記の様なコードで。。。

```fsharp
module myMain =

  open System
  open System.Linq
  open System.ServiceModel
  open System.ServiceModel.Channels
  open System.Security.Cryptography.X509Certificates
  open System.Xml

  open System.ServiceModel.Dispatcher
  open System.ServiceModel.Description


  let testURL = """https://xxx"""
  let endpointAddress = EndpointAddress(testURL)

  // 通信設定
  let tempBinding = new WSHttpBinding()
  tempBinding.Security.Mode <- SecurityMode.Transport
  tempBinding.Security.Transport.ClientCredentialType <- HttpClientCredentialType.Certificate

  // カスタムエンコーダー
  let messageEncodingBindingElementType = typedefof<MessageEncodingBindingElement>
  let elements = tempBinding.CreateBindingElements();

  let elementsWithoutEncodingElement =
    elements |> Seq.filter(fun elm -> messageEncodingBindingElementType.IsAssignableFrom(elm.GetType()) |> not)

  let newEncodingElement =

    // TEXTを使用
    new TextMessageEncodingBindingElement()
    |> fun x ->
      x.MessageVersion <- MessageVersion.Soap12
      x:> BindingElement


  let binding = new CustomBinding(elementsWithoutEncodingElement.Prepend(newEncodingElement))
  let client = new soapClient()
  client.Endpoint.Address <- endpointAddress
  client.Endpoint.Binding <- binding


  // セキュリティ設定
  client.ClientCredentials.ClientCertificate.SetCertificate(
      StoreLocation.CurrentUser
    , StoreName.My
    , X509FindType.FindByThumbprint
    , "拇印"
  )

  client.ClientCredentials.ServiceCertificate.SetDefaultCertificate (
      StoreLocation.CurrentUser
    , StoreName.CertificateAuthority
    , X509FindType.FindByThumbprint
    , "拇印"
  )

  client.request()
  |> printfn "%A"
```

## エンコーディング

### MTOM

```fsharp
  let newEncodingElement =
    new MtomMessageEncodingBindingElement()
    |> fun x ->
      x.MessageVersion <- MessageVersion.Soap12
      x:> BindingElement
```

### カスタムエンコーダー使用

```fsharp
(*

  カスタム テキスト エンコーダー
  https://github.com/dotnet/samples/tree/main/framework/wcf/Extensibility/MessageEncoder/Text/CS/library

  上記のプロジェクトをソリューションに追加

*)

  let newEncodingElement =
   new Microsoft.Samples.CustomTextMessageEncoder.CustomTextMessageBindingElement()
    |> fun x -> 
      x.Encoding <- "UTF-8"
      x.MessageVersion <- MessageVersion.Soap11
      x.MediaType <- "text/xml"
      x:> BindingElement
```

## ヘッダーに追記

```fsharp
module Util =

  (*

    see also: Thoughts Compiler(Add custom SOAP headers to your service requests)
    https://ion-sapoval.github.io/.net/c%23/soap/headers/2014/07/29/create-custom-soap-header.html

  *)

  type CustomHeader ()  =
    inherit MessageHeader()

    override _.Name = "Header"
    override _.Namespace = "http://foo"

    override this.OnWriteStartHeader(writer : XmlDictionaryWriter, messageVersion:MessageVersion) =
      writer.WriteStartElement("a", this.Name, this.Namespace);
      this.WriteHeaderAttributes(writer, messageVersion);

    override _.OnWriteHeaderContents(writer :XmlDictionaryWriter, messageVersion:MessageVersion) =
      writer.WriteStartElement("timeStamp")
      writer.WriteString("20220502012300000")
      writer.WriteEndElement()

module myMain =
 
 (* 中略 *)

  // SOAPヘッダー追加
  do
  use scope = new OperationContextScope(client.InnerChannel)
  OperationContext.Current.OutgoingMessageHeaders.Add(new Util.CustomHeader())
```

## Inspector

メッセージ送受信前後で処理を入れる場合

```fsharp
module Util3 =

  (*

    see also : Message Inspector ( https://docs.microsoft.com/ja-jp/dotnet/framework/wcf/samples/message-inspectors )

  *)

  type Inspector () =
    interface IClientMessageInspector with
      member this.AfterReceiveReply(reply: byref<Message>, correlationState: obj): unit =
        Console.WriteLine("IClientMessageInspector.AfterReceiveReply called.");
      member this.BeforeSendRequest(request: byref<Message>, channel: IClientChannel): obj =
        Console.WriteLine("IClientMessageInspector.BeforeSendRequest called.")

  type MyEndpointBehavior () =
    interface IEndpointBehavior with
      member this.AddBindingParameters(endpoint: ServiceEndpoint, bindingParameters: BindingParameterCollection): unit =
        ()
      member this.ApplyClientBehavior(endpoint: ServiceEndpoint, clientRuntime: ClientRuntime): unit =
        clientRuntime.ClientMessageInspectors.Add(new Inspector())
      member this.ApplyDispatchBehavior(endpoint: ServiceEndpoint, endpointDispatcher: EndpointDispatcher): unit =
        ()
      member this.Validate(endpoint: ServiceEndpoint): unit =
        ()

module myMain =

  (* 中略 *)

  client.Endpoint.EndpointBehaviors.Add(Util3.MyEndpointBehavior())

``` 

## HttpClientで処理する場合

```fsharp
(*

  1. HttpClientでpostする
  2. レスポンス内容からデータを抽出する
  
*)

open System
open System.Net.Http
open System.Net.Security
open System.Security.Authentication
open System.Security.Cryptography.X509Certificates

let mainImpl (client : HttpClient) (url:string) = task {

  let content = "ここに送信内容"
  let! response = client.PostAsync(url,content)
  let! responseContent = response.Content.ReadAsStringAsync()
  return ( responseContent |> (*データ抽出*) )

}

let handler = new HttpClientHandler()
handler.ClientCertificateOptions <- ClientCertificateOption.Manual
handler.SslProtocols <- SslProtocols.Tls12
handler.ClientCertificates.Add((*certの設定*)) |> ignore
handler.ServerCertificateCustomValidationCallback <-
  System.Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,bool>(
    fun _ _ _ _ ->
      handler.ClientCertificates.Add((*certの設定*)) |> ignore
      true
  )
let client = new HttpClient(handler)

mainImpl client myUrl |> fun x -> x.Result |> printfn "%A"
```

## see also
[SOAPヘッダーにXMLデータを挿入したいです。。。](https://github.com/fffsharp/Forum/discussions/6)
[SOAPデータを読み込みたいです。。。](https://github.com/fffsharp/Forum/discussions/7)
[カスタムエンコーダー C# => F#](https://gist.github.com/callmekohei/f08ab0f34191bf8af839f78d2f001bc3)