POSTing JSON to an F# Web API by Mark Seemann
How to write an ASP.NET Web API service that accepts JSON in F#.
It seems that many people have problems with accepting JSON as input to a POST method when they attempt to implement an ASP.NET Web API service in F#.
It's really quite easy, with one weird trick :)
You can follow my recipe for creating a pure F# Web API project to get started. Then, you'll need to add a Data Transfer Record and a Controller to accept your data:
[<CLIMutable>] type MyData = { MyText : string; MyNumber : int } type MyController() = inherit ApiController() member this.Post(myData : MyData) = this.Ok myData
That's quite easy; there's only one problem with this: the incoming myData
value is always null.
The weird trick #
In addition to routes etc. you'll need to add this to your Web API configuration:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver <- Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
You add this in your Application_Start method in your Global class, so you only have to add it once for your entire project.
The explanation #
Why does this work? Part of the reason is that when you add the [<CLIMutable>] attribute to your record, it causes the record type to be compiled with auto-generated internal mutable fields, and these are named by appending an @ character - in this case, the field names become MyText@
and MyNumber@
.
Apparently, the default JSON Contract Resolver (whatever that is) goes for those fields, even though they're internal, but the CamelCasePropertyNamesContractResolver doesn't. It goes for the properly named MyText
and MyNumber
writeable public properties that the compiler also generates.
As the name implies, the CamelCasePropertyNamesContractResolver converts the names to camel case, so that the JSON properties become myText
and myNumber
instead, but I only find this appropriate anyway, since this is the convention for JSON.
Example HTTP interaction #
You can now start your service and make a POST request against it:
POST http://localhost:49378/my HTTP/1.1 Content-Type: application/json { "myText": "ploeh", "myNumber": 42 }
This request creates this response:
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 {"myText":"ploeh","myNumber":42}
That's all there is to it.
You can also receive XML instead of JSON using a similar trick.
Comments
[<JsonObject(MemberSerialization=MemberSerialization.OptOut)>]
to the type also works