Frank 7.3.3
dotnet add package Frank --version 7.3.3
NuGet\Install-Package Frank -Version 7.3.3
<PackageReference Include="Frank" Version="7.3.3" />
<PackageVersion Include="Frank" Version="7.3.3" />
<PackageReference Include="Frank" />
paket add Frank --version 7.3.3
#r "nuget: Frank, 7.3.3"
#:package Frank@7.3.3
#addin nuget:?package=Frank&version=7.3.3
#tool nuget:?package=Frank&version=7.3.3
Frank
F# computation expressions, or builders, for configuring the Microsoft.AspNetCore.Hosting.IWebHostBuilder and defining routes for HTTP resources using Microsoft.AspNetCore.Routing.
This project was inspired by @filipw's Building Microservices with ASP.NET Core (without MVC).
Installation
dotnet add package Frank
Features
WebHostBuilder- computation expression for configuringWebHostResourceBuilder- computation expression for configuring resources (routing)- No pre-defined view engine - use your preferred view engine implementation, e.g. Falco.Markup, Oxpecker.ViewEngine, or Hox
- Easy extensibility - just extend the
Builderwith your own methods!
Basic Example
module Program
open System.IO
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Routing
open Microsoft.AspNetCore.Routing.Internal
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Logging
open Frank
open Frank.Builder
let home =
resource "/" {
name "Home"
get (fun (ctx:HttpContext) ->
ctx.Response.WriteAsync("Welcome!"))
}
[<EntryPoint>]
let main args =
webHost args {
useDefaults
logging (fun options-> options.AddConsole().AddDebug())
plugWhen isDevelopment DeveloperExceptionPageExtensions.UseDeveloperExceptionPage
plugWhenNot isDevelopment HstsBuilderExtensions.UseHsts
plugBeforeRouting HttpsPolicyBuilderExtensions.UseHttpsRedirection
plugBeforeRouting StaticFileExtensions.UseStaticFiles
resource home
}
0
Middleware Pipeline
Frank provides two middleware operations with different positions in the ASP.NET Core pipeline:
Request → plugBeforeRouting → UseRouting → plug → Endpoints → Response
plugBeforeRouting
Use for middleware that must run before routing decisions are made:
- HttpsRedirection - redirect before routing
- StaticFiles - serve static files without routing overhead
- ResponseCompression - compress all responses
- ResponseCaching - cache before routing
webHost args {
plugBeforeRouting HttpsPolicyBuilderExtensions.UseHttpsRedirection
plugBeforeRouting StaticFileExtensions.UseStaticFiles
resource myResource
}
plug
Use for middleware that needs routing information (e.g., the matched endpoint):
- Authentication - may need endpoint metadata
- Authorization - requires endpoint to check policies
- CORS - may use endpoint-specific policies
webHost args {
plug AuthenticationBuilderExtensions.UseAuthentication
plug AuthorizationAppBuilderExtensions.UseAuthorization
resource protectedResource
}
Conditional Middleware
Both plugWhen and plugWhenNot run in the plug position (after routing):
webHost args {
plugWhen isDevelopment DeveloperExceptionPageExtensions.UseDeveloperExceptionPage
plugWhenNot isDevelopment HstsBuilderExtensions.UseHsts
resource myResource
}
Conditional Before-Routing Middleware
Both plugBeforeRoutingWhen and plugBeforeRoutingWhenNot run in the plugBeforeRouting position (before routing):
let isDevelopment (app: IApplicationBuilder) =
app.ApplicationServices
.GetService<IWebHostEnvironment>()
.IsDevelopment()
webHost args {
// Only redirect to HTTPS in production
plugBeforeRoutingWhenNot isDevelopment HttpsPolicyBuilderExtensions.UseHttpsRedirection
// Only serve static files locally in development (CDN in production)
plugBeforeRoutingWhen isDevelopment StaticFileExtensions.UseStaticFiles
resource myResource
}
Content Negotiation
The negotiate { } computation expression 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.
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>")
})
})
}
Related Packages
Frank has several companion packages that build on this core: Frank.Auth (authorization), Frank.OpenApi (OpenAPI generation), Frank.JsonHome (hypermedia discovery), Frank.Datastar (reactive SSE), Frank.Rdf (linked data), and Frank.Analyzers (compile-time checks).
See the project repository for the complete guide and sample applications.
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
-
net10.0
- FSharp.Core (>= 10.1.302)
-
net8.0
- FSharp.Core (>= 10.1.302)
-
net9.0
- FSharp.Core (>= 10.1.302)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on Frank:
| Package | Downloads |
|---|---|
|
Frank.Datastar
Datastar SSE integration for Frank web framework with F# computation expression support. Native SSE implementation with no external dependencies, supports .NET 8.0/9.0/10.0. |
|
|
Frank.Auth
Resource- and handler-level authorization extensions for Frank web framework |
|
|
Frank.OpenApi
OpenAPI document generation extensions for Frank web framework |
|
|
Frank.Validation
Hand-authored SHACL Core validation for Frank resources, built on Frank.Rdf |
|
|
Frank.Alps
Hand-authored ALPS profile documents for Frank resources |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 7.3.3 | 185 | 8/11/2026 |
| 7.3.2 | 166 | 8/10/2026 |
| 7.2.1 | 194 | 6/22/2026 |
| 7.2.0 | 243 | 2/10/2026 |
| 7.1.0 | 158 | 2/8/2026 |
| 7.0.0-build.0 | 83 | 2/6/2026 |
| 6.5.0 | 215 | 2/5/2026 |
| 6.4.1 | 155 | 2/4/2026 |
| 6.4.1-build.0 | 83 | 2/4/2026 |
| 6.4.0 | 140 | 2/2/2026 |
| 6.4.0-build.0 | 84 | 2/2/2026 |
| 6.3.0 | 544 | 3/15/2025 |
| 6.2.0 | 5,527 | 11/18/2020 |
| 6.1.0 | 1,917 | 6/11/2020 |
| 6.0.0 | 947 | 6/2/2020 |
| 5.0.5 | 1,115 | 1/7/2019 |
### 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.