Fable.Remoting 是 Fable 和 .NET 应用程序的 RPC 通信层,它抽象了 Http 和 Json,让您仅根据在编译时静态检查的纯无状态函数来考虑客户端-服务器交互:
此接口是一种记录类型,其中每个字段都是Async<'T>
或返回Async<'T>
的函数
类型 IGreetingApi = { 问候:字符串->异步<字符串>}
让greetingApi = { greet = fun name ->async { letgreeting = sprintf "Hello, %s" name returngreeting}}// 将实现公开为 HTTP servicelet webApp = 远程处理.createApi() |> Remoting.fromValuegreetingApi
// 获取 serviceletgreetingApi 的类型化代理 = 远程处理.createApi() |> Remoting.buildProxy<IGreetingApi>// 开始使用 serviceasync { 让!消息=greetingApi.greet“世界” printfn "%s" 消息 // 你好,世界}
就是这样,没有 HTTP,没有 JSON,而且都是类型安全的。
SAFE-TodoList 一个简单的全栈Todo列表应用程序(初学者)
tabula-rasa 一个真实世界的博客平台(中级)
使用事件采购实现的 Yobo 瑜伽课程预订系统(高级)
该库在后端的任何地方运行:作为 Suave WebPart
、作为 Giraffe/Saturn HttpHandler
或作为 Asp.NET Core 中间件的任何其他框架。客户端可以是 Fable 或 .NET 应用程序。
“Fable.Remoting 解决了一个古老的问题,即在编译时保持前端代码与后端代码同步,并且使用像 F# 一样令人愉快的语言”- David Falkner
使用 SAFE 简化模板,其中 Fable.Remoting 已设置并准备就绪
图书馆 | 版本 |
---|---|
神鬼寓言.Remoting.MsgPack | |
寓言.远程.客户端 | |
寓言.Remoting.Json | |
神鬼寓言远程服务器 | |
寓言.远程.Suave | |
寓言.远程.长颈鹿 | |
神鬼寓言.Remoting.AspNetCore | |
Fable.Remoting.DotnetClient | |
Fable.Remoting.AzureFunctions.Worker | |
神鬼寓言.Remoting.AwsLambda |
创建一个新的 F# 控制台应用程序:
dotnet new console -lang F#
定义要在客户端和服务器之间共享的类型:
// SharedTypes.fsmodule SharedTypestype Student = {Name : stringAge : int}// 服务器和客户端之间的共享规范 type IStudentApi = {studentByName : string -> Async<Student option>allStudents : unit -> Async<list<Student>>}
IStudentApi
类型非常重要,这是服务器和客户端之间协议的规范。 Fable.Remoting
期望此类类型仅具有在最终结果上返回Async
函数:
Async<A>A -> Async<B>A -> B -> Async<C> // 等等...
尝试将此类类型放入单独的文件中,以便稍后从客户端引用这些文件
然后在服务器上提供IStudentApi
的实现:
打开 SharedTypeslet getStudents() = 异步{返回[{名称=“迈克”; 年龄=23; } 名字=“约翰”; 年龄=22; } 名字=“戴安娜”;年龄=22; }] }让 findStudentByName 名称 = 异步{让!学生 = getStudents()let 学生 = List.tryFind (fun 学生 -> 学生.Name = 姓名) 学生返回学生 }let StudentApi : IStudentApi = {studentByName = findStudentByName 所有学生 = getStudents}
现在我们已经实现了studentApi
,您可以将其公开为来自不同 Web 框架的 Web 服务。我们从温柔开始
使用 Paket 从 Nuget 安装库:
paket add Fable.Remoting.Suave --project /path/to/Project.fsproj
从值studentApi
创建 WebPart
open Suaveopen Fable.Remoting.Serveropen Fable.Remoting.Suavelet webApp : WebPart =Remoting.createApi()|> Remoting.fromValue StudentApi|> Remoting.buildWebPart//启动Web服务器startWebServer defaultConfig webApp
是的,就是这么简单。您可以将webApp
值视为伪代码中的以下内容:
让网络应用= 选择 [ POST >=> 路径“/IStudentApi/studentByName” >=> (* 反序列化请求正文(来自 json)*) >=> (* 使用反序列化输入调用 StudentApi.getStudentByName *) >=> (* 向客户端提供序列化的输出(转为 json)*) // 其他路线 ]
您可以从 Fable.Remoting.Server(推荐)启用诊断日志记录,以查看该库如何在幕后发挥它的魔力:)
让 webApp =Remoting.createApi()|> Remoting.fromValue StudentApi|> Remoting.withDiagnosticsLogger (printfn "%s")|> Remoting.buildWebPart
使用 paket 从 Nuget 安装软件包
paket add Fable.Remoting.AspNetCore --project /path/to/Project.fsproj
现在您可以将远程处理程序配置为 AspNetCore 中间件
let webApp =Remoting.createApi()|> Remoting.fromValue StudentApilet configureApp (app : IApplicationBuilder) =// 将 Remoting 处理程序添加到 ASP.NET Core 管道app.UseRemoting webApp[<EntryPoint>]let main _ =WebHostBuilder().UseKestrel ().Configure(Action<IApplicationBuilder> configureApp).Build().Run()0
您可以按照 Suave 部分进行库安装,它将变成:
paket add Fable.Remoting.Giraffe --project /path/to/Project.fsproj
现在,通过打开Fable.Remoting.Giraffe
命名空间,您将从值server
获取 HttpHandler,而不是 WebPart:
open Giraffeopen Fable.Remoting.Serveropen Fable.Remoting.Giraffelet webApp : HttpHandler =Remoting.createApi()|> Remoting.fromValue StudentApi|> Remoting.buildHttpHandlerlet configureApp (app : IApplicationBuilder) =// 将 Giraffe 添加到 ASP.NET Core pipelineapp .UseGiraffe webApplet configureServices(services:IServiceCollection)=//添加Giraffe dependencyservices.AddGiraffe() |>ignore[<EntryPoint>]let main _ =WebHostBuilder().UseKestrel().Configure(Action<IApplicationBuilder> configureApp).ConfigureServices(configureServices).Build().Run()0
您可以使用 Giraffe 库生成的相同webApp
。
open Saturnopen Fable.Remoting.Serveropen Fable.Remoting.Giraffelet webApp : HttpHandler =Remoting.createApi()|> Remoting.fromValue StudentApi|> Remoting.buildHttpHandlerlet app = application {url "http://127.0.0.1:8083/"use_router webApp}运行应用程序
要在隔离模式下使用 Azure Functions 并将自定义 HttpTrigger 作为无服务器远程处理服务器,只需安装:
dotnet add package Fable.Remoting.AzureFunctions.Worker
或使用包
paket add Fable.Remoting.AzureFunctions.Worker --project /path/to/Project.fsproj
由于 Azure Functions 对 HttpHandler 一无所知,因此我们需要使用内置的HttpRequestData
和HttpResponseData
对象。幸运的是,我们有Remoting.buildRequestHandler
和HttpResponseData.fromRequestHandler
函数来救援:
open Fable.Remoting.Serveropen Fable.Remoting.AzureFunctions.Workeropen Microsoft.Azure.Functions.Workeropen Microsoft.Azure.Functions.Worker.Httpopen Microsoft.Extensions.Loggingtype Functions(log:ILogger<Functions>) =[<Function("Index ")>]member _.Index ([<HttpTrigger(AuthorizationLevel.Anonymous, 路由 = "{*any}")>] req: HttpRequestData, ctx: FunctionContext) =Remoting.createApi()|> Remoting.withRouteBuilder FunctionsRouteBuilder.apiPrefix|> Remoting.fromValue myImplementation|> Remoting.buildRequestHandler|> HttpResponseData.fromRequestHandler req
当然,每个 Function App 都有一个实现并不理想,因此HttpResponseData.fromRequestHandlers
可以解决这个问题:
类型 Functions(log:ILogger<Functions>) =[<Function("Index")>]member _.Index ([<HttpTrigger(AuthorizationLevel.Anonymous, Route = "{*any}")>] req: HttpRequestData, ctx : FunctionContext) =let handlerOne =Remoting.createApi()|> Remoting.withRouteBuilder FunctionsRouteBuilder.apiPrefix|> Remoting.fromValue myImplementationOne|> Remoting.buildRequestHandler 让 handlerTwo =Remoting.createApi()|> Remoting.withRouteBuilder FunctionsRouteBuilder.apiPrefix|> Remoting.fromValue myImplementationTwo|> Remoting.buildRequestHandler [ handlerOne; handlerTwo ] |> HttpResponseData.fromRequestHandlers 要求
使用 Paket 从 nuget 安装Fable.Remoting.Client
:
paket add Fable.Remoting.Client --project /path/to/Project.fsproj
将共享类型引用到您的客户端项目
<Compile Include="path/to/SharedTypes.fs" />
开始使用该库:
open Fable.Remoting.Clientopen SharedTypes// StudentApi : IStudentApilet StudentApi =Remoting.createApi()|> Remoting.buildProxy<IStudentApi>async { // 学生:学生[] 让!学生 = StudentApi.allStudents() for Student in Students do// Student : Studentprintfn "学生 %s 已 %d 岁" Student.Name Student.Age}|> Async.StartImmediate
最后,当您使用webpack-dev-server
时,您必须更改配置:
开发服务器:{ contentBase: 解析('./public'), 端口:8080}
对此:
开发服务器:{ contentBase: 解析('./public'), 端口:8080, proxy: {'/*': { // 告诉 webpack-dev-server 将所有从客户端的请求重新路由到服务器 target: "http://localhost:5000",// 假设后端服务器托管在端口上开发期间 5000 次更改来源:true}}
就是这样!
您还可以在非寓言项目中使用客户端功能,例如控制台、桌面或移动应用程序。
使用 Paket 从 nuget 安装Fable.Remoting.DotnetClient
:
paket add Fable.Remoting.DotnetClient --project /path/to/Project.fsproj
引用客户端项目中的共享类型
<Compile Include="path/to/SharedTypes.fs" />
开始使用该库:
open Fable.Remoting.DotnetClientopen SharedTypes// StudentApi : IStudentApilet StudentApi = Remoting.createApi“http://localhost:8085” |> Remoting.buildProxy<IStudentApi>async { // 学生:学生[] 让!学生 = StudentApi.allStudents() for Student in Students do// Student : Studentprintfn "学生 %s 已 %d 岁" Student.Name Student.Age}|> Async.StartImmediate
请注意,与 Fable 客户端不同,您需要提供后端的基本 Url,因为 dotnet 客户端将与后端分开部署。
向IStudentApi
添加另一个记录字段函数
实现该功能
重启服务器和客户端
完毕!您现在也可以从客户端使用该功能。
如果您对该库的实现方式感兴趣,请参阅以下文章(有点过时,但为您提供了该机制的概述):使用 F# 进行静态类型的客户端-服务器通信:概念验证