Fable.Remoting ist eine RPC-Kommunikationsschicht für Fable- und .NET-Apps. Sie abstrahiert Http und Json und lässt Sie Ihre Client-Server-Interaktionen nur als reine zustandslose Funktionen betrachten, die zur Kompilierungszeit statisch überprüft werden:
Diese Schnittstelle ist ein Datensatztyp, bei dem jedes Feld entweder Async<'T>
oder eine Funktion ist, die Async<'T>
zurückgibt
Typ IGreetingApi = { grüße: string -> Async<string>}
let GreetingApi = { Greet = lustiger Name ->async { let Greeting = sprintf „Hallo, %s“ Name Return Greeting}}// Stellen Sie die Implementierung als HTTP-Servicelet bereit. webApp = Remoting.createApi() |> Remoting.fromValue GreetingApi
// Holen Sie sich einen typisierten Proxy für das Servicelet GreetingApi = Remoting.createApi() |> Remoting.buildProxy<IGreetingApi>// Beginnen Sie mit der Verwendung von serviceasync { lassen! message = GreetingApi.greet „Welt“ printfn „%s“-Nachricht // Hello, World}
Das ist alles, kein HTTP, kein JSON und alles ist typsicher.
SAFE-TodoList Eine einfache Full-Stack-Todo-Listenanwendung (Anfänger)
tabula-rasa, eine realitätsnahe Blogging-Plattform (Mittelstufe)
Yobo Yoga-Kursbuchungssystem implementiert mit Event Sourcing (fortgeschritten)
Die Bibliothek läuft überall im Backend: Als Suave WebPart
, als Giraffe/Saturn HttpHandler
oder jedes andere Framework als Asp.NET Core Middleware. Clients können Fable- oder .NET-Anwendungen sein.
„Fable.Remoting löst das uralte Problem, Ihren Front-End-Code zur Kompilierungszeit mit Ihrem Backend-Code synchron zu halten, und das in einer Sprache, die so angenehm zu verwenden ist wie F#“ – David Falkner
Verwenden Sie die SAFE Simplified-Vorlage, in der Fable.Remoting bereits eingerichtet und einsatzbereit ist
Bibliothek | Version |
---|---|
Fable.Remoting.MsgPack | |
Fable.Remoting.Client | |
Fable.Remoting.Json | |
Fable.Remoting.Server | |
Fable.Remoting.Suave | |
Fable.Remoting.Giraffe | |
Fable.Remoting.AspNetCore | |
Fable.Remoting.DotnetClient | |
Fable.Remoting.AzureFunctions.Worker | |
Fable.Remoting.AwsLambda |
Erstellen Sie eine neue F#-Konsolen-App:
dotnet new console -lang F#
Definieren Sie die Typen, die Sie zwischen Client und Server teilen möchten:
// SharedTypes.fsmodule SharedTypestype Student = {Name: stringAge: int}// Gemeinsame Spezifikationen zwischen Server und Clienttype IStudentApi = {studentByName: string -> Async<Student option>allStudents: unit -> Async<list<Student>>}
Der Typ IStudentApi
ist sehr wichtig, dies ist die Spezifikation des Protokolls zwischen Ihrem Server und Client. Fable.Remoting
erwartet, dass dieser Typ nur über Funktionen verfügt, die im Endergebnis Async
zurückgeben:
Async<A>A -> Async<B>A -> B -> Async<C>// etc...
Versuchen Sie, solche Typen in separaten Dateien abzulegen, um später vom Client aus auf diese Dateien verweisen zu können
Stellen Sie dann eine Implementierung für IStudentApi
auf dem Server bereit:
öffne SharedTypeslet getStudents() = async {return [{ Name = "Mike"; Alter = 23; }{ Name = „John“; Alter = 22; }{ Name = „Diana“; Alter = 22; }] }let findStudentByName name = asynchron {let! Students = getStudents()let student = List.tryFind (fun student -> student.Name = name) Studentsreturn student }let studentApi : IStudentApi = {studentByName = findStudentByName allStudents = getStudents}
Nachdem wir nun die Implementierung studentApi
haben, können Sie sie als Webdienst aus verschiedenen Web-Frameworks bereitstellen. Wir beginnen mit Suave
Installieren Sie die Bibliothek von Nuget mit Paket:
paket add Fable.Remoting.Suave --project /path/to/Project.fsproj
Erstellen Sie ein WebPart aus dem Wert studentApi
open Suaveopen Fable.Remoting.Serveropen Fable.Remoting.Suavelet webApp : WebPart =Remoting.createApi()|> Remoting.fromValue studentApi|> Remoting.buildWebPart// Starten Sie den WebserverstartWebServer defaultConfig webApp
Ja, so einfach ist das. Sie können sich den webApp
Wert so vorstellen, als wäre er im Pseudocode wie folgt:
let webApp = wählen [ POST >=> Pfad „/IStudentApi/studentByName“ >=> (* Anforderungstext deserialisieren (aus JSON) *) >=> (* studentApi.getStudentByName mit der deserialisierten Eingabe aufrufen *) >=> (* Geben Sie dem Client die Ausgabe wieder serialisiert (an JSON) *) // andere Routen ]
Sie können die Diagnoseprotokollierung von Fable.Remoting.Server aktivieren (empfohlen), um zu sehen, wie die Bibliothek hinter den Kulissen ihre Magie entfaltet :)
let webApp =Remoting.createApi()|> Remoting.fromValue studentApi|> Remoting.withDiagnosticsLogger (printfn "%s")|> Remoting.buildWebPart
Installieren Sie das Paket von Nuget mit paket
paket add Fable.Remoting.AspNetCore --project /path/to/Project.fsproj
Jetzt können Sie Ihren Remote-Handler als AspNetCore-Middleware konfigurieren
let webApp =Remoting.createApi()|> Remoting.fromValue studentApilet configureApp (app : IApplicationBuilder) =// Remoting-Handler zur ASP.NET Core-Pipeline hinzufügenapp.UseRemoting webApp[<EntryPoint>]let main _ =WebHostBuilder().UseKestrel ().Configure(Action<IApplicationBuilder> configureApp).Build().Run()0
Sie können dem Suave-Teil bis zur Bibliotheksinstallation folgen, wo daraus Folgendes wird:
paket add Fable.Remoting.Giraffe --project /path/to/Project.fsproj
Anstelle eines WebParts erhalten Sie durch Öffnen des Fable.Remoting.Giraffe
-Namespace nun einen HttpHandler vom server
:
open Giraffeopen Fable.Remoting.Serveropen Fable.Remoting.Giraffelet webApp : HttpHandler =Remoting.createApi()|> Remoting.fromValue studentApi|> Remoting.buildHttpHandlerlet configureApp (app : IApplicationBuilder) =// Giraffe zur ASP.NET Core-Pipeline hinzufügenapp .UseGiraffe webApplet configureServices (Dienste: IServiceCollection) =// Giraffe dependenciesservices.AddGiraffe() hinzufügen |>ignore[<EntryPoint>]let main _ =WebHostBuilder().UseKestrel().Configure(Action<IApplicationBuilder> configureApp).ConfigureServices(configureServices).Build(). Run()0
Sie können dieselbe webApp
verwenden, die von der Giraffe-Bibliothek generiert wurde.
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}App ausführen
Um Azure Functions im isolierten Modus mit benutzerdefiniertem HttpTrigger als serverlosen Remoting-Server zu verwenden, installieren Sie einfach:
dotnet add package Fable.Remoting.AzureFunctions.Worker
oder mit Paket
paket add Fable.Remoting.AzureFunctions.Worker --project /path/to/Project.fsproj
Da Azure Functions nichts über HttpHandler weiß, müssen wir integrierte HttpRequestData
und HttpResponseData
Objekte verwenden. Glücklicherweise haben wir die Funktionen Remoting.buildRequestHandler
und HttpResponseData.fromRequestHandler
zur Rettung:
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, Route = "{*any}")>] req: HttpRequestData, ctx: FunctionContext) =Remoting.createApi()|> Remoting.withRouteBuilder FunctionsRouteBuilder.apiPrefix|> Remoting.fromValue myImplementation|> Remoting.buildRequestHandler|> HttpResponseData.fromRequestHandler req
Natürlich ist es nicht ideal, eine Implementierung pro Funktions-App zu haben, daher ist HttpResponseData.fromRequestHandlers
hier zur Rettung gekommen:
Typ 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 let handlerTwo =Remoting.createApi()|> Remoting.withRouteBuilder FunctionsRouteBuilder.apiPrefix|> Remoting.fromValue myImplementationTwo|> Remoting.buildRequestHandler [ handlerOne; handlerTwo ] |> HttpResponseData.fromRequestHandlers req
Installieren Sie Fable.Remoting.Client
von Nuget mit Paket:
paket add Fable.Remoting.Client --project /path/to/Project.fsproj
Verweisen Sie die freigegebenen Typen auf Ihr Clientprojekt
<Compile Include="path/to/SharedTypes.fs" />
Beginnen Sie mit der Nutzung der Bibliothek:
open Fable.Remoting.Clientopen SharedTypes// studentApi : IStudentApilet studentApi =Remoting.createApi()|> Remoting.buildProxy<IStudentApi>async { // Studenten: Student[] lassen! Students = studentApi.allStudents() for student in Students do// student : Studentprintfn „Student %s ist %d Jahre alt“ student.Name student.Age}|> Async.StartImmediate
Wenn Sie schließlich webpack-dev-server
verwenden, müssen Sie die Konfiguration wie folgt ändern:
devServer: { contentBase: discover('./public'), Port: 8080}
dazu:
devServer: { contentBase: discover('./public'), Port: 8080, Proxy: {'/*': { // Weisen Sie den Webpack-Dev-Server an, alle Anfragen vom Client zum Serverziel umzuleiten: „http://localhost:5000“,// unter der Annahme, dass der Backend-Server auf dem Port gehostet wird 5000 während der Entwicklung changeOrigin: true}}
Das ist es!
Sie können die Client-Funktionalität auch in Nicht-Fable-Projekten verwenden, z. B. einer Konsole, einem Desktop oder einer mobilen Anwendung.
Installieren Sie Fable.Remoting.DotnetClient
von Nuget mit Paket:
paket add Fable.Remoting.DotnetClient --project /path/to/Project.fsproj
Verweisen Sie auf die freigegebenen Typen in Ihrem Clientprojekt
<Compile Include="path/to/SharedTypes.fs" />
Beginnen Sie mit der Nutzung der Bibliothek:
öffne Fable.Remoting.DotnetClientopen SharedTypes// studentApi : IStudentApilet studentApi = Remoting.createApi „http://localhost:8085“ |> Remoting.buildProxy<IStudentApi>async { // Studenten: Student[] lassen! Students = studentApi.allStudents() for student in Students do// student : Studentprintfn „Student %s ist %d Jahre alt“ student.Name student.Age}|> Async.StartImmediate
Beachten Sie hier, dass Sie im Gegensatz zum Fable-Client die Basis-URL des Backends angeben müssen, da der Dotnet-Client separat vom Backend bereitgestellt wird.
Fügen Sie IStudentApi
eine weitere Datensatzfeldfunktion hinzu
Implementieren Sie diese Funktion
Starten Sie Server und Client neu
Erledigt! Sie können diese Funktion jetzt auch vom Client aus nutzen.
Lesen Sie den folgenden Artikel, wenn Sie daran interessiert sind, wie diese Bibliothek implementiert wird (etwas veraltet, gibt Ihnen aber einen Überblick über den Mechanismus): Statisch typisierte Client-Server-Kommunikation mit F#: Proof of Concept