It’s very easy to get started on an F# Web API application thanks to Dan Mohl‘s excellent F# C# MVC 4 template. After installing the template, create an F# and C# Web Application (ASP.NET MVC 4) in Templates .. Visual F# .. ASPNET and select WebApi Project in the next dialog. You start with 2 projects, a C# MVC 4 project (WebApi) and an F# Web API project (WebAppApi)
If you look in the ValuesController.cs in the WebAppApi project, you’ll see the following code:
namespace FsWeb.Controllers open System.Web open System.Web.Mvc open System.Net.Http open System.Web.Http type ValuesController() = inherit ApiController() // GET /api/values member x.Get() = [| "value1"; "value2" |] |> Array.toSeq // GET /api/values/5 member x.Get (id:int) = "value" // POST /api/values member x.Post ([<FromBody>] value:string) = () // PUT /api/values/5 member x.Put (id:int) ([<FromBody>] value:string) = () // DELETE /api/values/5 member x.Delete (id:int) = ()
To test this you can run this project and test the API using Fiddler, or alternatively you can follow Scott Hanselman’s post on installing HTTPie. (Replace [port] with whatever is configured for your application.)
C:\>http http://localhost:port/api/values
HTTP/1.1 200 OK
Content-Length: 19
Content-Type: application/json; charset=utf-8
Date: Tue, 09 Oct 2012 21:50:53 GMT
Server: Microsoft-IIS/8.0
[
"value1",
"value2"
]
Let’s try something more complex. Say our model is a record:
type Value = { Id: int; Name: string; }
(Admittedly it’s not that complex but it will suit our purpose.)
Now change the Get() method on the ValuesController
member x.Get() = [| { Id=1; Name="value1" }; { Id=2; Name="value2" } |] |> Array.toSeq
Running HTTPie as before, we get the following JSON output:
C:\>http http://localhost:port/api/values
HTTP/1.1 200 OK
Content-Length: 55
Content-Type: application/json; charset=utf-8
Date: Tue, 09 Oct 2012 22:54:52 GMT
Server: Microsoft-IIS/8.0
[
{
"Id@": 1,
"Name@": "value1"
},
{
"Id@": 2,
"Name@": "value2"
}
]
Unfortunately the result is not exactly how we want it. What’s happening is that JSON.NET is serializing the field names of the record rather than the properties, hence the @ signs at the end of each member. To fix this, we can add a reference to the Newtonsoft.JSON nuget package in the WebAppApi project, and mark the record with a JsonObject attribute
open Newtonsoft.Json [<CLIMutable>] [<JsonObject(MemberSerialization=MemberSerialization.OptOut)>] type Value = { Id: int; Name: string; }
Now calling the API again, we get the following JSON response
[
{
"Id": 1,
"Name": "value1"
},
{
"Id": 2,
"Name": "value2"
}
]
If you only ever intend your clients to use JSON to talk to your API you could consider yourself done. But what does the response look like as XML?
C:\>http http://localhost:port/api/values Accept:application/xml
HTTP/1.1 200 OK
Content-Length: 291
Content-Type: application/xml; charset=utf-8
Date: Wed, 10 Oct 2012 22:44:22 GMT
Server: Microsoft-IIS/8.0
<ArrayOfValue
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/FsWeb.Controllers">
<Value>
<Id_x0040_>1</Id_x0040_>
<Name_x0040_>value1</Name_x0040_>
</Value>
<Value>
<Id_x0040_>2</Id_x0040_>
<Name_x0040_>value2</Name_x0040_>
</Value>
</ArrayOfValue>
To serialize correctly as both XML and Json we can use the DataContract attribute (in System.Runtime.Serialization.dll)
open System.Runtime.Serialization [<CLIMutable>] [<DataContract>] type Value = { [<DataMember>]Id: int; [<DataMember>]Name: string; }
Now our response looks better:
<ArrayOfValue
xmlns="http://schemas.datacontract.org/2004/07/FsWeb.Controllers"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Value>
<Id>1</Id>
<Name>value1</Name>
</Value>
<Value>
<Id>2</Id>
<Name>value2</Name>
</Value>
</ArrayOfValue>
Unfortunately this technique requires us to decorate each and every field with the DataMember attibute. I’m sure there must be a better way.
Thanks! Exactly the pointer I needed. Just great that we can’t get beyond having to decorate everything just to get it to serialise. I tried using f# classes in my solution and had the same issue so I guess it’s not limited to records. This is exactly the reason I like ServiceStack!
Thanks for this, I was having this exact issue today, and you helped me out! 🙂 Regarding the XML serializing issue. I found that if I configured the XmlFormatter to use the XmlSerializer, instead of the DataContractSerializer, I got the XML I wanted.
So:
static member RegisterWebApi(config: HttpConfiguration) =
config.Formatters.XmlFormatter.Indent <- true
config.Formatters.XmlFormatter.UseXmlSerializer <- true
Thanks again!
FunDomain has Serialization stuff robbed from FsUno.Prod which addresses this and more – doesnt need any attrs at all (perhaps `[]` for the write path but that may only be necessary if you want XML – if you leave it off you can def still render json)
Was assuming 2014 when I bothered commenting BTW 😛
The better way – reset Json.Net’s serializer to use Newtonsoft’s default contract binder (not the Web API one) and (in recent Json.Net’s) it all just works:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
See http://stackoverflow.com/a/26036775/26167
…since 2014 http://james.newtonking.com/archive/2014/04/30/json-net-6-0-release-3-serialize-all-the-f