Skip to content

Editor Setup

Every application built with Dovetail gets one JSON Schema for its configuration, merged from the [DovetailConfiguration] sections of every library it references. This guide points your editor at it, so appsettings.json, YAML and TOML files complete section and key names, show the documentation you wrote on the options classes, and flag unknown keys and out-of-range values.

A build of an application or a test project writes the merged schema to:

<project>/obj/appsettings.schema.json

That path is stable, so editor settings can point at it and be checked in. Build the project once before expecting completion: the file doesn’t exist before then.

  • A project that targets several frameworks writes that file from its first target framework. The others write $(IntermediateOutputPath)appsettings.schema.json, for example obj/Debug/net10.0/appsettings.schema.json.
  • The build also copies the file next to the application’s appsettings.json in the output directory and publishes it as appsettings.schema.json.
  • Applications that embed their configuration (MAUI and Uno, where DovetailEmbedConfiguration is true) keep the file in obj/ only. It is not copied to the output directory and not published, so editors still use the obj/ path.
  • A library (any project that isn’t a host) doesn’t write appsettings.schema.json. It writes $(IntermediateOutputPath)ConfigurationSchema.json and packs it at the package root.

If a project that isn’t detected as an application should merge a schema, or an application shouldn’t, set DovetailConfigurationSchemaHost to true or false. The other properties (DovetailConfigurationSchema, DovetailConfigurationSchemaStrict, DovetailConfigurationSchemaIncludeSchemaStore, DovetailEmbedConfiguration and DovetailConfigurationSchemaRequired) are described under the schema.

VS Code needs one mapping per file type: its JSON support reads json.schemas, and the YAML and Even Better TOML extensions read yaml.schemas and evenBetterToml.schema.associations. Put them in .vscode/settings.json, which allows comments:

{
"json.schemas": [
{
"fileMatch": ["/MyApp/appsettings*.json"],
"url": "./MyApp/obj/appsettings.schema.json",
},
],
"yaml.schemas": {
"./MyApp/obj/appsettings.schema.json": ["MyApp/appsettings*.yaml", "MyApp/appsettings*.yml"],
},
"evenBetterToml.schema.associations": {
"^.+/MyApp/appsettings.*\\.toml$": "./MyApp/obj/appsettings.schema.json",
},
}

The SdkSample checks in a working file for a solution with two applications.

Notes on the three extensions:

  • JSON reports unknown keys, completes the canonical names and shows descriptions on hover.
  • YAML (the Red Hat extension) does the same. Its fileMatch globs don’t match files under a dot-folder.
  • TOML needs the explicit association above: Even Better TOML can’t use the SchemaStore catalog, and it reports an unknown key on the table header rather than on the key. It ignores deprecated.
  • Keys are matched ignoring case, as the configuration system does: message is accepted, Mesage is flagged.

Set DovetailScaffoldEditorSettings to true in the application’s project file:

<PropertyGroup>
<DovetailScaffoldEditorSettings>true</DovetailScaffoldEditorSettings>
</PropertyGroup>

On build, Dovetail creates or updates only its own entries in .vscode/settings.json (json.schemas, yaml.schemas and evenBetterToml.schema.associations), pointing at the application’s obj/appsettings.schema.json. Comments, trailing commas, indentation, line endings and a byte-order mark are preserved, other settings and other entries in those three maps are untouched, and re-running changes nothing (the file isn’t even rewritten, so its timestamp stays). Dovetail never adds $schema keys or # yaml-language-server modelines to your configuration files.

Each application gets entries of its own, keyed by its schema path, so several applications in one repository can share one settings file. Dovetail recognises its entries by that path: an entry that already maps the application’s schema only gets whatever is missing (a file pattern, a glob), and anything you added to it stays. If you move a project, delete its old entries by hand.

The file goes in the folder that holds .vscode: the nearest ancestor of the project that contains a .git entry, or the project folder when there is none. Set DovetailEditorSettingsDirectory to use another folder (a relative path is relative to the project). The folder must contain the project, because VS Code resolves the schema path relative to the folder it has open. The file and the .vscode folder are created only when they don’t exist.

Only applications write the file, once per build (from the first target framework of a project that targets several), never in design-time builds, and not when DovetailConfigurationSchema is false.

If the file can’t be updated safely, because it isn’t valid JSON with comments, isn’t UTF-8, has an unexpected shape (say json.schemas isn’t an array) or is read-only, Dovetail leaves it as it is and the build reports a warning with the entries to paste. Fix the file, or set DovetailScaffoldEditorSettings to false, to get rid of the warning.

PropertyDefaultEffect
DovetailScaffoldEditorSettingsofftrue writes the mappings on build.
DovetailEditorSettingsDirectorygit rootThe folder that holds .vscode.

It is off by default because it edits a file that is probably checked in.

Visual Studio’s JSON editor reads schemas from JsonSchemaSegment items, with no per-file settings. Dovetail adds, with the file pattern appsettings\..*json (the one Aspire’s packages use):

  • a segment for the application’s (or test project’s) merged obj/appsettings.schema.json;
  • a segment for each referenced package that ships a ConfigurationSchema.json at its root, which is where libraries built with Dovetail pack theirs. A package that declares its own segment for the file (Aspire’s client packages do) is skipped, so completions aren’t doubled. Referenced projects need no segment: their sections are in the merged file.

Nothing needs configuring. Dovetail finds the packages from the build’s resolved references, in the builds Visual Studio runs when it loads the project too, not only in full builds. DovetailConfigurationSchemaVisualStudio=false turns the segments off and keeps the schema files; DovetailConfigurationSchema=false turns off both. If completion doesn’t appear after the first build, close and reopen the file.

The repository’s tests check the items MSBuild produces, including in a design-time build. Whether Visual Studio itself picks up items a target adds, and how it merges two segments that describe the same section, has not been verified in Visual Studio.

Rider uses its own JSON Schema mappings rather than the VS Code settings or the segments:

  1. Open Settings | Languages & Frameworks | Schemas and DTDs | JSON Schema Mappings.
  2. Add a mapping, choose obj/appsettings.schema.json as the schema file, set the version to Draft 7, and add the application’s appsettings*.json files as the mapped files.
  3. Rider applies JSON Schema mappings to YAML files too. Add the appsettings*.yaml files the same way.

Any editor with a language server for the format can use the schema file:

  • yaml-language-server reads a yaml.schemas setting like VS Code’s, or a # yaml-language-server: $schema=... modeline in the file.
  • Taplo takes the schema from .taplo.toml or, in VS Code, evenBetterToml.schema.associations.

An application can also serve its schema, for tools that read it over HTTP. This needs an ASP.NET Core application that references Joinery.Dovetail.Hosting, and is off by default:

<PropertyGroup>
<DovetailConfigurationSchemaEndpoint>true</DovetailConfigurationSchemaEndpoint>
<!-- Optional: serve it only in these environments -->
<DovetailConfigurationSchemaEndpointEnvironments>Development;Staging</DovetailConfigurationSchemaEndpointEnvironments>
</PropertyGroup>
PropertyDefaultEffect
DovetailConfigurationSchemaEndpointofftrue generates a joint that maps the endpoint. Only projects with OutputType Exe or WinExe that reference ASP.NET Core and Joinery.Dovetail.Hosting get it.
DovetailConfigurationSchemaEndpointEnvironmentsevery environmentA semicolon-separated list of environment names (Development;Staging, compared ignoring case). The endpoint is mapped only when the host environment is one of them.
DovetailConfigurationSchemaEndpointSourceFallbacktrue except belowWhether the project’s obj/ directory is compiled into the application as a place to look for the schema. By default it is, except in publish builds and ContinuousIntegrationBuild builds, so a build machine path isn’t shipped. false never embeds it.

The route is GET /.well-known/dovetail/configuration-schema:

  • It looks for appsettings.schema.json in the application’s output directory first, then in the embedded obj/ fallback directory, which is how it works when run from source with no copy beside the assembly. A published application serves the copy published with it.
  • The response is application/schema+json with a strong ETag and Cache-Control: no-cache. A request with a matching If-None-Match gets 304 Not Modified. The file is re-read when it changes on disk.
  • It answers 404 when neither location has the file.

Point a mapping at the URL, for example "url": "http://localhost:5000/.well-known/dovetail/configuration-schema" in json.schemas.

To map it yourself, call the extension on the WebApplication (or any IEndpointRouteBuilder) instead of setting the property. Joinery.Dovetail.Hosting emits it into every ASP.NET Core project that references it, as an internal type. Both arguments are optional, and the method returns null when environments excludes the current environment:

using Joinery.Dovetail.Hosting;
app.MapDovetailConfigurationSchema(fallbackDirectory: null, environments: "Development")
?.RequireAuthorization();
  • No completion at all. Build the project: the file is written after compilation. Then check the path in the mapping is relative to the folder VS Code opened, and that the file name matches appsettings*.
  • A key is flagged as unknown. Check its spelling against the options class. Keys can’t be added to a section that [DovetailConfiguration] declares; set DovetailConfigurationSchemaStrict to false to stop the check.
  • A section is missing from completion. The application’s schema only covers libraries it references. Check the library has [DovetailConfiguration] on its options type and that the application builds against it.
  • Quoted numbers are flagged. "Timeout": "30" is an incorrect type to the editor even though the binder accepts it. Write the number without quotes.