0 Comments

00_Title

这篇博客文章是 F#日历2020 。 非常感谢 谢尔盖 在今年组织起来的时候,一定要在阅读这篇博客之后阅读其他博客帖子。

极好 框架基于模型视图更新(MVU)模式,提供了编写功能性第一移动(和桌面)客户端的方法。如果您不熟悉Fabulous,建议您检查一下 发布 通过 提姆 (Fabulous的维护者)或官员 docs .

后端实现为已启用SignalR的ASP.NET Core Web应用程序。你可以看看如何 这里 .

为了展示如何将SignalR与Fabulous结合使用,我们将实现一个聊天应用程序。而且我觉得这叫“很棒的聊天”-看看用这个框架做很棒的事情有多么容易?因此,让我们忽略以下问题:“这个世界真的需要另一个聊天应用程序吗?!”在我们的脑海中,让我们看看我们将在此应用程序中实现什么,以突出显示如何在功能齐全的第一个移动应用程序中使用SignalR。

假设我们有以下要求:

  • 用户应通过其姓名来识别自己。
  • 用户应该能够发送消息。
  • 该应用程序处于活动状态时,用户应该能够阅读消息。

换句话说,两个操作完全由UI驱动,用户输入他的用户名,然后进入聊天。用户键入一条消息,然后按一下按钮将其发送。但是,当涉及到能够阅读消息时,该事件将从后台触发。此外,SignalR连接通常建立一次,然后用于会话的其余部分。因此,让我们在应用中创建一个模块,其中将包含与SignalR交互有关的所有操作。

module SignalR =
    let connectToServer =
        // connect to SignalR service

    let startListeningToChatMessages (connection: HubConnection) dispatch =
        // receive messages

    let sendMessage (connection: HubConnection) (message: ChatMessage) =
        // send message

用户提供其姓名后,我们将连接到该服务。不是因为它本身是必需的。但是,如果稍后我们决定添加一些适当的身份验证,则不会改变应用程序的流程。事不宜迟,让我们实现登录视图。

 该图显示了登录视图

我们用于此部分的视图如下所述。

let view model dispatch =
    View.ContentPage
        (title= " 登录 ",
         content =
             View.StackLayout
                 (verticalOptions = LayoutOptions.Center,
                  horizontalOptions = LayoutOptions.Center,
                  children =
                      [ View.Label(text = "Please enter your Username")
                        View.Entry
                            (text = model.Username,
                             maxLength = 15,
                             placeholder = "Please enter your username",
                             completed = (fun _ -> (loginUser dispatch)),
                             textChanged = fun e -> dispatch (UsernameChanged e.NewTextValue))
                        View.Button(text = " 登录 ", command = fun _ -> (loginUser dispatch))
                        ]))

一旦用户点击 登录 按钮,我们要建立SignalR连接。

let connectToServer =
    let connection =
        HubConnectionBuilder()
            .WithUrl(Config.SignalRUrl)
            .WithAutomaticReconnect()
            .Build()

    async {
        do! connection.StartAsync() |> Async.AwaitTask
        return connection
    }

Since we will want to hold on to the connection, we will invoke a Command which will be evaluated in the Update method.

let update msg (model: Model) =
    match msg with
    // ... other message handling
    | Connected connection ->
        { model with
              SignalRConnection = Some connection
              AppState = Ready },
        Cmd.none
    // ... more message handling

现在,在用户连接到聊天之后,我们将显示聊天视图。该视图允许用户键入消息,发送消息并阅读连接到该服务的其他用户的响应或问题。

 该图显示了实际的聊天视图

Let's start with writing messages. Similar as with the username we again have an Entry and a button for sending the messages. Once the user sends a message, we invoke SendAsync in our SignalR module:

let sendMessage (connection: HubConnection) (message: ChatMessage) =
    async {
        let jsonMessage = JsonSerializer.Serialize(message)

        do! connection.SendAsync("SendMessage", jsonMessage)
            |> Async.AwaitTask
    }

So thus far, we have connected to the SignalR service, and we can send messages to the server. But we are still missing one essential part, and that is how we can receive messages from the backend service. What we need to do is register a listener to a specific SignalR-method (called NewMessage). We can implement our function as follows:

let startListeningToChatMessages (connection: HubConnection) dispatch =
    let handleReceivedMessage (msg: string) =
        printfn "Received message: %s" msg
        dispatch (Msg.MessageReceived(JsonSerializer.Deserialize<ChatMessage>(msg)))
        ()

    connection.On<string>("NewMessage", handleReceivedMessage)

现在,在处理程序方法中,每当从SignalR服务收到新消息时,我们将调度一个命令。因此,让我们从一开始就扩展登录功能,不仅可以创建连接,还可以注册接收者。

let loginUser dispatch =
    async {
        let! connection = SignalR.connectToServer
        dispatch (Msg.Connected connection)

        SignalR.startListeningToChatMessages connection dispatch
        |> ignore

        dispatch (Msg.LoggedIn)
    }
    |> Async.StartImmediate

We pass in the dispatcher from the view method when registering. This allows us to dispatch a command, i.e. invoke the update method and add the new message to our list of chat messages:

let update msg (model: Model) =
    match msg with
    // ... other message handling
    | MessageReceived chatMessage ->
        { model with
              Messages = chatMessage :: model.Messages },
        Cmd.none
    // ... more message handling

这样,用户将能够与当前正在使用聊天程序的任何人发送和接收聊天消息。

结论

这就是我们可以在Fabulous应用程序中使用SignalR并创建Fabulous Chat应用程序的方式。也许我们仍然需要仔细研究设计和安全性,才能真正赢得这个声誉。

您还看到了如何处理不是源自用户输入而是在后台发生的事件。每当您使用一些在后台执行其他代码的代码时,都可以使用此技术。

您可以在上找到该应用程序的完整示例 的GitHub .

标题照片作者: 泰勒·拉斯托维奇(Tyler Lastovich) 像素

发表评论