Summary
F#でSOAP通信をしてみました。その時のメモ
wsdlの読込
FSharp.Data.WsdlProviderを使うと手軽に行けるが、うまくいかない場合があったので、Visual Studio よりC#クラスを自動作成(プロジェクト > 接続済みサービス > 接続済サービスの管理 > サービス参照の追加 > WCF Web Service)。その分を使用すると確実
基本形
下記の様なコードで。。。
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
let newEncodingElement =
new MtomMessageEncodingBindingElement()
|> fun x ->
x.MessageVersion <- MessageVersion.Soap12
x:> BindingElement
カスタムエンコーダー使用
(*
カスタム テキスト エンコーダー
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
ヘッダーに追記
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
メッセージ送受信前後で処理を入れる場合
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で処理する場合
(*
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データを挿入したいです。。。
SOAPデータを読み込みたいです。。。
カスタムエンコーダー C# => F#