Open API: Add fluent builder for registering custom backoffice OpenAPI documents#22774
Conversation
Bundles AddOpenApi, the [MapToApi]-aware ShouldInclude predicate, the Umbraco schema reference ID convention, and AddOpenApiDocumentToUi behind a single IUmbracoBuilder.AddBackOfficeOpenApiDocument call. Authors pass documentName, an optional title (used both as Info.Title and the UI dropdown label), and an optional configure callback that runs last so it can override anything the helper sets. An optional jsonOptionsName is forwarded to ReplaceOpenApiSchemaService for documents that need schema-time JSON serialization aligned to a named JsonOptions. Schema reference ID logic moves out of ConfigureUmbracoOpenApiOptionsBase into UmbracoSchemaIdGenerator.CreateSchemaReferenceId so both the new helper and the base class share one source of truth. The extension template's composer collapses to a single AddBackOfficeOpenApiDocument call, with document Info.Version, backoffice security, and the operation ID transformer staying in the configure callback.
Replace the parameter-list AddBackOfficeOpenApiDocument helper with a callback-based form that yields a BackOfficeOpenApiDocumentBuilder. The builder owns its state and applies it to the IUmbracoBuilder once the user callback returns, so authors don't need to remember a terminal Build call. Extension methods can layer on (e.g. WithBackOfficeAuthentication in Umbraco.Cms.Api.Management) without the core helper carrying every opinion. Defaults stay sensible: filtering by [MapToApi(documentName)], the Umbraco schema reference IDs, and the tag/sort transformers that v17's global Swashbuckle pipeline applied. UI dropdown registration is opt-out via ExcludeFromUi rather than opt-in. JSON options for schema generation are an opt-in via WithHttpJsonOptions (instance or factory), described purely in terms of the schema effect. Move UmbracoSchemaIdGenerator's CreateSchemaReferenceId wrapper out of ConfigureUmbracoOpenApiOptionsBase so both the base config class and the new builder share one source of truth, and update the ContentTypeSchemaTransformer / unit test callsites accordingly. Refresh the extension template to use the new shape.
The Http qualifier was naming the .NET type rather than the intent. The parameter type carries the disambiguation; the method name is now intent-focused and the XML doc explains the use case (matching the serialization conventions of the API endpoints the document describes).
…-document-extension
Convenience overload that accepts the registered name and resolves the matching Microsoft.AspNetCore.Http.Json.JsonOptions via IOptionsMonitor. Documents on all three WithJsonOptions overloads now explicitly name the HTTP JsonOptions type so consumers know which framework type they are configuring.
…cument Replaces the AddUmbracoOpenApiDocument<ConfigureUmbracoManagementApiOpenApiOptions> call with the new fluent builder. The custom config class becomes dead code and is deleted; all per-document opinions (Info metadata, security requirements, transformers, JSON options) move into the configuration callback alongside the document registration. Behavior preserved: same ShouldInclude (now via [MapToApi]-only since all Management controllers carry the attribute through their base class), same schema reference IDs, same operation IDs via UmbracoOperationIdTransformer, same backoffice security requirements, same schema/operation transformers, same named JSON options for schema generation.
|
Claude finished @lauraneto's task in 5m 55s —— View job PR ReviewTarget: Introduces
Note Complexity advisory — This PR may benefit from splitting.
This is an observation, not a blocker. The full review follows below. Important
Suggestions
Approved with SuggestionsGood to go, but please carefully consider the importance of the suggestions. The missing tests for |
There was a problem hiding this comment.
Pull request overview
This PR introduces a fluent, backoffice-focused OpenAPI document registration helper (IUmbracoBuilder.AddBackOfficeOpenApiDocument(...)) that applies Umbraco defaults (endpoint filtering, schema ID naming, stable sorting/tagging, optional UI registration), and migrates the built-in Management API OpenAPI registration to use the new builder-based approach.
Changes:
- Added
AddBackOfficeOpenApiDocument+BackOfficeOpenApiDocumentBuilderfor fluent registration/configuration of custom backoffice OpenAPI documents. - Migrated Management API OpenAPI registration to the new builder (and removed
ConfigureUmbracoManagementApiOpenApiOptions). - Extracted/centralized schema reference ID generation into
UmbracoSchemaIdGenerator.CreateSchemaReferenceIdand updated call sites/tests accordingly.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoOpenApiOptionsBaseTests.cs | Updates tests to validate schema reference IDs via UmbracoSchemaIdGenerator.CreateSchemaReferenceId. |
| templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs | Updates the extension template to register its OpenAPI doc via the new builder and management auth helper. |
| src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeOpenApiDocumentBuilderExtensions.cs | Adds management-specific fluent builder extension for backoffice authentication requirements. |
| src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs | Migrates Management API OpenAPI document registration to AddBackOfficeOpenApiDocument with per-document configuration. |
| src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiOpenApiOptions.cs | Removes the old named-options-based Management API OpenAPI configuration class. |
| src/Umbraco.Cms.Api.Delivery/OpenApi/Transformers/ContentTypeSchemaTransformer.cs | Switches schema ID generation to UmbracoSchemaIdGenerator.CreateSchemaReferenceId. |
| src/Umbraco.Cms.Api.Common/OpenApi/UmbracoSchemaIdGenerator.cs | Adds CreateSchemaReferenceId(JsonTypeInfo) to apply Umbraco schema naming + framework fallback. |
| src/Umbraco.Cms.Api.Common/OpenApi/UmbracoBuilderOpenApiExtensions.cs | Adds the public IUmbracoBuilder.AddBackOfficeOpenApiDocument(...) entrypoint. |
| src/Umbraco.Cms.Api.Common/OpenApi/BackOfficeOpenApiDocumentBuilder.cs | Introduces the fluent builder implementing defaults, UI inclusion/exclusion, and optional schema JSON options alignment. |
| src/Umbraco.Cms.Api.Common/DependencyInjection/OpenApiServiceCollectionExtensions.cs | Adds a lazy-title overload for UI dropdown registration and routes the public overload through it. |
| src/Umbraco.Cms.Api.Common/DependencyInjection/OpenApiSchemaServiceExtensions.cs | Adds a factory-based overload for replacing the internal OpenAPI schema service’s JsonOptions source. |
| src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoOpenApiOptionsBase.cs | Uses UmbracoSchemaIdGenerator.CreateSchemaReferenceId and removes the duplicated schema-ID helper implementation. |
AndyButland
left a comment
There was a problem hiding this comment.
This looks good to me @lauraneto. I've played around with the different options for the AddBackOfficeOpenApiDocument builder methods and am seeing the expected results at /umbraco/openapi/index.html.
On the question of including UmbracoOperationIdTransformer, I think it makes sense to do so. The developer using AddBackOfficeOpenApiDocument is already opting into Umbraco's defaults, so you could argue it makes sense to align this too. If you do include it, would just suggest to make sure the XML docs for AddBackOfficeOpenApiDocument are detailed enough to explain what you are opting into by using it. E.g. include something like "Operation IDs are generated by UmbracoOperationIdTransformer by default; register your own IOpenApiOperationTransformer via ConfigureOpenApiOptions to override."
The inline point about unit tests for the builder class would be good to look at too.
Approving, but please have a look over the above and decide which way you want to go before merging in.
And of course we need to make sure the details of this are documented on https://docs.umbraco.com too (can be added to your existing PR).
Make UmbracoOperationIdTransformer part of the builder's defaults instead of the Management API adding it explicitly, and expand the XML docs on AddBackOfficeOpenApiDocument to spell out the defaults a caller opts into. Add tests covering the new builder and its defaults: - Unit tests for BackOfficeOpenApiDocumentBuilder defaults (CreateSchemaReferenceId, ShouldInclude, ConfigureOpenApiOptions composition, WithTitle/WithUiTitle UI dropdown handling, ExcludeFromUi). - Integration tests that register sample controllers, fetch the generated OpenAPI document and verify the defaults end-to-end: Info.Title from WithTitle, MapToApi filtering, Umbraco operation-id and schema-id conventions (including the version-suffix branch), tag-by-group-name and tag-first path sorting. - Integration tests for the three WithJsonOptions overloads (instance, factory, named) confirming the configured JsonOptions reach schema generation.
UmbracoOperationIdTransformer is now part of the AddBackOfficeOpenApiDocument defaults, so the template's custom action-name transformer would only overwrite the work the default just did. Drop it, and consolidate the documentation pointer to a single link.
…it in AddBackOfficeOpenApiDocument Filter only removes redundant JSON-equivalent MIME types (text/json, application/*+json, text/plain) when application/json is present. Non-JSON types like application/xml are preserved. Register the transformer as a default in AddBackOfficeOpenApiDocument so custom backoffice documents get the same treatment as Umbraco's own APIs.
…ceOpenApiDocument
AndyButland
left a comment
There was a problem hiding this comment.
Great work on adding the tests here @lauraneto. I found a few small things further to note, but once they are handled then I think this is all good to merge in.
- Drop RequireNonNullablePropertiesSchemaTransformer and MimeTypesTransformer from the Management API's ConfigureOpenApiOptions block — both are now defaults on the builder. - Expand MimeTypesTransformer XML docs to reflect its broader role (it now applies to every backoffice document, not just the Management API) and correct the response-side inline comment. - Move MimeTypesTransformerTests from the Delivery test folder/namespace to the Api.Common test folder/namespace, since the transformer is no longer Delivery-specific. - Rename BackOfficeOpenApiDocumentExtensionTests to UmbracoBuilderOpenApiExtensionsTests so the test fixture name matches the concrete class under test.
Summary
IUmbracoBuilder.AddBackOfficeOpenApiDocument(...)for registering a custom OpenAPI document with Umbraco's defaults.ConfigureUmbracoManagementApiOpenApiOptionsis removed; all per-document opinions (Info metadata, security requirements, transformers, JSON options) move into the configuration callback alongside the document registration.Defaults applied
When the method is called, the document is registered with the following out of the box (no callback configuration required):
AddOpenApi(documentName)to register the document withMicrosoft.AspNetCore.OpenApi.ShouldIncludepredicate that filters endpoints by[MapToApi(documentName)].CreateSchemaReferenceIdset toUmbracoSchemaIdGenerator.CreateSchemaReferenceId, applying Umbraco naming conventions to types under theUmbraco.Cmsnamespace and falling back to the framework default for everything else.TagActionsByGroupNameTransformerregistered as both an operation and document transformer to tag operations by API group name and clean up unused tags.SortTagsAndPathsTransformerregistered as a document transformer for stable, diffable output.ExcludeFromUi()to opt out).Note
Open to suggestions on other things we should include here. For example, I wasn't sure whether
UmbracoOperationIdTransformerwas applicable to extensions or just our own APIs? (of course even if we add it, extensions can always override it.Builder methods
BackOfficeOpenApiDocumentBuilder(inUmbraco.Cms.Api.Common.OpenApi):WithTitle(string title)— sets the document'sInfo.Title(also used as the default UI dropdown label).WithUiTitle(string uiTitle)— overrides only the UI dropdown label, leavingInfo.Titleuntouched.ExcludeFromUi()— suppresses registration in the UI document selector dropdown.ConfigureOpenApiOptions(Action<OpenApiOptions> configure)— adds a configuration callback forOpenApiOptions. Composes — multiple calls run in order after Umbraco's defaults.WithJsonOptions(string jsonOptionsName)— convenience for resolving a registered namedMicrosoft.AspNetCore.Http.Json.JsonOptionsviaIOptionsMonitor.WithJsonOptions(JsonOptions jsonOptions)— sets a caller-suppliedMicrosoft.AspNetCore.Http.Json.JsonOptionsinstance.WithJsonOptions(Func<IServiceProvider, JsonOptions> factory)— sets a factory invoked when the schema service is first resolved.BackOfficeOpenApiDocumentBuilderExtensions(inUmbraco.Cms.Api.Management.OpenApi):WithBackOfficeAuthentication()— adds backoffice OAuth2 security requirements to the document.Usage
Minimal:
Typical (extension template):
Opt out of the UI dropdown (e.g. machine-only docs):
Override only the UI dropdown label:
Align schema serialization with a registered named
JsonOptions:Testing
Scaffold a project from the Umbraco extension template (which uses the new helper out of the box) and run the site. Open
/umbraco/openapiin the backoffice and confirm the extension's document appears in the dropdown alongside Default / Management / Delivery, with theWithTitlevalue as the label and the[MapToApi("my-api")]controllers rendered under it. Then exercise each builder method:WithTitle— change the title in the composer, restart, and confirm the document'sInfo.Title(visible at/umbraco/openapi/{documentName}.jsonand in the UI header) and the dropdown label both update.WithUiTitle— set it to something different fromWithTitle; the UI dropdown should show theWithUiTitlevalue whileInfo.Titlecontinues to reflectWithTitle.ExcludeFromUi— add the call; the document should disappear from the dropdown but remain reachable at/umbraco/openapi/{documentName}.json.ConfigureOpenApiOptions— add anAddDocumentTransformerthat mutates the document (e.g.doc.Info.Description = "...") and confirm it shows up in the JSON. Verify multipleConfigureOpenApiOptionscalls compose by adding two transformers and observing both effects.WithJsonOptions(string)— register a namedMicrosoft.AspNetCore.Http.Json.JsonOptionsviaservices.Configure<JsonOptions>("my-name", ...), then call.WithJsonOptions("my-name"); confirm the schema reflects those options.WithJsonOptions(JsonOptions)— pass aJsonOptionsinstance with a clear deviation from the default (e.g.PropertyNamingPolicy = nullfor PascalCase) and confirm the schema property names in the JSON reflect that change.WithJsonOptions(Func<>)— passsp => sp.GetRequiredService<IOptionsMonitor<JsonOptions>>().Get("name")and confirm the schema reflects the resolved named options.WithBackOfficeAuthentication— leave it on (it's in the template by default) and confirm the SwaggerUI prompts for backoffice authentication when invoking endpoints from the rendered docs.Confirm the built-in Management API document still renders correctly through the migrated registration — same operation IDs, same security requirements, same schema output. The
UmbracoSchemaIdGenerator.CreateSchemaReferenceIdextraction is a refactor with no behaviour change for the Default and Delivery documents.