更新如下......
我最近开始在F#中尝试使用ServiceStack,所以很自然地我开始移植Hello World示例:
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()
Run Code Online (Sandbox Code Playgroud)
这很好用.它非常简洁,易于使用,我喜欢它.但是,我注意到样本中定义的路径允许Name
不包括参数.当然,Hello, !
看起来有点蹩脚的输出.我可以使用String.IsNullOrEmpty
,但在F#中通过使用Option类型明确可选的事物是惯用的.所以我相应地修改了我的Hello
类型,看看会发生什么:
[<CLIMutable; Route("/hello"); Route("/hello/{Name}")>]
type Hello = { Name : string option }
Run Code Online (Sandbox Code Playgroud)
一旦我这样做,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!" }
Run Code Online (Sandbox Code Playgroud)
当我不提供Name
参数时,这会编译并运行完美.但是,当我提供名字时......
KeyValueDataContractDeserializer:转换为类型时出错:类型定义应以'{'开头,期望序列化类型'FSharpOption`1',得到的字符串以:World开头
这当然不是一个完全的惊喜,但它让我想到了我的问题:
这将是微不足道的,我写的是可以包装类型的实例函数T
为类型的实例FSharpOption<T>
.ServiceStack中是否有任何钩子可以让我在反序列化期间提供这样的功能?我看了,但我找不到任何东西,我希望我只是在找错了地方.
这对于F#use来说比起初看起来更重要,因为在F#中定义的类默认不允许为null.因此,将一个类作为另一个类的可选属性的唯一(令人满意的,非hacky)方式是,您猜对了,Option类型.
更新:
我能够通过进行以下更改来实现这一点:
在ServiceStack源代码中,我公开了这种类型:
ServiceStack.Text.Common.ParseFactoryDelegate
......我也公开了这个领域:
ServiceStack.Text.Jsv.JsvReader.ParseFnCache
有了这两件事公开,我能够用F#编写这段代码来修改ParseFnCache
字典.我必须在创建AppHost实例之前运行此代码 - 如果我在AppHost的Configure
方法中运行它,它就不起作用了.
JsvReader.ParseFnCache.[typeof<Option<string>>] <-
ParseFactoryDelegate(fun () ->
ParseStringDelegate(fun s -> (if String.IsNullOrEmpty s then None else Some s) |> box))
Run Code Online (Sandbox Code Playgroud)
这适用于我原来的测试用例,但除了我必须对ServiceStack的内部进行细微更改这一事实之外,它很糟糕,因为我必须为我希望能够包装的每种类型执行一次Option<T>
.
如果我能以通用的方式做到这一点会更好.在C#术语中,如果我可以提供给ServiceStack a Func<T, Option<T>>
和ServiceStack,当反序列化其泛型类型定义与我的函数的返回类型匹配的属性时,反序列化T
然后将结果传递给我的函数,那将是非常棒的.
像这样的东西会非常方便,但如果它实际上是ServiceStack的一部分而不是我可能在其他地方破坏某些东西的丑陋黑客,那么我可以使用每次包装类型的方法.
因此,ServiceStack 中有几个可扩展点,在框架级别上,您可以添加自己的自定义请求绑定器,这允许您提供自己使用的模型绑定器,例如:
base.RequestBinders.Add(typeof(Hello), httpReq => {
var requestDto = ...;
return requestDto;
});
Run Code Online (Sandbox Code Playgroud)
但是,您需要自己处理不同内容类型的模型绑定,请参阅CreateContentTypeRequest了解 ServiceStack 是如何做到的。
然后是 JSON Serializer 级别的钩子,例如:
JsConfig<Hello>.OnDeserializedFn = dto => newDto;
Run Code Online (Sandbox Code Playgroud)
这允许您修改返回类型的实例,但它仍然需要是相同的类型,但看起来 F# 选项修饰符更改了类型的结构定义?
但我愿意添加任何能让 ServiceStack 更适合 F# 的钩子。Hello
使用选项将普通类型一般转换为 F#类型的代码是什么样的Hello
?