Fable.Remoting é uma camada de comunicação RPC para aplicativos Fable e .NET, ele abstrai Http e Json e permite que você pense em suas interações cliente-servidor apenas em termos de funções puras sem estado que são verificadas estaticamente em tempo de compilação:
Esta interface é um tipo de registro onde cada campo é Async<'T>
ou uma função que retorna Async<'T>
digite IGreetingApi = { saudação: string -> Async<string>}
deixe saudaçãoApi = { saudação = nome divertido ->async { let saudação = sprintf "Olá, %s" nome retornar saudação}}// Expor a implementação como um servicelet HTTP webApp = Remoting.createApi() |> Remoting.fromValue saudaçãoApi
// obtém um proxy digitado para o servicelet saudaçãoApi = Remoting.createApi() |> Remoting.buildProxy<IGreetingApi>// Comece a usar o serviceasync { deixar! mensagem = saudaçãoApi.greet "Mundo" printfn mensagem "%s" // Olá, mundo}
É isso, sem HTTP, sem JSON e tudo com segurança de tipo.
SAFE-TodoList Um aplicativo simples de lista de tarefas completa (iniciante)
tabula-rasa uma plataforma de blog do mundo real (intermediário)
Sistema de reserva de aulas Yobo Yoga implementado com Event Sourcing (avançado)
A biblioteca é executada em qualquer lugar no backend: como Suave WebPart
, como Giraffe/Saturn HttpHandler
ou qualquer outra estrutura como middleware Asp.NET Core. Os clientes podem ser aplicativos Fable ou .NET.
"Fable.Remoting resolve o antigo problema de manter seu código front-end sincronizado com seu código backend em tempo de compilação e em uma linguagem tão agradável de usar quanto F#" - David Falkner
Use o modelo SAFE Simplificado onde o Fable.Remoting já está configurado e pronto para uso
Biblioteca | Versão |
---|---|
Fable.Remoting.MsgPack | |
Fábula.Remoting.Cliente | |
Fábula.Remoting.Json | |
Fable.Remoting.Servidor | |
Fábula.Remoting.Suave | |
Fábula.Remoting.Girafa | |
Fábula.Remoting.AspNetCore | |
Fable.Remoting.DotnetClient | |
Fable.Remoting.AzureFunctions.Worker | |
Fable.Remoting.AwsLambda |
Crie um novo aplicativo de console F#:
dotnet new console -lang F#
Defina os tipos que deseja compartilhar entre cliente e servidor:
// SharedTypes.fsmodule SharedTypestype Student = {Name : stringAge : int} // Especificações compartilhadas entre Server e Clienttype IStudentApi = {studentByName : string -> Async<Student option>allStudents : unit -> Async<list<Student>>}
O tipo IStudentApi
é muito importante, é a especificação do protocolo entre seu servidor e cliente. Fable.Remoting
espera que esse tipo tenha apenas funções retornando Async
no resultado final:
Assíncrono<A>A -> Assíncrono<B>A -> B -> Assíncrono<C>// etc...
Tente colocar esses tipos em arquivos separados para referenciá-los posteriormente no Cliente
Em seguida, forneça uma implementação para IStudentApi
no servidor:
abra SharedTypeslet getStudents() = assíncrono {return [{Nome = "Mike"; Idade = 23; }{ Nome = "João"; Idade = 22; }{ Nome = "Diana"; Idade = 22; }] }deixe findStudentByName nome = assíncrono {deixe! alunos = getStudents () deixar aluno = List.tryFind (aluno divertido -> aluno. Nome = nome) alunos retornar aluno } deixar alunoApi: IStudentApi = {studentByName = findStudentByName todos os alunos = getAlunos}
Agora que temos a implementação studentApi
, você pode expô-la como um serviço web a partir de diferentes frameworks web. Começamos com Suave
Instale a biblioteca do Nuget usando Paket:
paket add Fable.Remoting.Suave --project /path/to/Project.fsproj
Crie uma WebPart a partir do valor studentApi
abra Suaveopen Fable.Remoting.Serveropen Fable.Remoting.Suavelet webApp : WebPart =Remoting.createApi()|> Remoting.fromValue studentApi|> Remoting.buildWebPart// inicia o servidor webstartWebServer defaultConfig webApp
Sim, é tão simples. Você pode pensar no valor webApp
como se fosse o seguinte em pseudocódigo:
deixe webApp = escolher [ POST >=> caminho "/IStudentApi/studentByName" >=> (* desserializar corpo da solicitação (de json) *) >=> (* invoque studentApi.getStudentByName com a entrada desserializada *) >=> (* devolve ao cliente a saída serializada (para json) *) // outras rotas ]
Você pode ativar o registro de diagnóstico em Fable.Remoting.Server (recomendado) para ver como a biblioteca está fazendo sua mágica nos bastidores :)
deixe webApp =Remoting.createApi()|> Remoting.fromValue studentApi|> Remoting.withDiagnosticsLogger (printfn "%s")|> Remoting.buildWebPart
Instale o pacote do Nuget usando paket
paket add Fable.Remoting.AspNetCore --project /path/to/Project.fsproj
Agora você pode configurar seu manipulador remoto como middleware AspNetCore
let webApp =Remoting.createApi()|> Remoting.fromValue studentApilet configureApp (app: IApplicationBuilder) =// Adicionar manipulador Remoting ao pipeline do ASP.NET Coreapp.UseRemoting webApp[<EntryPoint>]let main _ =WebHostBuilder().UseKestrel ().Configure(Action<IApplicationBuilder> configureApp).Build().Run()0
Você pode acompanhar a parte Suave até a instalação da biblioteca, onde ela se tornará:
paket add Fable.Remoting.Giraffe --project /path/to/Project.fsproj
Agora, em vez de uma WebPart, ao abrir o namespace Fable.Remoting.Giraffe
, você obterá um HttpHandler do server
de valor:
open Giraffeopen Fable.Remoting.Serveropen Fable.Remoting.Giraffelet webApp: HttpHandler =Remoting.createApi()|> Remoting.fromValue studentApi|> Remoting.buildHttpHandlerlet configureApp (app: IApplicationBuilder) =// Adicionar Giraffe ao pipelineapp do ASP.NET Core .UseGiraffe webApplet configureServices (serviços: IServiceCollection) =// Adicionar Giraffe dependenciesservices.AddGiraffe() |> ignore[<EntryPoint>]deixe main _ =WebHostBuilder().UseKestrel().Configure(Action<IApplicationBuilder> configureApp).ConfigureServices(configureServices).Build(). Executar()0
Você pode usar o mesmo webApp
gerado pela biblioteca Giraffe.
abra 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}executar aplicativo
Para usar o Azure Functions em modo isolado com HttpTrigger personalizado como servidor remoto sem servidor, basta instalar:
dotnet add package Fable.Remoting.AzureFunctions.Worker
ou usando pacote
paket add Fable.Remoting.AzureFunctions.Worker --project /path/to/Project.fsproj
Como o Azure Functions não sabe nada sobre HttpHandler, precisamos usar objetos HttpRequestData
e HttpResponseData
integrados. Felizmente, temos as funções Remoting.buildRequestHandler
e HttpResponseData.fromRequestHandler
para nos ajudar:
abra 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 ")>]membro _.Index ([<HttpTrigger(AuthorizationLevel.Anonymous, Route = "{*any}")>] req: HttpRequestData, ctx: FunctionContext) =Remoting.createApi()|> Remoting.withRouteBuilder FunctionsRouteBuilder.apiPrefix|> Remoting.fromValue myImplementation|> Remoting.buildRequestHandler|> HttpResponseData.fromRequestHandler requerimento
É claro que ter uma implementação por aplicativo de função não é o ideal, então HttpResponseData.fromRequestHandlers
está aqui para ajudar:
tipo Functions(log:ILogger<Functions>) =[<Function("Index")>]membro _.Index ([<HttpTrigger(AuthorizationLevel.Anonymous, Route = "{*any}")>] req: HttpRequestData, ctx : FunctionContext) =let handlerOne =Remoting.createApi()|> Remoting.withRouteBuilder FunctionsRouteBuilder.apiPrefix|> Remoting.fromValue myImplementationOne|> Remoting.buildRequestHandler let handlerTwo =Remoting.createApi()|> Remoting.withRouteBuilder FunctionsRouteBuilder.apiPrefix|> Remoting.fromValue myImplementationTwo|> Remoting.buildRequestHandler [ handlerOne; handlerTwo] |> HttpResponseData.fromRequestHandlers req
Instale Fable.Remoting.Client
do nuget usando Paket:
paket add Fable.Remoting.Client --project /path/to/Project.fsproj
Faça referência aos tipos compartilhados ao seu projeto cliente
<Compile Include="path/to/SharedTypes.fs" />
Comece a usar a biblioteca:
abra Fable.Remoting.Clientopen SharedTypes// studentApi : IStudentApilet studentApi =Remoting.createApi()|> Remoting.buildProxy<IStudentApi>async { //alunos: Aluno[] deixar! alunos = estudanteApi.allStudents() para aluno em alunos do // aluno: Studentprintfn "O aluno %s tem %d anos" student.Name student.Age}|> Async.StartImmediate
Finalmente, quando você estiver usando webpack-dev-server
, você deve alterar a configuração disto:
devServidor: { contentBase: resolve('./public'), porta: 8080}
para isso:
devServidor: { contentBase: resolve('./public'), porta: 8080, proxy: {'/*': { // diz ao webpack-dev-server para redirecionar todas as solicitações do cliente para o servidor alvo: "http://localhost:5000",// assumindo que o servidor backend está hospedado na porta 5000 durante o desenvolvimento changeOrigin: true}}
É isso!
Você também pode usar a funcionalidade do cliente em projetos que não sejam do Fable, como um console, desktop ou aplicativo móvel.
Instale Fable.Remoting.DotnetClient
do nuget usando Paket:
paket add Fable.Remoting.DotnetClient --project /path/to/Project.fsproj
Faça referência aos tipos compartilhados em seu projeto cliente
<Compile Include="path/to/SharedTypes.fs" />
Comece a usar a biblioteca:
abra Fable.Remoting.DotnetClientopen SharedTypes// studentApi: IStudentApilet studentApi = Remoting.createApi "http://localhost:8085" |> Remoting.buildProxy<IStudentApi>async { //alunos: Aluno[] deixar! alunos = estudanteApi.allStudents() para aluno em alunos do // aluno: Studentprintfn "O aluno %s tem %d anos" student.Name student.Age}|> Async.StartImmediate
Observe aqui que, diferentemente do cliente Fable, você precisará fornecer o URL base do back-end porque o cliente dotnet será implantado separadamente do back-end.
Adicione outra função de campo de registro ao IStudentApi
Implemente essa função
Reinicie o servidor e o cliente
Feito! Agora você também pode usar essa função do cliente.
Consulte o artigo a seguir se estiver interessado em como esta biblioteca é implementada (um pouco desatualizada, mas oferece uma visão geral do mecanismo) Comunicação cliente-servidor estaticamente digitada com F#: prova de conceito