Skip to content

Customization

OpenAPI and Swagger type providers generate one or several API client types (depending on the value of IgnoreControllerPrefix). Each provided API client is a subclass of ProvidedApiClientBase, so you can control and customize HTTP calls.

fsharp
type ProvidedApiClientBase(httpClient: HttpClient) =
    member val HttpClient = httpClient with get, set

    abstract member Serialize: obj -> string
    abstract member Deserialize: string * Type -> obj

The snippet shows only the most important parts of ProvidedApiClientBase. The full source includes default Serialize and Deserialize implementations built on System.Text.Json.

Key features:

  1. You can provide your own HttpClient instance during API client construction and fully control request execution (if you do not provide one, the type provider creates a default instance).
  2. Serialize and Deserialize are abstract methods. If the default JsonSerializerOptions do not fit your needs, override them and configure System.Text.Json as required.

Request interception

Since you control HttpClient, you can use outgoing request middleware, implement your own DelegatingHandler, and intercept all HTTP requests generated by the provided API client.

fsharp
open System
open System.Net.Http
open SwaggerProvider

let [<Literal>] Schema = "https://petstore.swagger.io/v2/swagger.json"
type PetStore = OpenApiClientProvider<Schema, PreferAsync=true>

type LoggingHandler(messageHandler) =
    inherit DelegatingHandler(messageHandler)
    override __.SendAsync(request, cancellationToken) =
        // Put a breakpoint here if you want to debug HTTP calls
        printfn "[%A]: %A" request.Method request.RequestUri
        base.SendAsync(request, cancellationToken)

[<EntryPoint>]
let main argv =
    let baseAddress = Uri("https://petstore.swagger.io/v2/")
    let handler1 = new HttpClientHandler (UseCookies = false)
    let handler2 = new LoggingHandler(handler1)
    use httpClient = new HttpClient(handler2, true, BaseAddress=baseAddress)
    let client = PetStore.Client(httpClient)
    async {
        let pet = PetStore.Pet(Id = Some(24L), Name = "Shani")
        do! client.AddPet(pet)
        let! myPet = client.GetPetById(24L)
        printfn "Waw, my name is %A" myPet.Name
    }
    |> Async.RunSynchronously
    0

Authentication

Authentication is a special case of request interception. Your custom DelegatingHandler is fully responsible for authentication data management (attach an authorization header, add authentication cookies, invalidate tokens, etc.).

fsharp
type AuthHandler(messageHandler) =
    inherit DelegatingHandler(messageHandler)
    override __.SendAsync(request, cancellationToken) =
        // Invalidate your token if it expired
        request.Headers.Authorization <-
            Headers.AuthenticationHeaderValue("Bearer", "Your OAuth token");
        base.SendAsync(request, cancellationToken)

If your token does not require invalidation, you can use HttpClient.DefaultRequestHeaders to add the Authorization header to all requests.

fsharp
let baseAddress = Uri("https://petstore.swagger.io/v2/")
let handler = new HttpClientHandler (UseCookies = false)
use httpClient = new HttpClient(handler, true, BaseAddress=baseAddress)
httpClient.DefaultRequestHeaders.Authorization <-
    Headers.AuthenticationHeaderValue("Bearer", "Your OAuth token")
let client = PetStore.Client(httpClient)

Serialization

Serialization is also flexible. Define your own API client type as a subclass of the generated API client and override Serialize and Deserialize.

INFO

The serializer is configurable but not replaceable! The Type provider emits types with JsonPropertyNameAttribute on properties for seamless serialization.

fsharp
open System
open SwaggerProvider
open System.Text.Json
open System.Text.Json.Serialization

let [<Literal>] Schema = "https://petstore.swagger.io/v2/swagger.json"
type PetStore = OpenApiClientProvider<Schema, PreferAsync=true>

let jsonSerializerSettings =
    // nuget: System.Text.Json
    let settings = JsonSerializerOptions(
                    //PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
                    // ...
                    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    ) 
    // nuget: FSharp.SystemTextJson
    JsonFSharpOptions.Default().AddToJsonSerializerOptions settings
    settings

type MyApiClient() =
    // inherit from provided API client
    inherit PetStore.Client()

    // Override default implementation of Serialize & Deserialize
    override __.Serialize(value:obj): string =
        JsonSerializer.Serialize(value, jsonSerializerSettings)
    override __.Deserialize(value, retTy:Type): obj =
        JsonSerializer.Deserialize(value, retTy, jsonSerializerSettings)


[<EntryPoint>]
let main argv =
    // Instantiate your API client
    let client = MyApiClient()
    async {
        let pet = PetStore.Pet(Id = Some(24L), Name = "Shani")
        do! client.AddPet(pet)
        let! myPet = client.GetPetById(24L)
        printfn "Waw, my name is %A" myPet.Name
    }
    |> Async.RunSynchronously
    0