Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# syntax for async controller methods in ASP.NET Core

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

like image 686
Joe Audette Avatar asked Sep 21 '16 20:09

Joe Audette


1 Answers

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#

like image 194
Joe Audette Avatar answered Oct 12 '22 03:10

Joe Audette