Is there a way to inject support for the F# Option type into ServiceStack?
f#, servicestack
Solution
So there are a couple of extensibility points in ServiceStack, on the framework level you can add your own Custom Request Binder this allows you to provide your own model binder that's used, e.g:
base.RequestBinders.Add(typeof(Hello), httpReq => {
var requestDto = ...;
return requestDto;
});
But then you would need to handle the model binding for the different Content-Types yourself, see CreateContentTypeRequest for how ServiceStack does it.
Then there are hooks at the JSON Serializer level, e.g:
JsConfig<Hello>.OnDeserializedFn = dto => newDto;
This lets you modify the instance of the type returned, but it still needs to be the same type but it looks like the F# option modifier changes the structural definition of the type?
But I'm open to adding any hooks that would make ServiceStack more palatable for F#. What does the code look like to generically convert a normal `Hello` type to an F# `Hello` type with option?
Problem
Updated below... I recently started experimenting with ServiceStack in F#, so naturally I started with porting the Hello World sample: ``` 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() ``` That works great. It's very concise, easy to work with, I love it. However, I noticed that the routes defined in the sample allow for the `Name` parameter to not be included. Of course, `Hello, !` looks kind of lame as output. I could use `String.IsNullOrEmpty`, but it is idiomatic in F# to be explicit about things that are optional by using the Option type. So I modified my `Hello` type accordingly to see what would happen: ``` [<CLIMutable; Route("/hello"); Route("/hello/{Name}")>] type Hello = { Name : string option } ``` As soon as I did this, the F# type system forced me to deal with the fact that `Name` might not have a value, so I changed `HelloService` to this to get everything to compile: ``` type HelloService() = inherit Service() member x.Any(req:Hello) = box { Result = match req.Name with | Some name -> sprintf "Hello, %s!" name | None -> "Hello!" } ``` This compiles, and runs perfectly when I don't supply a `Name` parameter. However, when I do supply a name... KeyValueDataContractDeserializer: Error converting to type: Type definitions should start with a '{', expecting serialized type 'FSharpOption`1', got string starting with: World This wasn't a complete surprise of course, but it brings me to my question: It would be trivial for me to write a function that can wrap an instance of type `T` into an instance of type `FSharpOption<T>`. Are there any hooks in ServiceStack that would let me provide such a function for use during deserialization? I looked, but I couldn't find any, and I'm hoping I was just looking in the wrong place. This is more important for F# use than it might seem at first, because classes defined in F# are by default not allowed to be null. So the only (satisfying, non-hacky) way of having one class as an optional property of another class is with, you guessed it, the Option type. Update: I was able to sort-of get this working by making the following changes: In the ServiceStack source, I made this type public: `ServiceStack.Text.Common.ParseFactoryDelegate` ...and I also made this field public: `ServiceStack.Text.Jsv.JsvReader.ParseFnCache` With those two things public, I was able to write this code in F# to modify the `ParseFnCache` dictionary. I had to run this code prior to creating an instance of my AppHost - it didn't work if I ran it inside the AppHost's `Configure` method. ``` JsvReader.ParseFnCache.[typeof<Option<string>>] <- ParseFactoryDelegate(fun () -> ParseStringDelegate(fun s -> (if String.IsNullOrEmpty s then None else Some s) |> box)) ``` This works for my original test case, but aside from the fact that I had to make brittle changes to the internals of ServiceStack, it sucks because I have to do it once for each type I want to be able to wrap in an `Option<T>`. What would be better is if I could do this in a generic way. In C# terms, it would be awesome if I could provide to ServiceStack a `Func<T, Option<T>>` and ServiceStack would, when deserializing a property whose generic type definition matches that of the return type of my function, deserialize `T` and then pass the result into my function. Something like that would be amazingly convenient, but I could live with the once-per-wrapped-type approach if it were actually part of ServiceStack and not my ugly hack that probably breaks something somewhere else.