umbraco-openapi-client
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUmbraco OpenAPI Client Setup
Umbraco OpenAPI客户端设置
CRITICAL: Why This Matters
重要提示:为何这很关键
NEVER use raw calls for Umbraco backoffice API communication. Raw fetch calls will result in 401 Unauthorized errors because they don't include the bearer token authentication that Umbraco requires.
fetch()ALWAYS use a generated OpenAPI client configured with Umbraco's auth context. This ensures:
- Proper bearer token authentication
- Type-safe API calls
- Automatic token refresh handling
绝对不要在Umbraco后台API通信中使用原生调用。 原生fetch调用会返回401未授权错误,因为它们不包含Umbraco所需的Bearer令牌认证。
fetch()务必使用配置了Umbraco认证上下文的生成式OpenAPI客户端,这能确保:
- 正确的Bearer令牌认证
- 类型安全的API调用
- 自动令牌刷新处理
When to Use This
使用场景
Use this pattern whenever you:
- Create custom C# API controllers with
[BackOfficeRoute] - Need to call your custom APIs from the backoffice frontend
- Build trees, workspaces, or any UI that loads data from custom endpoints
当你遇到以下情况时,请使用此模式:
- 创建带有特性的自定义C# API控制器
[BackOfficeRoute] - 需要从后台前端调用自定义API
- 构建树、工作区或任何从自定义端点加载数据的UI
Setup Overview
设置概述
The setup has 4 parts:
- C# Backend: Controller with Swagger/OpenAPI documentation
- Client Dependencies: and
@hey-api/openapi-ts@hey-api/client-fetch - Generation Script: Fetches swagger.json and generates TypeScript client
- Entry Point Configuration: Configures client with Umbraco auth
设置分为4个部分:
- C#后端:带有Swagger/OpenAPI文档的控制器
- 客户端依赖:和
@hey-api/openapi-ts@hey-api/client-fetch - 生成脚本:获取swagger.json并生成TypeScript客户端
- 入口点配置:使用Umbraco认证信息配置客户端
Step-by-Step Implementation
分步实现
1. C# Backend Setup (Swagger/OpenAPI)
1. C#后端设置(Swagger/OpenAPI)
Your API must be exposed via Swagger. Create a composer:
csharp
// Composers/MyApiComposer.cs
using Asp.Versioning;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace MyExtension.Composers;
public class MyApiComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
// Register the API and Swagger
builder.Services.AddSingleton<ISchemaIdHandler, MySchemaIdHandler>();
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, MySwaggerGenOptions>();
builder.Services.Configure<UmbracoPipelineOptions>(options =>
{
options.AddFilter(new UmbracoPipelineFilter(Constants.ApiName)
{
SwaggerPath = $"/umbraco/swagger/{Constants.ApiName.ToLower()}/swagger.json",
SwaggerRoutePrefix = $"{Constants.ApiName.ToLower()}",
});
});
}
}
// Swagger schema ID handler
public class MySchemaIdHandler : SchemaIdHandler
{
public override bool CanHandle(Type type)
=> type.Namespace?.StartsWith("MyExtension") ?? false;
}
// Swagger generation options
public class MySwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
options.SwaggerDoc(
Constants.ApiName,
new OpenApiInfo
{
Title = "My Extension API",
Version = "1.0",
});
}
}
// Constants
public static class Constants
{
public const string ApiName = "myextension";
}你的API必须通过Swagger暴露。创建一个Composer:
csharp
// Composers/MyApiComposer.cs
using Asp.Versioning;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace MyExtension.Composers;
public class MyApiComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
// Register the API and Swagger
builder.Services.AddSingleton<ISchemaIdHandler, MySchemaIdHandler>();
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, MySwaggerGenOptions>();
builder.Services.Configure<UmbracoPipelineOptions>(options =>
{
options.AddFilter(new UmbracoPipelineFilter(Constants.ApiName)
{
SwaggerPath = $"/umbraco/swagger/{Constants.ApiName.ToLower()}/swagger.json",
SwaggerRoutePrefix = $"{Constants.ApiName.ToLower()}",
});
});
}
}
// Swagger schema ID handler
public class MySchemaIdHandler : SchemaIdHandler
{
public override bool CanHandle(Type type)
=> type.Namespace?.StartsWith("MyExtension") ?? false;
}
// Swagger generation options
public class MySwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
options.SwaggerDoc(
Constants.ApiName,
new OpenApiInfo
{
Title = "My Extension API",
Version = "1.0",
});
}
}
// Constants
public static class Constants
{
public const string ApiName = "myextension";
}2. Client package.json Dependencies
2. Client package.json依赖
Add to your :
Client/package.jsonjson
{
"scripts": {
"generate-client": "node scripts/generate-openapi.js https://localhost:44325/umbraco/swagger/myextension/swagger.json"
},
"devDependencies": {
"@hey-api/client-fetch": "^0.10.0",
"@hey-api/openapi-ts": "^0.66.7",
"chalk": "^5.4.1",
"node-fetch": "^3.3.2"
}
}添加到你的:
Client/package.jsonjson
{
"scripts": {
"generate-client": "node scripts/generate-openapi.js https://localhost:44325/umbraco/swagger/myextension/swagger.json"
},
"devDependencies": {
"@hey-api/client-fetch": "^0.10.0",
"@hey-api/openapi-ts": "^0.66.7",
"chalk": "^5.4.1",
"node-fetch": "^3.3.2"
}
}3. Generation Script
3. 生成脚本
Create :
Client/scripts/generate-openapi.jsjavascript
import fetch from "node-fetch";
import chalk from "chalk";
import { createClient, defaultPlugins } from "@hey-api/openapi-ts";
console.log(chalk.green("Generating OpenAPI client..."));
const swaggerUrl = process.argv[2];
if (swaggerUrl === undefined) {
console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`));
process.exit(1);
}
// Ignore self-signed certificates on localhost
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`);
fetch(swaggerUrl)
.then(async (response) => {
if (!response.ok) {
console.error(chalk.red(`ERROR: ${response.status} ${response.statusText}`));
return;
}
await createClient({
input: swaggerUrl,
output: "src/api",
plugins: [
...defaultPlugins,
{
name: "@hey-api/client-fetch",
bundle: true,
exportFromIndex: true,
throwOnError: true,
},
{
name: "@hey-api/typescript",
enums: "typescript",
},
{
name: "@hey-api/sdk",
asClass: true,
},
],
});
console.log(chalk.green("Client generated successfully!"));
})
.catch((error) => {
console.error(`ERROR: ${chalk.red(error.message)}`);
});创建:
Client/scripts/generate-openapi.jsjavascript
import fetch from "node-fetch";
import chalk from "chalk";
import { createClient, defaultPlugins } from "@hey-api/openapi-ts";
console.log(chalk.green("Generating OpenAPI client..."));
const swaggerUrl = process.argv[2];
if (swaggerUrl === undefined) {
console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`));
process.exit(1);
}
// Ignore self-signed certificates on localhost
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`);
fetch(swaggerUrl)
.then(async (response) => {
if (!response.ok) {
console.error(chalk.red(`ERROR: ${response.status} ${response.statusText}`));
return;
}
await createClient({
input: swaggerUrl,
output: "src/api",
plugins: [
...defaultPlugins,
{
name: "@hey-api/client-fetch",
bundle: true,
exportFromIndex: true,
throwOnError: true,
},
{
name: "@hey-api/typescript",
enums: "typescript",
},
{
name: "@hey-api/sdk",
asClass: true,
},
],
});
console.log(chalk.green("Client generated successfully!"));
})
.catch((error) => {
console.error(`ERROR: ${chalk.red(error.message)}`);
});4. Entry Point Configuration (CRITICAL)
4. 入口点配置(关键)
Configure the generated client with Umbraco's auth context in your entry point:
typescript
// src/entrypoints/entrypoint.ts
import type { UmbEntryPointOnInit, UmbEntryPointOnUnload } from "@umbraco-cms/backoffice/extension-api";
import { UMB_AUTH_CONTEXT } from "@umbraco-cms/backoffice/auth";
import { client } from "../api/client.gen.js";
export const onInit: UmbEntryPointOnInit = (host, _extensionRegistry) => {
// CRITICAL: Configure the OpenAPI client with authentication
host.consumeContext(UMB_AUTH_CONTEXT, (authContext) => {
if (!authContext) return;
const config = authContext.getOpenApiConfiguration();
client.setConfig({
baseUrl: config.base,
credentials: config.credentials,
auth: config.token, // This provides the bearer token!
});
console.log("API client configured with auth");
});
};
export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => {
// Cleanup if needed
};在你的入口点中使用Umbraco的认证上下文配置生成的客户端:
typescript
// src/entrypoints/entrypoint.ts
import type { UmbEntryPointOnInit, UmbEntryPointOnUnload } from "@umbraco-cms/backoffice/extension-api";
import { UMB_AUTH_CONTEXT } from "@umbraco-cms/backoffice/auth";
import { client } from "../api/client.gen.js";
export const onInit: UmbEntryPointOnInit = (host, _extensionRegistry) => {
// 关键:使用认证信息配置OpenAPI客户端
host.consumeContext(UMB_AUTH_CONTEXT, (authContext) => {
if (!authContext) return;
const config = authContext.getOpenApiConfiguration();
client.setConfig({
baseUrl: config.base,
credentials: config.credentials,
auth: config.token, // 这会提供Bearer令牌!
});
console.log("API client configured with auth");
});
};
export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => {
// 如有需要可进行清理
};5. Using the Generated Client
5. 使用生成的客户端
After running , use the generated service:
npm run generate-clienttypescript
// In your workspace context, repository, or data source
import { MyExtensionService } from "../api/index.js";
// The client handles auth automatically!
const response = await MyExtensionService.getItems({
query: { skip: 0, take: 50 },
});
const item = await MyExtensionService.getItem({
path: { id: "some-guid" },
});
await MyExtensionService.createItem({
body: { name: "New Item", value: 123 },
});运行后,使用生成的服务:
npm run generate-clienttypescript
// 在你的工作区上下文、仓库或数据源中
import { MyExtensionService } from "../api/index.js";
// 客户端会自动处理认证!
const response = await MyExtensionService.getItems({
query: { skip: 0, take: 50 },
});
const item = await MyExtensionService.getItem({
path: { id: "some-guid" },
});
await MyExtensionService.createItem({
body: { name: "New Item", value: 123 },
});Generation Workflow
生成流程
- Start Umbraco - The swagger.json endpoint must be accessible
- Run generation:
npm run generate-client - Generated files appear in :
src/api/- - TypeScript types from your C# models
types.gen.ts - - Service class with typed methods
sdk.gen.ts - - HTTP client configuration
client.gen.ts - - Re-exports everything
index.ts
- 启动Umbraco - swagger.json端点必须可访问
- 运行生成命令:
npm run generate-client - 生成的文件会出现在目录中:
src/api/- - 来自C#模型的TypeScript类型
types.gen.ts - - 带有类型化方法的服务类
sdk.gen.ts - - HTTP客户端配置
client.gen.ts - - 重新导出所有内容
index.ts
Common Mistakes
常见错误
❌ WRONG: Raw fetch
❌ 错误用法:原生fetch
typescript
// This will get 401 Unauthorized!
const response = await fetch('/umbraco/myextension/api/v1/items');typescript
// 这会返回401未授权错误!
const response = await fetch('/umbraco/myextension/api/v1/items');❌ WRONG: fetch with credentials only
❌ 错误用法:仅带凭证的fetch
typescript
// Still fails - cookies don't work for Management API
const response = await fetch('/umbraco/myextension/api/v1/items', {
credentials: 'include'
});typescript
// 仍然失败 - Cookie不适用于管理API
const response = await fetch('/umbraco/myextension/api/v1/items', {
credentials: 'include'
});✅ CORRECT: Generated OpenAPI client
✅ 正确用法:生成的OpenAPI客户端
typescript
// Client is configured with bearer token in entry point
const response = await MyExtensionService.getItems();typescript
// 客户端已在入口点配置了Bearer令牌
const response = await MyExtensionService.getItems();Reference Example
参考示例
See the complete working implementation in:
- - Full OpenAPI client setup
examples/notes-wiki/Client/ - - Tree with OpenAPI integration
examples/tree-example/Client/
可查看完整的可运行实现:
- - 完整的OpenAPI客户端设置
examples/notes-wiki/Client/ - - 集成OpenAPI的树组件
examples/tree-example/Client/
Key Files to Create
需要创建的关键文件
- - Swagger registration
Composers/MyApiComposer.cs - - Generation script
Client/scripts/generate-openapi.js - - Auth configuration
Client/src/entrypoints/entrypoint.ts - - Generated (don't edit manually)
Client/src/api/
That's it! Always generate your API client and configure it with auth. Never use raw fetch for authenticated endpoints.
- - Swagger注册
Composers/MyApiComposer.cs - - 生成脚本
Client/scripts/generate-openapi.js - - 认证配置
Client/src/entrypoints/entrypoint.ts - - 生成的文件(请勿手动编辑)
Client/src/api/
完成!请始终生成API客户端并配置认证信息。绝对不要在认证端点中使用原生fetch。