I'm new to F# and trying to translate some C# ASP.NET Core code into F#
There is a C# controller here, and a working translated F# controller here
even though I got it working, I can't seem to figure out how to make the controller actions async. The methods call async code on a Commands object and a Queries object that are injected. The Commands and Queries are currently implemented in C#.
So for example a couple of the async C# controller methods are:
public async Task<IEnumerable<ToDoItem>> Get()
{
return await queries.GetAll();
}
[HttpGet("{id}", Name = "GetTodo")]
public async Task<IActionResult> GetById(string id)
{
var item = await queries.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
public async Task<IActionResult> Create([FromBody] ToDoItem item)
{
if (item == null)
{
return BadRequest();
}
if (string.IsNullOrEmpty(item.Id)) item.Id = Guid.NewGuid().ToString();
await commands.Add(item);
return CreatedAtRoute("GetTodo", new { id = item.Id }, item);
}
and I've translated those to F# like this:
[<HttpGet>]
member __.Get() =
__.Queries.GetAll() // this should be awaited
[<HttpGet("{id}", Name = "GetFSTodo")>]
member __.GetToDoItem(id) =
let data = __.Queries.Find(id) // this should be awaited
if isNull data
then __.NotFound() :> IActionResult
else
new ObjectResult(data) :> IActionResult
[<HttpPost>]
member __.Create([<FromBody>] item:ToDoItem) =
item.Id <- Guid.NewGuid().ToString()
(__.Commands.Add(item)) |> ignore // this should be awaited
let rv = new RouteValueDictionary()
rv.Add("id",item.Id)
__.CreatedAtRoute("GetTodo", rv, item) :> IActionResult
These methods work but I think they are not correctly done since they aren't awaiting the async calls on Queries and Commands. I've thrashed for some hours with trial and error but every attempt I've made to make the controller methods async results in them not returning any data to the browser even though they return a 200 status code. You can see some of my attempts commented out in the F# controller
Hoping some F# guru(s) could help me translate those methods correctly. There are some pretty bad tooling issues currently in terms of F# with ASP.NET Core which makes it more difficult for a newbie like me. I've mentioned those issues in the readme
There are a few additional methods in the code but I figure if I can learn how to solve for these methods then the same solution will probably apply to the other methods.
The code is in a public repository so you can easily try it in VS 2015 as long as you have the latest VS updates and the latest ASP.NET Core tooling installed
UPDATE:
thanks to the linked post by Mark Seemann, I was able to get this method working async
[<HttpGet("{id}", Name = "GetFSTodo")>]
member __.GetToDoItem(id) =
async {
let! data = __.Queries.Find(id) |> asyncReturn
if isNull data
then return __.NotFound() :> IActionResult
else
return new ObjectResult(data) :> IActionResult }
|> Async.StartAsTask
by using the helper function
let asyncReturn x = async { return x }
I'm still struggling with this method
[<HttpGet>]
member __.Get() =
async {
let! data = __.Queries.GetAll() |> asyncReturn
return data }
|> Async.StartAsTask
which is translated from this C# method:
[HttpGet]
public async Task<IEnumerable<ToDoItem>> Get()
{
return await queries.GetAll();
}
the async F# method works but it produces different json output than the C# version
C#
[{"id":"4f4e1596-6a48-4854-9982-7a2568aa1b1b","title":"add input validation","isDone":false,"dateAdded":"2016-09-20T21:16:04.8791044Z"},{"id":"9929b657-6a53-40b6-8c1c-1e4d0db593cd","title":"make F# controller async","isDone":false,"dateAdded":"2016-09-21T19:36:44.6650776Z"},{"id":"5bb5f544-6289-4051-ad65-d0dc016128e7","title":"learn F# basics","isDone":true,"dateAdded":"2016-09-22T11:59:00"},{"id":"e5e06118-c49f-496a-8175-9719ea72beed","title":"monkey business","isDone":false,"dateAdded":"2016-09-22T16:22:20.3133161Z"},{"id":"af0db8f2-6b49-4e31-86fa-e27c8e091f42","title":"funky bidness","isDone":false,"dateAdded":"2016-09-22T16:23:35.1175195Z"}]
F#
{"result":[{"id":"4f4e1596-6a48-4854-9982-7a2568aa1b1b","title":"add input validation","isDone":false,"dateAdded":"2016-09-20T21:16:04.8791044Z"},{"id":"9929b657-6a53-40b6-8c1c-1e4d0db593cd","title":"make F# controller async","isDone":false,"dateAdded":"2016-09-21T19:36:44.6650776Z"},{"id":"5bb5f544-6289-4051-ad65-d0dc016128e7","title":"learn F# basics","isDone":true,"dateAdded":"2016-09-22T11:59:00"},{"id":"e5e06118-c49f-496a-8175-9719ea72beed","title":"monkey business","isDone":false,"dateAdded":"2016-09-22T16:22:20.3133161Z"},{"id":"af0db8f2-6b49-4e31-86fa-e27c8e091f42","title":"funky bidness","isDone":false,"dateAdded":"2016-09-22T16:23:35.1175195Z"}],"id":65,"exception":null,"status":5,"isCanceled":false,"isCompleted":true,"creationOptions":0,"asyncState":null,"isFaulted":false}
so I still could use some help on how to make the F# version produce the expected output
UPDATED 2016-09-28
Thanks to Ruben Bartelink this is what my controller looks like now correctly implemented as async and handling the nuances that differ between C# and F# async patterns:
namespace FSharp.WebLib
open System
open Microsoft.AspNetCore.Mvc
open Microsoft.AspNetCore.Routing
open Microsoft.AspNetCore.JsonPatch
open FSharp.Models
module ActionResult =
let ofAsync (res: Async<IActionResult>) =
res |> Async.StartAsTask
[<Route("api/[controller]")>]
type FSToDoController(commands: IToDoCommands, queries: IToDoQueries) =
inherit Controller()
[<HttpGet>]
member this.Get() =
ActionResult.ofAsync <| async {
let! data = queries.GetAll()
return JsonResult(data) :> _ }
[<HttpGet("{id}", Name = "GetFsToDo")>]
member this.Get(id) =
ActionResult.ofAsync <| async {
let! res = queries.Find id
match res with
| None -> return this.NotFound() :> _
| Some data -> return ObjectResult(data) :> _ }
// create
[<HttpPost>]
member this.Post([<FromBody>] item:ToDoItem) =
ActionResult.ofAsync <| async {
if not this.ModelState.IsValid then
return this.BadRequest() :> _
else
let item = { item with Id = Guid.NewGuid() |> string }
do! commands.Add item
let rv = RouteValueDictionary()
rv.Add("id",item.Id)
return this.CreatedAtRoute("GetFsToDo", rv, item) :> _ }
// update
[<HttpPut("{id}")>]
member this.Put(id:String, [<FromBody>] item:ToDoItem) =
ActionResult.ofAsync <| async {
if (not this.ModelState.IsValid) || String.IsNullOrEmpty item.Id then
return this.BadRequest() :> _
else
let! res = queries.Find id
match res with
| None -> return this.NotFound() :> _
| Some toDo ->
do! commands.Update item
return NoContentResult() :> _ }
for anyone else interested in learning F# particularly for use in ASP.NET Core, this is part of a proof of concept project on github that has both C# and F# implementations of a ToDo list back end web api both of which are consumed from a front end implemented with polymer web components. Models and data access are also implemented in both languages to provide a good comparison for C# devs like myself to learn F#
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With