Is there a way to implement support like F # Option in ServiceStack?

Updated below ...

I recently started experimenting with ServiceStack in F #, so naturally, I started by porting the Hello World example :

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() 

This works great. It is very concise, easy to work, I love it. However, I noticed that the routes defined in the sample allow us to include the Name parameter. Of course Hello, ! looks lame as a conclusion. I could use String.IsNullOrEmpty , but idiomatic in F # to be explicit about things that are optional, using the Option type. So I changed my Hello type to see what happens:

 [<CLIMutable; Route("/hello"); Route("/hello/{Name}")>] type Hello = { Name : string option } 

As soon as I did this, a system like F # made me cope with the fact that Name might not matter, so I changed HelloService to this so that everything HelloService :

 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 works fine when I do not provide a Name parameter. However, when I provide the name ...

KeyValueDataContractDeserializer: Convert errors to type: Type definitions should start with '{', waiting for the serialized type 'FSharpOption`1', got a line starting with: World

Of course, this was not a complete surprise, but this 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 hooks in ServiceStack that would allow me to provide such a function for use during deserialization? I looked, but I could not find, and I hope that I was just looking for the wrong place.

This is more important for using F # than it might seem at first glance, because the classes defined in F # are not null by default. Thus, the only (satisfactory, non-hacky) way to have one class as an optional property of another class is, you guessed it, the Option type.


Update:

I managed to sort the work by making the following changes:

In the ServiceStack source, I made this type public: ServiceStack.Text.Common.ParseFactoryDelegate

... and I also made this field open: ServiceStack.Text.Jsv.JsvReader.ParseFnCache

With these two things public, I was able to write this code in F # to modify the ParseFnCache dictionary. I had to run this code before instantiating my AppHost - it did not work if I ran it inside the AppHost 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 besides the fact that I had to make fragile changes for the internal components of ServiceStack, it sucks because I have to do this once for each type that I want Option<T> .

What would be better if I could do it in a general way. In terms of C #, it would be great if I could provide ServiceStack a Func<T, Option<T>> , and ServiceStack when deserializing a property whose general type definition matches the type of my function returned, deserialize T , and then pass the result to my function.

Something like this would be surprisingly convenient, but I could live with a once-per-wrapper approach if it was actually part of the ServiceStack, and not my ugly hack that probably broke something somewhere still.

+7
source share
2 answers

So, there are several extensibility points in ServiceStack, at the frame level you can add your own Custom Request Binder , this allows you to provide your own connecting device, which is used, for example:

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

But then you will need to handle model binding for different Content-Types yourself, see CreateContentTypeRequest , as ServiceStack does.

Then, at the JSON selector level, there are hooks, for example:

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

This allows you to change the instance of the return type, but it should still be the same type, but does the F # parameter modifier seem to change the structural definition of the type?

But I'm open to adding any hooks that will make ServiceStack more enjoyable for F #. What does the code look like for the general conversion of a regular type Hello to type F # Hello with a parameter?

+4
source

The only thing I can think of is to replace the parameter type with your own type, which has an implicit conversion from string to myOption and all you need.

Not everything is nice, but workable. Your type should probably also be serializable.

 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 

Then your record type will be

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

ServiceStack, on the other hand, is open source, so maybe something can be done there.

+1
source

All Articles