With F#, it's easy to create DTOs for use with the ASP.NET Web API, using record types.

When writing HTTP (or RESTful) web services with the ASP.NET Web API, the most normal approach is to define Data Transfer Objects (DTOs), which represents the data structures that go on the wire in the form of JSON or XML. While I usually call these boundary objects for renditions (a bit punny), a more normal terminology is, indeed, DTOs.

To enable the default .NET serializers (and particularly, deserializers) to do their magic, these DTOs must be mutable and have a default constructor. Not particularly something that seems to fit nicely with F#.

Until recently, I've been declaring such DTOs like this in F#:

type HomeRendition() =
    [<DefaultValue>] val mutable Message : string
    [<DefaultValue>] val mutable Time : string

However, then I discovered the [<CLIMutable>] attribute. This effectively turn a standard F# record type into something that can be serialized with the normal .NET serializers. This means that it's possible to redefine the above HomeRendition like this:

[<CLIMutable>]
type HomeRendition = {
    Message : string
    Time : string }

This is much better, because this looks like a proper immutable record type from the perspective of the F# compiler. However, from a CLI perspective, the HomeRendition class has a default constructor, and mutable properties.

A DTO defined like this still works with the ASP.NET Web API, although by default, the serialization looks a bit strange:

<HomeRendition>
  <Message_x0040_>
    This message is served by a pure F# ASP.NET Web API implementation.
  </Message_x0040_>
  <Time_x0040_>2013-10-15T23:32:42.6725088+02:00</Time_x0040_>
</HomeRendition>

The reason for that is that the [<CLIMutable>] attribute causes the record type to be compile with auto-generated internal mutable fields, and these are named by appending an @ character - in this case, the field names become Message@ and Time@. Since the unicode value for the @ character is x0040, these field names become Message_x0040_ and Time_x0040_.

Wait a minute! Did I say internal fields? Yes, I did. Then why are the internal fields being serialized instead of the public properties? Well, what can I say, other than the DataContractSerializer once again proves to be a rather poor choice of default serializer. Yet another reason to use a sane XML serializer.

No doubt, one of my readers can come up with a good solution for the DataContractSerializer too, but since I always switch my Web API services to use a proper XML serializer, I don't really care:

GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer <- true

Now all is well again:

<HomeRendition>
  <Message>
    This message is served by a pure F# ASP.NET Web API implementation.
  </Message>
  <Time>2013-10-15T23:50:06.9025322+02:00</Time>
</HomeRendition>

That's it: much easier, and more robust, Web API DTOs with F# record types. Just apply the [<CLIMutable>] attribute.


Comments

Hi Mark, thanks for this, I blogged about serialization of record types last year. the only way I could get XML serialization to work nicely with the DataContractSerializer was to put a DataMember attribute on each field of the record and a DataContract attribute on the record itself.

JSON serialization can be handled nicely by default in WEP API however by annotating the class with JsonObject(MemberSerialization=MemberSerialization.OptOut). I have checked and by combining our 2 methods the WEB API can serialize nicely as both JSON and XML.

2013-10-16 12:54 UTC

Hi Patrick, thank you for writing. For JSON serialization, I usually just add this configuration:

config.Formatters.JsonFormatter.SerializerSettings.ContractResolver <-
    Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()

This not only gives the JSON idiomatic casing, but also renders CLIMutable F# records nicely.

2013-10-16 19:38 UTC

Any thoughts on trying to return a Discriminated Union with an F# WebAPI controller? I notice that asp.net throws a runtime InvalidOperationException when you try to do this. Furthermore, the CLIMutable Attribute isn't even allowed on a DU. I find this counter intuitve, however, as you can work with F# DUs in C# code and determine the type using the Tag propery that is visible only in C#/VB.net. Why can't this Tag property be serialized and returned as part of a DU DTO?

2013-12-27 20:10 UTC

Nick, I haven't tried returning a Discriminated Union from a Web API Controller. Why would I want to do that? As I've previously described, at the boundaries, applications aren't object-oriented, and similarly, they aren't functional either. What would it mean to return a Discriminated Union? How would it render as JSON or XML?

Apart from that, I have no idea how a Discriminated Union compiles to IL...

2013-12-29 9:07 UTC

Hey Mark, It's interesting that Json serialization works even without `CLIMutable`, both when reading or writing data.

For example this works properly:

type HomeRecord = {
    Message : string
    Time : string
}

type InputModel = {
    Message : string
}

type HomeController() =
    inherit ApiController()
    member this.Get() =
        this.Ok({ Message = "Hello from F#!"; Time = DateTime.Now.ToString() })
    member this.Post(input : InputModel) =
        this.Ok(input.Message)

But only for the Json serializer, the XML one does not work without `CLIMutable`. Do you know what makes the Json serialization work? Is there special support implemented in the Json serializer for the immutable F# record types?

Thanks, Mark

2017-02-04 17:00 UTC

Mark, thank you for writing. ASP.NET Web API uses JSON.NET for JSON serialization, and, as far as know, it has built-in F# support. It can also serialize discriminated unions.

The .NET XML serializer doesn't have built-in F# support.

2017-02-04 18:07 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Tuesday, 15 October 2013 22:01:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!