【问题标题】:F# syntax for async controller methods in ASP.NET CoreASP.NET Core 中异步控制器方法的 F# 语法
【发布时间】:2016-09-21 20:41:16
【问题描述】:

我是 F# 新手,正在尝试将一些 C# ASP.NET Core 代码转换为 F#

有一个 C# 控制器 here 和一个工作翻译的 F# 控制器 here

即使我得到它的工作,我似乎无法弄清楚如何使控制器动作异步。这些方法在注入的 Commands 对象和 Queries 对象上调用异步代码。命令和查询目前在 C# 中实现。

例如,一些异步 C# 控制器方法是:

    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);
    }

我已经将它们翻译成 F#,如下所示:

    [<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   

这些方法有效,但我认为它们没有正确完成,因为它们没有等待查询和命令上的异步调用。我已经反复试验了几个小时,但我为使控制器方法异步所做的每一次尝试都会导致它们不会向浏览器返回任何数据,即使它们返回 200 状态代码也是如此。你可以在F# controller看到我的一些尝试被注释掉了

希望一些 F# 大师可以帮助我正确翻译这些方法。目前在使用 ASP.NET Core 的 F# 方面存在一些非常糟糕的工具问题,这使得像我这样的新手更加困难。我在readme中提到了这些问题

代码中有一些额外的方法,但我认为如果我可以学习如何解决这些方法,那么相同的解决方案可能会适用于其他方法。

代码位于公共存储库中,因此只要您安装了最新的 VS 更新和最新的 ASP.NET Core 工具,您就可以轻松地在 VS 2015 中试用它

更新:

感谢 Mark Seemann 的链接帖子,我能够让这个方法异步工作

[<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

使用辅助函数

let asyncReturn x = async { return x }

我还在为这个方法苦苦挣扎

[<HttpGet>]
member __.Get() =
    async {
           let! data = __.Queries.GetAll() |> asyncReturn
           return data  }
        |> Async.StartAsTask

从这个 C# 方法翻译而来:

[HttpGet]
public async Task<IEnumerable<ToDoItem>> Get()
{
    return await queries.GetAll();
}

异步 ​​F# 方法有效,但生成的 json 输出与 C# 版本不同

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}

所以我仍然可以使用一些帮助来让 F# 版本产生预期的输出

【问题讨论】:

  • 尝试 Async.AwaitTask
  • 你可以在这篇博文的最后看到一个异步控制器动作的例子:blog.ploeh.dk/2016/04/11/async-as-surrogate-io这有帮助吗?
  • @mark-seemann 谢谢!感谢您的帖子,我取得了一些进展,并更新了我的问题,希望获得进一步的见解
  • queries.GetAll()的返回类型是什么?
  • 它返回一个Task of List of ToDoItem

标签: f# asp.net-core


【解决方案1】:

2016 年 9 月 28 日更新

感谢 Ruben Bartelink,这就是我的控制器现在正确实现为异步并处理 C# 和 F# 异步模式之间不同的细微差别的样子:

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() :> _ } 

对于有兴趣学习 F# 尤其是在 ASP.NET Core 中使用的其他任何人,这是 proof of concept project on github 的一部分,它具有 ToDo 列表后端 Web api 的 C# 和 F# 实现,两者都从前端使用最后用聚合物网络组件实现。模型和数据访问也用两种语言实现,以便为像我这样的 C# 开发人员学习 F# 提供一个很好的比较

【讨论】:

  • 您可以将方法声明为:IActionResult,并将正文中的:&gt; IActionResult 替换为:&gt; _。对于(__.Commands.Add(item)) |&gt; asyncReturn |&gt; ignore,您应该只能使用do! __.Commands.Add(item)(如果它返回Async&lt;unit&gt;do!|&gt; ignorelet! _ = cmd...。IOW 不要继续asyncReturn 的重新加载跨度>
  • 谢谢@RubenBartelink!我已经根据您的建议对我的答案进行了改进,但如果我对将方法声明为 IActionResult 进行了建议的更改,则无法编译它,也许我做错了,我链接了一个要点来展示我的尝试
  • 那是因为我没有考虑清楚。结果应该是 Task:&gt; _ 才能工作。此外,如果您实际使用的是this,请不要称它为__ :)。而Option.ofObj 是处理null 周围的if 的最佳OOTB (F#4) 方法(如果您使用fsharpforfunandprofit.com/rop 进行验证,它可以更好地概括)。和`做! Async.AwaitTask(__.Commands.Add(item))` 写得更好do! this.Commands.Add item |&gt; Async.AwaitTask。如果您将 sln 与 fsproj 放在 github 上,我很乐意润色。 IIRC panesofglass 也有一个 F# ToDoMvc impl 值得一看
  • 也会评论要点
  • 再次感谢! Option.ofObj 有效,但 Task 仍然无法编译,我已经更新了要点
猜你喜欢
  • 1970-01-01
  • 2022-06-14
  • 1970-01-01
  • 2017-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-30
相关资源
最近更新 更多