7

下面更新...

我最近开始在 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()

这很好用。它非常简洁,易于使用,我喜欢它。但是,我注意到示例中定义的路由允许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

当然,这并不完全令人惊讶,但它让我想到了我的问题:

编写一个可以将 type 的实例包装到 typeT的实例中的函数对我来说是微不足道的FSharpOption<T>ServiceStack 中是否有任何钩子可以让我在反序列化期间提供这样的功能?我看了看,但我找不到任何东西,我希望我只是找错地方了。

这对于 F# 的使用比最初看起来更重要,因为在 F# 中定义的类默认情况下不允许为空。因此,将一个类作为另一个类的可选属性的唯一(令人满意的,非 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))

这适用于我的原始测试用例,但除了我必须对 ServiceStack 的内部进行脆弱的更改之外,这很糟糕,因为我必须为每种我希望能够包装在Option<T>.

如果我能以通用的方式做到这一点,那会更好。在 C# 术语中,如果我可以向 ServiceStack 提供 aFunc<T, Option<T>>并且 ServiceStack 将在反序列化其泛型类型定义与我的函数的返回类型匹配的属性时,反序列化T然后将结果传递给我的函数,那就太棒了。

像这样的东西会非常方便,但如果它实际上是 ServiceStack 的一部分,而不是我的丑陋黑客可能会破坏其他地方的东西,我可以接受一次包装类型的方法。

4

2 回答 2

4

因此,ServiceStack 中有几个可扩展点,在框架级别上,您可以添加自己的自定义请求绑定器,这允许您提供自己使用的模型绑定器,例如:

base.RequestBinders.Add(typeof(Hello), httpReq => {
    var requestDto = ...;
    return requestDto;
});

但是您需要自己处理不同 Content-Type 的模型绑定,请参阅CreateContentTypeRequest了解 ServiceStack 是如何做到的。

然后是 JSON 序列化器级别的钩子,例如:

JsConfig<Hello>.OnDeserializedFn = dto => newDto;

这使您可以修改返回的类型的实例,但它仍然需要是相同的类型,但看起来 F# 选项修饰符更改了类型的结构定义?

但我愿意添加任何可以使 ServiceStack 更适合 F# 的钩子。Hello使用选项将普通类型一般转换为 F#类型的代码是什么样的Hello

于 2012-10-11T11:07:18.847 回答
1

我能想到的唯一一件事就是用您自己的类型替换选项类型,该类型具有从stringto的隐式转换myOption,以及您需要的任何其他类型。

不是那么好,但可行。您的类型可能还需要可序列化。

type myOption = 
   | None 
   | Some of string
   static  member public op_Implicit (s:string) = if s <> null then Some s else None
   member public this.Value = match this with 
                              | Some s -> s
                              | _      -> null
   member this.Opt = match this with 
                     | Some s -> Option.Some s
                     | None   -> Option.None 

您的记录类型将是

[<CLIMutable>]
type Hello = 
   { Name : myOption }

另一方面,ServiceStack开源的,所以也许可以在那里做点什么。

于 2012-10-11T10:53:01.023 回答