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# 进行静态类型的客户端-服务器通信:概念验证