惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
月光博客
月光博客
AI
AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
CXSECURITY Database RSS Feed - CXSecurity.com
WordPress大学
WordPress大学
GbyAI
GbyAI
L
Lohrmann on Cybersecurity
O
OpenAI News
Schneier on Security
Schneier on Security
P
Palo Alto Networks Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
W
WeLiveSecurity
L
LINUX DO - 最新话题
人人都是产品经理
人人都是产品经理
S
Security Affairs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
Arctic Wolf
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
TaoSecurity Blog
TaoSecurity Blog
C
Check Point Blog
Scott Helme
Scott Helme
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Vercel News
Vercel News
The Hacker News
The Hacker News
Y
Y Combinator Blog
Latest news
Latest news
SecWiki News
SecWiki News
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
C
Cisco Blogs
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
宝玉的分享
宝玉的分享
L
LINUX DO - 热门话题

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Umbraco 18 and OpenAPI: a heads-up for extension developers
Laura Neto · 2026-05-19 · via DEV Community

Time to upgrade your extension to Umbraco 18?
A friendly heads up, the OpenAPI generation got an overhaul.

Microsoft.AspNetCore.OpenApi is Umbraco's new library of choice for OpenAPI document generation, replacing Swashbuckle.AspNetCore. It ships with ASP.NET Core and is maintained by Microsoft alongside the framework, which means one fewer third-party dependency to worry about.

The trade-off: if your extension wired up its own OpenAPI document on v17, it won't compile against v18. The Swashbuckle types it relied on are gone.

Two paths forward:

  1. Migrate to Microsoft OpenAPI. Two options:
  2. Keep using Swashbuckle (not recommended). Install Swashbuckle.AspNetCore in your own extension, wire it up, then plug your document into Umbraco's Swagger UI via AddOpenApiDocumentToUi(...). When rewriting isn't an option right now.

ℹ️ Note: the OpenAPI spec URL also changed from /umbraco/swagger/{documentName}/swagger.json to /umbraco/openapi/{documentName}.json. If you generate a client from the spec (e.g. via the v17 extension template's package.json generate-client script), update the URL before regenerating.

Use the AddBackOfficeOpenApiDocument extension method

We added AddBackOfficeOpenApiDocument() in v18 to take the boilerplate out of registering an OpenAPI document. It's an extension method on IUmbracoBuilder that wires up Umbraco's sensible defaults so you don't have to.

Out of the box, the helper registers your document, scopes it to endpoints decorated with [MapToApi(documentName)], applies Umbraco's naming conventions for schemas and operation IDs, and adds the document to the Swagger UI dropdown at /umbraco/openapi/.

⚠️ Watch out: the first time you call AddBackOfficeOpenApiDocument (or AddOpenApi directly) and build, you'll hit error CS9137: The 'interceptors' feature is not enabled in this namespace. The Microsoft.AspNetCore.OpenApi source generator (which surfaces your XML doc comments as descriptions in the OpenAPI document, among other compile-time enrichments) propagates through Umbraco's project reference, but the property that enables it ships only with the Web SDK. Class-library projects don't get it automatically, so add it to your .csproj:

<PropertyGroup>
  <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
</PropertyGroup>

Minimal usage

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api");
    }
}

Enter fullscreen mode Exit fullscreen mode

This will add and configure a "my-api" OpenAPI document that will display the controllers/endpoints that have the attribute [MapToApi("my-api")].

Typical usage

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api", document => document
            .WithTitle("My API")
            .WithBackOfficeAuthentication());
    }
}

Enter fullscreen mode Exit fullscreen mode

Besides adding a "my-api" OpenAPI document, this will set the document title to "My API" and declare that the document's operations require backoffice authentication (which is what enables the "Authorize" flow in the Swagger UI).

Builder method reference

Method What it does
WithTitle(string) Sets the document's Info.Title. Also used as the UI dropdown label unless WithUiTitle is set.
WithUiTitle(string) Overrides only the UI dropdown label, leaving Info.Title untouched.
ExcludeFromUi() Suppresses registration in the Swagger UI document dropdown. Document is still reachable at its JSON URL.
WithBackOfficeAuthentication() Registers the backoffice OAuth2 security scheme on the document and marks operations as requiring authentication. Swagger UI uses this to prompt for login; generated clients use it to send the token.
ConfigureOpenApiOptions(Action<OpenApiOptions>) Adds a configuration callback for the underlying OpenApiOptions. Multiple calls will run in order after Umbraco's defaults.
WithJsonOptions(...) Sets the JsonOptions used when generating the document's schema. See Aligning schema serialization below.

Aligning schema serialization

Schema generation needs to use the same JsonOptions your API uses at runtime, otherwise the generated SDKs and the actual payloads will not match. By default, Microsoft.AspNetCore.OpenApi generates schemas using the global HTTP JsonOptions, which means whoever hosts your extension can change them and that affects your schema.

For most backoffice extensions you want the BackOffice named JsonOptions (the same ones Umbraco's Management API uses). Point the document at them with WithJsonOptions:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api", document => document
            .WithJsonOptions(Constants.JsonOptionsNames.BackOffice));
    }
}

Enter fullscreen mode Exit fullscreen mode

For this to be meaningful at runtime, your controllers also need to serialize with those same options. The v17 extension template inherits from a plain ControllerBase, which uses the default options instead. Fix that by adding [JsonOptionsName(Constants.JsonOptionsNames.BackOffice)] to your own base controller or to each controller directly.

Without it, the schema reflects backoffice serialization but the actual responses are serialized with the default options, and you're back to the schema-vs-runtime mismatch.

Going beyond the defaults

The helper is not a closed box. Anything you'd normally do on OpenApiOptions (custom operation/schema/document transformers, your own CreateSchemaReferenceId, a replacement operation ID transformer, etc.) is still available through ConfigureOpenApiOptions. It just runs after Umbraco's defaults, so you compose on top of them rather than starting from scratch.

For example, registering your own schema transformer:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api", document => document
            .WithTitle("My API")
            .ConfigureOpenApiOptions(options =>
                options.AddSchemaTransformer<MyExtensionSchemaTransformer>()));
    }
}

Enter fullscreen mode Exit fullscreen mode

Migrating an existing composer

If your extension followed the shape of the v17 dotnet template, the migration is: delete the old composer body and write the AddBackOfficeOpenApiDocument(...) call. Below is the same extension's composer before and after.

v17 (Swashbuckle)

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddSingleton<IOperationIdHandler, CustomOperationHandler>();

        builder.Services.Configure<SwaggerGenOptions>(opt =>
        {
            opt.SwaggerDoc(Constants.ApiName, new OpenApiInfo
            {
                Title = "My Test Extension Backoffice API",
            });

            opt.OperationFilter<UmbracoExtensionOperationSecurityFilter>();
        });
    }

    public class UmbracoExtensionOperationSecurityFilter : BackOfficeSecurityRequirementsOperationFilterBase
    {
        protected override string ApiName => Constants.ApiName;
    }

    public class CustomOperationHandler : OperationIdHandler
    {
        // ...
    }
}

Enter fullscreen mode Exit fullscreen mode

v18 (Microsoft OpenAPI + new builder)

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder) =>
        builder.AddBackOfficeOpenApiDocument(
            Constants.ApiName,
            document => document
                .WithTitle("My Test Extension Backoffice API")
                .WithBackOfficeAuthentication());
}

Enter fullscreen mode Exit fullscreen mode

The helper registers UmbracoOperationIdTransformer by default, so the old IOperationIdHandler is no longer needed. If you want custom operation ID logic, register your own through ConfigureOpenApiOptions:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api", document => document
            .ConfigureOpenApiOptions(options =>
                options.AddOperationTransformer((operation, context, _) =>
                {
                    operation.OperationId = $"{context.Description.ActionDescriptor.RouteValues["action"]}";
                    return Task.CompletedTask;
                })));
    }
}

Enter fullscreen mode Exit fullscreen mode

Use Microsoft's AddOpenApi method directly

If you want to register a document that isn't a backoffice document, or you want to start from a clean slate without Umbraco's defaults, call AddOpenApi yourself. The helper is just a thin wrapper around that method. Umbraco already wires the OpenAPI routing and Swagger UI hosting, so you don't need to call MapOpenApi or configure endpoints. You only need to register your documents.

For the transformer model itself (how operation/schema/document transformers work), see Microsoft's Customize OpenAPI documents docs.

For Umbraco's specifics ([MapToApi] filtering via options.ShouldInclude, schema/operation ID conventions, the public UmbracoSchemaIdGenerator.Generate(type) helper), see the API versioning and OpenAPI docs.

A few things worth remembering:

  • Backoffice authentication: call options.AddBackofficeSecurityRequirements() (from Umbraco.Cms.Api.Management.OpenApi) instead of subclassing BackOfficeSecurityRequirementsOperationFilterBase.
  • Swagger UI registration: documents are no longer auto-listed. Call services.AddOpenApiDocumentToUi(documentName, uiTitle) to surface yours in the dropdown.
  • InterceptorsNamespaces opt-in: same as the helper path. See the Watch out callout at the top of the helper section.

Keep using Swashbuckle

Not recommended. Consider this only if rewriting a complex Swashbuckle setup isn't feasible right now. Trade-offs:

  • Extra dependency on Swashbuckle.AspNetCore.
  • OpenAPI 3.0 output, whereas the rest of Umbraco's documents emit 3.1.
  • Extra wiring you maintain yourself.
  • Both libraries depend on Microsoft.OpenApi. A breaking change there that Swashbuckle doesn't follow leaves your extension incompatible with the rest of the stack.

If you still want to go this route, see the Swashbuckle.AspNetCore README for general setup. The rest of this section is the Umbraco-specific wiring.

1. Install the NuGet package

Add a PackageReference to Swashbuckle.AspNetCore in your extension's .csproj (or dotnet add package Swashbuckle.AspNetCore).

2. Register Swashbuckle and your document in a composer

In v17, Umbraco called AddSwaggerGen for you and your extension only configured SwaggerGenOptions. In v18 you have to call it yourself:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddSwaggerGen(opt =>
        {
            opt.SwaggerDoc(Constants.ApiName, new OpenApiInfo
            {
                Title = "My Test Extension Backoffice API",
                Version = "1.0",
            });

            var previousDocInclusion = opt.SwaggerGeneratorOptions.DocInclusionPredicate;
            opt.DocInclusionPredicate((docName, apiDesc) =>
                docName == Constants.ApiName
                    ? apiDesc.ActionDescriptor.HasMapToApiAttribute(docName)
                    : previousDocInclusion?.Invoke(docName, apiDesc) ?? true);

            // any operation filters, schema filters, or other v17 setup you had...
        });
    }
}

Enter fullscreen mode Exit fullscreen mode

3. Host the Swagger JSON middleware via an IUmbracoPipelineFilter

Umbraco no longer calls app.UseSwagger(), so the middleware that serves the Swagger JSON isn't running. Add it through Umbraco's pipeline filter, and set the RouteTemplate to match UmbracoOpenApiOptions.RouteTemplate (default umbraco/openapi/{documentName}.json) so the dropdown link resolves.

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.Configure<UmbracoPipelineOptions>(options =>
        {
            options.AddFilter(new UmbracoPipelineFilter("Swashbuckle")
            {
                PostPipeline = app =>
                {
                    var openApiOptions = app.ApplicationServices.GetRequiredService<IOptions<UmbracoOpenApiOptions>>().Value;
                    if (!openApiOptions.Enabled)
                    {
                        return;
                    }

                    var routeTemplate = openApiOptions.RouteTemplate;
                    app.UseWhen(
                        ctx => ctx.Request.Path == routeTemplate.Replace("{documentName}", Constants.ApiName).EnsureStartsWith('/'),
                        branch => branch.UseSwagger(c => c.RouteTemplate = routeTemplate));
                },
            });
        });
    }
}

Enter fullscreen mode Exit fullscreen mode

Scope the middleware to your own document's URL. Otherwise Swashbuckle intercepts requests for Umbraco's own documents (Management, Delivery) and returns 404 because they aren't registered with AddSwaggerGen.

4. Register the document in the Swagger UI dropdown

Register the document in Umbraco's Swagger UI dropdown:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddOpenApiDocumentToUi(Constants.ApiName, "My API");
    }
}

Enter fullscreen mode Exit fullscreen mode

TL;DR

The helper is the simplest option for most extensions. AddBackOfficeOpenApiDocument() is what the v18 template uses, gives you Umbraco's sensible defaults out of the box (naming conventions, Swagger UI registration, JsonOptions alignment), and lets you register an OpenAPI document in a single call.

Calling AddOpenApi yourself is the standard ASP.NET Core approach and works fine. It means more wiring and code to maintain, so reach for it when you need full control (non-backoffice documents, or skipping the Umbraco defaults).

If rewriting really isn't an option right now, keeping Swashbuckle as your own dependency and plugging into the dropdown via AddOpenApiDocumentToUi(...) will get you through, but plan to revisit when you can.

Further reading