Frank.OpenApi 7.3.3

dotnet add package Frank.OpenApi --version 7.3.3
                    
NuGet\Install-Package Frank.OpenApi -Version 7.3.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Frank.OpenApi" Version="7.3.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Frank.OpenApi" Version="7.3.3" />
                    
Directory.Packages.props
<PackageReference Include="Frank.OpenApi" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Frank.OpenApi --version 7.3.3
                    
#r "nuget: Frank.OpenApi, 7.3.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Frank.OpenApi@7.3.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Frank.OpenApi&version=7.3.3
                    
Install as a Cake Addin
#tool nuget:?package=Frank.OpenApi&version=7.3.3
                    
Install as a Cake Tool

Frank.OpenApi

NuGet Version

Native OpenAPI document generation for Frank applications, with first-class support for F# types and declarative metadata using computation expressions.

Installation

dotnet add package Frank.OpenApi

HandlerBuilder Computation Expression

Define handlers with embedded OpenAPI metadata using the handler computation expression:

open Frank.Builder
open Frank.OpenApi

type Product = { Name: string; Price: decimal }
type CreateProductRequest = { Name: string; Price: decimal }

let createProductHandler =
    handler {
        name "createProduct"
        summary "Create a new product"
        description "Creates a new product in the catalog"
        tags [ "Products"; "Admin" ]
        produces typeof<Product> 201
        accepts typeof<CreateProductRequest>
        handle (fun (ctx: HttpContext) -> task {
            let! request = ctx.Request.ReadFromJsonAsync<CreateProductRequest>()
            let product = { Name = request.Name; Price = request.Price }
            ctx.Response.StatusCode <- 201
            do! ctx.Response.WriteAsJsonAsync(product)
        })
    }

let productsResource =
    resource "/products" {
        name "Products"
        post createProductHandler
    }

HandlerBuilder Operations

Operation Description
name "operationId" Sets the OpenAPI operationId
summary "text" Brief summary of the operation
description "text" Detailed description
tags [ "Tag1"; "Tag2" ] Categorize endpoints
produces typeof<T> statusCode Define response type and status code
produces typeof<T> statusCode ["content/type"] Response with content negotiation
producesEmpty statusCode Empty responses (204, 404, etc.)
accepts typeof<T> Define request body type
accepts typeof<T> ["content/type"] Request with content negotiation
handle (fun ctx -> ...) Handler function (supports Task, Task<'a>, Async<unit>, Async<'a>)

F# Type Schema Generation

Frank.OpenApi automatically generates JSON schemas for F# types:

// F# records with required and optional fields
type User = {
    Id: Guid
    Name: string
    Email: string option  // Becomes nullable in schema
}

// Discriminated unions (anyOf/oneOf)
type Response =
    | Success of data: string
    | Error of code: int * message: string

// Collections
type Products = {
    Items: Product list
    Tags: Set<string>
    Metadata: Map<string, string>
}

WebHostBuilder Integration

Enable OpenAPI document generation in your application:

[<EntryPoint>]
let main args =
    webHost args {
        useDefaults
        useOpenApi  // Adds /.well-known/openapi.json endpoint

        resource productsResource
    }
    0

The OpenAPI document will be available at /.well-known/openapi.json.

Content Negotiation

Define multiple content types for requests and responses:

handler {
    name "getProduct"
    produces typeof<Product> 200 [ "application/json"; "application/xml" ]
    accepts typeof<ProductQuery> [ "application/json"; "application/xml" ]
    handle (fun ctx -> task { (* ... *) })
}

These operations only describe the content types in the generated OpenAPI document — they don't dispatch on Accept at runtime. For that, use the negotiate { } computation expression, which lives in Frank core (Frank.Builder, no Frank.OpenApi needed):

resource "/products/{id}" {
    get (negotiate {
        accepts "application/json" (fun ctx -> task {
            do! ctx.Response.WriteAsJsonAsync(product)
        })
        accepts "text/html" (fun ctx -> task {
            do! ctx.Response.WriteAsync($"<h1>{product.Name}</h1>")
        })
    })
}

negotiate { } performs real per-media-type dispatch: each accepts registers an independent representation, and the one matching the request's Accept header (by RFC 9110 quality and specificity rules) is the only one whose handler runs. Nothing matching means 406 Not Acceptable, and every response carries Vary: Accept.

Name collision: Frank.Builder.negotiate (this CE) and the older Frank.ContentNegotiation.negotiate function (statusCode -> body -> ctx -> Task, which delegates the whole response to MVC's formatter registry) share the identifier negotiate. With both modules opened, F#'s ordinary shadowing rules apply and the last open wins. Qualify one of them, or use the non-colliding ctx.Negotiate(200, body) extension member.

Backward Compatibility

Frank.OpenApi is fully backward compatible with existing Frank applications. You can:

  • Mix HandlerDefinition and plain RequestDelegate handlers in the same resource
  • Add OpenAPI metadata incrementally without changing existing code
  • Use the library only where you need API documentation

Requires Frank. See sample/Frank.OpenApi.Sample for a runnable Product Catalog API.

See the project repository for the complete guide and sample applications.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
7.3.3 89 8/11/2026
7.3.2 87 8/10/2026
7.2.1 119 6/22/2026
7.2.0 148 2/10/2026

### New in 7.3.3 (Released 2026-08-10)
**Frank.Rdf - Async IBufferWriter Streaming**
- **New: `Doc.writeJsonLdAsync doc bufferWriter`** — async overload that writes JSON-LD expanded-form directly to an `IBufferWriter<byte>` (e.g. `HttpResponse.BodyWriter`/`PipeWriter`), without intermediate string allocation or copying. Encodes UTF8 directly to the buffer for maximum efficiency in response streaming. Completes after serialization and flushing to the buffer.
- **Recommended for response streaming:** `writeJsonLdAsync` is the preferred method when serving JSON-LD from a Frank handler over HTTP. Streaming directly to `PipeWriter` avoids `AllowSynchronousIO` requirements and eliminates intermediate buffering layers that `StreamWriter` would introduce.
- **Three serialization options now available:** Use `writeJsonLdAsync` for HTTP responses (most efficient), `writeJsonLd` for flexibility with any `TextWriter`, and `toJsonLd` for testing/debugging.
- **Sample updated:** `sample/Frank.Rdf.Sample` demonstrates `Doc.writeJsonLdAsync` streaming the `application/ld+json` representation directly to the response body via `negotiate { }` content negotiation.
- **Test coverage:** comprehensive test cases for `writeJsonLdAsync` including single and multi-subject documents, language-tagged strings, and round-trip parsing.