# Orchardcore Nswag Regenerate

> Regenerate the OrchardCore.OpenApi module's NSwag-generated C#/TypeScript API clients, and verify the regeneration produces a stable (non-reshuffled) diff. Use when asked to "regenerate the NSwag client(s)", update Services/OpenApiClient.cs or .scripts/bloom/services/OpenApiClient.ts, or investigate noisy diffs from NSwag regeneration.

- Skill: `orchardcms/orchardcore-nswag-regenerate` (Agent Skill)
- Install (CLI): `npx skillmds@latest add orchardcms/orchardcore-nswag-regenerate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/orchardcms/orchardcore-nswag-regenerate/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: orchardcms (https://skillmd.com/u/orchardcms)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/orchardcms/orchardcore-nswag-regenerate

---


# OrchardCore NSwag Regenerate

Regenerates the OpenApi module's NSwag-generated C#/TypeScript API clients and verifies the regeneration produces a stable, non-reshuffled diff.

## Preferred: `tools/OpenApiClientGenerator` (automated, no browser/dev-server needed)

A console project that does the whole pipeline headlessly:

```bash
dotnet run --project tools/OpenApiClientGenerator -c Release
yarn build   # refreshes the Vue app's bundled JS against the new TS client
```

It boots the CMS in-process on an ephemeral port, uses `OrchardCore.AutoSetup` with
`src/OrchardCore.Cms.Web/Recipes/openapi-generation-setup.recipe.json` (`issetuprecipe: true`)
to provision the **Default** tenant with the same feature set as `openapi-generation.recipe.json`
below, fetches `swagger.json`, writes a scratch `.nswag` config pointing at that capture, and
shells out to the `nswag` CLI (same prerequisite as the manual path: `dotnet tool install -g
NSwag.ConsoleCore`). Source: `tools/OpenApiClientGenerator/Program.cs`.

**Why Default tenant specifically**: `OrchardCore.Tenants` (and `.Distributed`,
`.FeatureProfiles`) have `DefaultTenantOnly = true` in their `[Feature]` manifest attribute —
they can only be enabled on the Default/Host shell, never a secondary tenant. A Blog-recipe
functional-test tenant cannot enable `OrchardCore.Tenants`, which is why this tool targets the
Default shell via `AutoSetup` rather than reusing the functional-test harness's tenant pattern.

This tool only regenerates the clients against whatever API surface the current source produces
— it does not add a CI drift-check gate (that was scoped out; see `nswag-automation-plan.md` in
git stash history for the fuller original proposal, "Part 1" of which this tool implements).

The manual procedure below still applies if you need to regenerate against a real running dev
server (e.g. to inspect `swagger.json` interactively) or investigate the tool's own behavior.

## Where things live

- Config: `src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag` — **this file already exists**, do not assume it's missing. It defines both generators:
  - `openApiToCSharpClient` → outputs `Services/OpenApiClient.cs` (relative to the module folder)
  - `openApiToTypeScriptClient` → outputs `../../../.scripts/bloom/services/OpenApiClient.ts`
- Source document: `documentGenerator.fromDocument.url` points at a running instance's Swashbuckle endpoint, `https://localhost:5001/swagger/v1/swagger.json`. NSwag also accepts a **local file path** in this same field instead of a URL — useful for reproducible/offline generation (see below).
- Recipe: `src/OrchardCore.Modules/OrchardCore.OpenApi/Recipes/openapi-generation.recipe.json` (name `OpenApiGeneration`) enables every feature that exposes an API endpoint: `OrchardCore.Contents`, `OrchardCore.Queries`, `OrchardCore.Tenants`, `OrchardCore.Search.Lucene`, `OrchardCore.Search.Elasticsearch`, `OrchardCore.OpenApi`. It also contains a `Settings` step setting `OpenApiSettings.AllowAnonymousSchemaAccess: true` — anonymous schema access is **disabled by default** (the middleware returns 401 for unauthenticated `swagger.json` fetches), and both NSwag paths below fetch the schema anonymously. The setup recipe used by the generator tool (`openapi-generation-setup.recipe.json`) has the same step. Note the `Settings` step replaces the whole stored `OpenApiSettings` object, wiping any OAuth configuration on the tenant it runs on — fine for throwaway generation tenants, worth knowing on a configured one.
  - It has `"issetuprecipe": false`, so it does **not** appear in the new-tenant/site-setup recipe dropdown. Run it against an already-set-up tenant instead, via **Admin ▸ Configuration ▸ Recipes**, click "Run" next to "OpenApi Generation", confirm the modal. (`/Admin/Recipes`, `AdminController.Execute` in `OrchardCore.Recipes`.)

## Manual procedure (fallback)

### NSwag CLI

Installed as a dotnet tool: `~/.dotnet/tools/nswag` (or `dotnet tool install -g NSwag.ConsoleCore` if missing). Run with:

```bash
nswag run src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag
```

This requires a live server at the configured `url`. For a real regeneration that updates the committed files, run the actual local dev server, set it up with the `OpenApiGeneration` recipe, and run the command above unmodified.

## Reproducible / offline generation (for testing determinism, without touching committed files)

1. Fetch `swagger.json` from a running instance into a scratch file.
2. Copy the `.nswag` config, and in the copy only change `documentGenerator.fromDocument.url` to the scratch file's path, and both generators' `output` to scratch paths. Do this with a small Python/jq one-liner rather than hand-editing — the config is plain JSON.
3. `nswag run <scratch-config>`.

This lets you diff two independent generations without ever touching the real generated files or needing HTTPS/dev-cert setup.

## Determinism: why regeneration used to reshuffle unrelated methods

Two independent root causes were found and fixed (skrypt/openapi branch):

1. **Swashbuckle's operation order wasn't pinned.** Without an explicit sort, `swagger.json` operations come out in whatever order ASP.NET Core's action discovery enumerates them — which depends on assembly/feature load order in OrchardCore's modular architecture, not source order. Fixed in `OrchardCore.OpenApi/Startup.cs`'s `AddSwaggerGen` call:
   ```csharp
   c.OrderActionsBy(apiDesc => $"{apiDesc.RelativePath}_{apiDesc.HttpMethod}");
   ```
   Route path + verb is fixed at compile time and unique per operation, so this is stable regardless of load order.

2. **A duplicate `operationId` across two operations.** `QueryApiController` used to have one action handling both `GET` and `POST` on `api/queries/{name}` under a single `[EndpointName("ApiExecuteQuery")]`. OpenAPI requires `operationId` to be unique per operation — reusing one across two operations forced NSwag to invent a disambiguating suffix internally, and that suffix logic wasn't deterministic across runs (e.g. `ApiExecuteQueryPOSTAsync` vs `ApiExecuteQueryPOSTPOSTAsync`). Fixed by splitting into two actions with distinct names (`ApiExecuteQueryGet` / `ApiExecuteQueryPost`), delegating to a shared private method — the pattern already used by e.g. `ElasticsearchApiController` (`ApiGetElasticsearchContent`/`ApiPostElasticsearchContent`).

**Lesson for any future controller/endpoint added to the OpenApi-exposed surface**: never reuse the same `[EndpointName]`/`operationId` across two different HTTP verbs on the same route. Give each verb its own action and its own name, even if they share implementation via a private helper.

## Verifying stability empirically

Boot two independently-provisioned tenants (each gets its own `ShellContext` and fresh feature/extension discovery — this is what actually varies, not wall-clock time), run the `OpenApiGeneration` recipe on each, fetch `swagger.json` from each, generate against both, and diff. A stable setup produces byte-identical output except for the tenant's own base-URL prefix embedded in the client's default `baseUrl` (an expected, real difference between distinct tenants — not a bug).

The existing functional test suite (`test/OrchardCore.Tests.Functional/Tests/Cms/OpenApiTests.cs`) already exercises the relevant plumbing (feature enablement, swagger.json access) if you need a template for scripting this via Playwright/`CmsTestBase`. Any throwaway verification test written for this should be deleted afterward — it's not meant to be a permanent part of the suite.

