Fable.Remoting for .NET Clients
Although Fable.Remoting is initially implemented for communication between a .NET backend and a Fable frontend, the RPC story wouldn't be complete with a strongly typed dotnet client that can talk to the same backend using the protocol definition, that why we have built one.
In fact, you can use the dotnet client with a dotnet server without a Fable project involved, think client-server interactions purely in F#. This has proven to make integration testing extremely simple through this client.
Installation
Install the library from Nuget:
paket add Fable.Remoting.DotnetClient --project /path/to/App.fsproj
# or
dotnet add package Fable.Remoting.DotnetClient
Using the library
The new way
As you would expect, you need to reference the shared types and protocols to your client project:
<Compile Include="..\Shared\SharedTypes.fs" />
With the new Remoting
API, the code is almost completely similar to the Fable client API, with only minor differences in proxy setup:
open Fable.Remoting.DotnetClient
open SharedTypes
let server =
Remoting.createApi "http://backend.api.io/v1" // Also note the base URI is no longer optional.
|> Remoting.buildProxy<IServer>
async {
let! length = server.getLength "hello"
return length
}
To make the proxy generation logic work, your protocol record must be immutable, i.e. not marked as [<CLIMutable>]
.
The old way using proxy quotations (still supported)
As you would expect, you need to reference the shared types and protocols to your client project:
<Compile Include="..\Shared\SharedTypes.fs" />
Now the code is similar the Fable client API with a couple of differences:
open Fable.Remoting.DotnetClient
open SharedTypes
// specifies how the routes should be generated
let routes = sprintf "http://backend.api.io/v1/%s/%s"
// proxy: Proxy<IServer>
let proxy = Proxy.create<IServer> routes
async {
// length : int
let! length = proxy.call <@ fun server -> server.getLength "hello" @>
// 5
return length
}
The major difference is the use of quotations, which simplified and implementation process greatly and keeps the solution entirely type-safe without fighting with the run-time with boxing/unboxing hacks to get types right.
Proxy.callSafely
Alongside the proxy.call
approach, there is also proxy.callSafely
which is the same but will return Async<Result<'t, Exception>>
to catch any exception that occurs along the request:
async {
let! result = proxy.callSafely <@ fun server -> server.throwError() @>
match result with
| Ok value -> (* will not match *)
| Error ex ->
| match ex with
| :? Http.ProxyRequestException as requestException ->
let statusCode = requestException.StatusCode
let response = requestException.Response
let responseText = requestException.ResponseText
(* do stuff with the exception information *)
| otherException ->
(* Usually network errors happen here *)
}