以下を更新...
最近、F#でServiceStackの実験を開始したので、当然、HelloWorldサンプルの移植から始めました。
open ServiceStack.ServiceHost
open ServiceStack.ServiceInterface
open ServiceStack.WebHost.Endpoints
[<CLIMutable; Route("/hello"); Route("/hello/{Name}")>]
type Hello = { Name : string }
[<CLIMutable>]
type HelloResponse = { Result : string }
type HelloService() =
inherit Service()
member x.Any(req:Hello) =
box { Result = sprintf "Hello, %s!" req.Name }
type HelloAppHost() =
inherit AppHostBase("Hello Web Services", typeof<HelloService>.Assembly)
override x.Configure container = ()
type Global() =
inherit System.Web.HttpApplication()
member x.Application_Start() =
let appHost = new HelloAppHost()
appHost.Init()
それはうまくいきます。それは非常に簡潔で、扱いやすく、私はそれが大好きです。ただし、サンプルで定義されているルートでは、Name
パラメーターを含めることができないことに気付きました。もちろん、Hello, !
出力としてはちょっと足りないように見えます。を使用することもできますが、F#では、 OptionタイプString.IsNullOrEmpty
を使用してオプションであるものについて明示するのは慣用句です。そこで、それに応じてタイプを変更して、何が起こるかを確認しました。Hello
[<CLIMutable; Route("/hello"); Route("/hello/{Name}")>]
type Hello = { Name : string option }
これを行うとすぐに、F#型システムは値を持たない可能性があるという事実に対処することを余儀なくされたので、すべてをコンパイルするためにこれName
に変更しました。HelloService
type HelloService() =
inherit Service()
member x.Any(req:Hello) =
box { Result =
match req.Name with
| Some name -> sprintf "Hello, %s!" name
| None -> "Hello!" }
これはコンパイルされ、パラメーターを指定しない場合は完全に実行されName
ます。しかし、私が名前を提供するとき...
KeyValueDataContractDeserializer:タイプへの変換エラー:タイプ定義は「{」で始まる必要があります。シリアル化されたタイプ「FSharpOption`1」が必要です。次で始まる文字列を取得します:World
もちろん、これは完全な驚きではありませんでしたが、それは私の質問に私をもたらします:
T
タイプのインスタンスをタイプのインスタンスにラップできる関数を作成するのは簡単ですFSharpOption<T>
。逆シリアル化中に使用するためにそのような関数を提供できるようにするフックはServiceStackにありますか?見ましたが、何も見つかりませんでした。間違った場所を探していたのではないかと思います。
F#で定義されたクラスはデフォルトでnullに許可されていないため、これはF#の使用にとって最初に思われるよりも重要です。したがって、あるクラスを別のクラスのオプションのプロパティとして持つ唯一の(満足のいく、ハッキーではない)方法は、ご想像のとおり、Option型を使用することです。
アップデート:
次の変更を加えることで、これを機能させることができました。
ServiceStackソースで、このタイプを公開しました。
ServiceStack.Text.Common.ParseFactoryDelegate
...そして私もこのフィールドを公開しました:
ServiceStack.Text.Jsv.JsvReader.ParseFnCache
ParseFnCache
これらの2つが公開されたので、辞書を変更するためにこのコードをF#で記述できました。AppHostのインスタンスを作成する前に、このコードを実行する必要がありました。AppHostのConfigure
メソッド内で実行した場合、このコードは機能しませんでした。
JsvReader.ParseFnCache.[typeof<Option<string>>] <-
ParseFactoryDelegate(fun () ->
ParseStringDelegate(fun s -> (if String.IsNullOrEmpty s then None else Some s) |> box))
これは私の元のテストケースでは機能しますが、ServiceStackの内部に脆弱な変更を加える必要があったことを除けば、ラップできるようにしたいタイプごとに1回行う必要があるため、問題がありOption<T>
ます。
これを一般的な方法で実行できれば、もっと良いでしょう。C#の用語では、ServiceStackに提供できれば素晴らしいでしょうFunc<T, Option<T>>
。ServiceStackは、ジェネリック型の定義が関数の戻り型の定義と一致するプロパティを逆シリアル化するときに、逆シリアルT
化してから結果を関数に渡します。
そのようなものは驚くほど便利ですが、それが実際にServiceStackの一部であり、おそらく他の場所で何かを壊すような醜いハックではない場合は、ラップごとに1回のアプローチで生きることができます。