Web Services Round Trip

My objective today was to have a web client save some data and retrieve it again. The data was to be stored in an F# record, representing a domain object, and be serialized and deserialized at the web service boundary.

The first issue was with JSON serialization. While a number of blogs indicated a need to change the serializerSettings ContractResolver, this does not appear to be necessary in ASP.NET Core.

This may be because of the change from app.UseMvc() to app.UseMvcCore() which is a lighter method that allows the developer to decide what MVC features are required. In my case this meant adding in app.AddJsonFormatters() so the services would serialize JSON.
One issue I keep running into is not knowing what namespace or module methods found in internet samples are in. To that end I’ve included the whole Program.fs here.

open System
open System.IO
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Mvc.Formatters.Json
open Microsoft.AspNetCore.Diagnostics
open Microsoft.Extensions.DependencyInjection

type Startup(env: IHostingEnvironment) =

    member this.ConfigureServices(services: IServiceCollection) =
        let mvc = services.AddMvcCore()
        mvc.AddJsonFormatters() |> ignore

    member this.Configure (app: IApplicationBuilder) =
        app.UseDeveloperExceptionPage() |> ignore
        app.UseMvc() |> ignore

[<EntryPoint>]
let main argv = 
    printfn "Starting"
    Logger.info "Startup"
    let host = WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).UseStartup<Startup>().Build()
    host.Run()
    0 //exit code

In contrast, the CLIMutable attribute is required on an F# record in order for it to be serializable. Without it the serialization of objects fails because MVC requires a default (empty) constructor and public properties to perform serialization. Using CLIMutable causes the record to be compiled with these.

[<CLIMutable>]
type Template = {
    Id: System.Guid
    Name: string
    Fields: FieldDefinition list
    MaintenanceProgramId: System.Guid
}

The template service (still called ‘First’ here) exposes a GET and a PUT, and passes the request onto domain layer operations (I will put together a post on the full architectural implementation when I have all the layers) to process and store it in-memory.

[<Route("api/first")>]
type FirstController() =
    inherit Controller()

    [<HttpGet>]
    member this.Get(id: System.Guid) =
        DomainOperations.GetTemplate id (new TemplateRepository())

    [<HttpPut>]
    member this.Create([<FromBody>]template: Template) =
        DomainOperations.CreateTemplate template (new TemplateCommandHandler())

Using Postman I was able to PUT the following to http://localhost:5000/first/api

{
  "id":"{{id}}",
  "name":"{{templateName}}",
  "fields":[],
  "maintenanceProgramId":"00000000-0000-0000-0000-000000000000"
}

and was returned a similar object from GET http://localhost:5000/first/api?id={{id}}
The full source as it was at this stage is available on my github.