Frank.JsonHome
7.3.3
dotnet add package Frank.JsonHome --version 7.3.3
NuGet\Install-Package Frank.JsonHome -Version 7.3.3
<PackageReference Include="Frank.JsonHome" Version="7.3.3" />
<PackageVersion Include="Frank.JsonHome" Version="7.3.3" />
<PackageReference Include="Frank.JsonHome" />
paket add Frank.JsonHome --version 7.3.3
#r "nuget: Frank.JsonHome, 7.3.3"
#:package Frank.JsonHome@7.3.3
#addin nuget:?package=Frank.JsonHome&version=7.3.3
#tool nuget:?package=Frank.JsonHome&version=7.3.3
Frank.JsonHome
Serves a JSON Home document describing a Frank application's entry-point resources — a machine-readable directory a client can discover once and use to find everything else, instead of hardcoding URLs. It has no dependency on Frank.Auth or Frank.OpenApi, and adds no NuGet dependency of its own.
Installation
dotnet add package Frank.JsonHome
Declaring Discoverable Resources
Resources opt in with rel; anything without one is omitted, so the document stays a curated entry point rather than a sitemap of everything the app happens to serve:
open Frank.Builder
open Frank.JsonHome
let products =
resource "/products" {
rel "tag:example.com,2026:products"
docs "https://example.com/docs/products"
get listProducts
post createProduct
}
let productById =
resource "/products/{id}" {
rel "tag:example.com,2026:product"
hrefVar "id" "https://example.com/param/product-id"
get getProduct
put updateProduct
}
// Signals a resource is on its way out, or already gone
let legacyExport =
resource "/legacy/export" {
rel "tag:example.com,2026:legacy-export"
deprecated
get legacyExportHandler
}
Application Wiring
[<EntryPoint>]
let main args =
webHost args {
useDefaults
useJsonHome // Serves /.well-known/home.json, advertises it via a Link header on every response
resource products
resource productById
resource legacyExport
}
0
Configure the path, relation type, and api metadata with the overload that takes a function:
useJsonHome (fun options ->
{ options with
Path = "/discovery.json"
Rel = "discovery"
Title = Some "Example API"
Links = [ "author", "mailto:api-admin@example.com" ] })
Discovery Operations
| Operation | Description |
|---|---|
rel "..." |
Link relation type keying this resource in the document — required for the resource to appear at all |
hrefVar "name" "uri" |
Absolute URI identifying a route variable's semantics, for templated resources |
docs "uri" |
Documentation link for this resource's relation type |
deprecated |
Marks status: "deprecated" |
gone |
Marks status: "gone" |
acceptRanges [ "bytes" ] |
HTTP range-specifiers this resource accepts |
acceptPrefer [ "return=minimal" ] |
RFC 7240 preferences this resource supports |
preconditionRequired [ Precondition.ETag ] |
Preconditions required on state-changing requests |
authScheme "Basic" [ "private" ] |
An HTTP authentication scheme this resource accepts, with its protection spaces |
Authorization Filtering
If Frank.Auth is in use, guard a resource the same way you would anywhere else — Frank.JsonHome reads the stock IAuthorizeData/AuthorizationPolicy metadata Frank.Auth attaches, with no reference between the two packages:
let adminReports =
resource "/admin/reports" {
rel "tag:example.com,2026:admin-reports"
requireRole "admin" // Frank.Auth
get getAdminReports
}
An anonymous request's /.well-known/home.json omits tag:example.com,2026:admin-reports entirely; an authenticated admin's includes it. This also works with a plain ASP.NET Core [<Authorize>]-equivalent, without Frank.Auth at all.
Filtering is per HTTP method, not per whole resource — a handler-level requirement only hides the method it's on, not the resource:
let widgets =
resource "/widgets" {
rel "tag:example.com,2026:widgets"
get listWidgets // public
delete (handler { requireRole "admin"; handle deleteWidget })
}
An anonymous request's hints.allow for tag:example.com,2026:widgets shows ["GET"]; an authenticated admin's shows ["GET", "DELETE"]. A resource left with no visible methods for the current principal is omitted entirely, same as before. Whenever any resource is guarded, every response carries Cache-Control: private, no-cache and Vary: Authorization, so a shared cache can never serve one principal's document to another.
See sample/Frank.JsonHome.Sample for a runnable demonstration, including curl output for both cases.
Related Packages
Requires Frank. Optionally combines with Frank.Auth for authorization-filtered discovery.
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
- Frank (>= 7.3.3)
- FSharp.Core (>= 10.1.302)
-
net8.0
- Frank (>= 7.3.3)
- FSharp.Core (>= 10.1.302)
-
net9.0
- Frank (>= 7.3.3)
- FSharp.Core (>= 10.1.302)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
### 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.