Skip to content

Migration Guide

David Kallesen edited this page Sep 28, 2026 · 5 revisions

πŸ”„ Migration Guide

This guide explains how to migrate existing projects from the old atc-rest-api-generator CLI tool to the new atc-rest-api-source-generator Roslyn-based source generator.

πŸ“‹ Overview

The old CLI tool (atc-rest-api-generator) required running scripts to generate code before building. The new source generator (Atc.Rest.Api.SourceGenerator) automatically generates code at build time - no manual steps required.

βš–οΈ Key Differences

Aspect Old CLI Generator New Source Generator
Generation Manual script execution Automatic at build time
Package atc-rest-api-generator (CLI tool) Atc.Rest.Api.SourceGenerator (NuGet)
Generated Code Committed to repo Generated on-the-fly (not committed)
Project Naming *.Api.Generated, *.ApiClient.Generated *.Api.Contracts, *.ApiClient
Configuration *GeneratorOptions.json Marker files (.atc-rest-api-*)
Target Framework .NET 8+ .NET 10+

βœ… Pre-Migration Checklist

Before starting, verify all items:

  • Project uses the old atc-rest-api-generator CLI tool
  • Valid OpenAPI 3.x specification file exists and passes validation
  • Project is under git version control with a clean working tree
  • All current tests pass (dotnet test)
  • CI/CD pipeline is green (no pre-existing failures)
  • Team is aware of the migration (generated code will change significantly)
  • You have read this entire guide before executing any commands
  • Ready to upgrade to .NET 10 with C# 14

⚠️ Important: The migration modifies project structure, renames projects, and changes namespaces. Always work on a dedicated branch and have a rollback plan ready.

πŸš€ Migration Steps

Step 1️⃣: Install the CLI Tool

dotnet tool install -g atc-rest-api-gen

Or update if already installed:

dotnet tool update -g atc-rest-api-gen

Step 2️⃣: Validate Your Project

Run the validation command to check if your project is ready for migration:

atc-rest-api-gen migrate validate -s <spec-path> [--solution <solution-path>]

Example:

atc-rest-api-gen migrate validate -s D:\Code\MyProject\src\Specs\api.yaml

The solution (.sln or .slnx) is found by itself: the nearest folder with one, searched from the specification's folder upwards (here D:\Code\MyProject), then from the current directory upwards. Pass --solution <path> to choose it explicitly.

πŸ’‘ Like every other command, migrate takes the specification with -s|--specification (since 2.1.1). Earlier versions used -s for the solution and -p|--spec for the specification. That spelling still works: a -s that points at a .sln/.slnx file or a folder is taken as the solution, and -p|--spec is still accepted for the specification.

The validator will check:

  • Solution structure (Api.Generated, Domain, Host API projects)
  • OpenAPI specification validity
  • Target framework compatibility
  • Package references
  • Handler implementations

Step 3️⃣: Review Validation Output

The validation output shows what the migration will do:

ATC REST API
Migration Validator

Solution:      D:\Code\MyProject\MyProject.slnx
Specification: D:\Code\MyProject\src\Specs\api.yaml

[blue]Project Structure[/]
  βœ“ Solution file found: MyProject.slnx
  βœ“ Api.Generated project found: MyProject.Api.Generated
  βœ“ Domain project found: MyProject.Domain
  βœ“ Host API project found: MyProject.Api

[blue]Target Framework & Language Version[/]
  βœ“ Target framework: net9.0 (upgrade required β†’ net10.0)
  βœ“ Language version: C# 13 (upgrade required β†’ C# 14)
  [yellow]![/] Upgrade to .NET 10 / C# 14 will be required

═══════════════════════════════════════════════════════════════════
[yellow]! Project is eligible for migration (with .NET 10 / C# 14 upgrade)[/]
═══════════════════════════════════════════════════════════════════

Step 4️⃣: Commit Your Changes

Before running migration, commit all your changes to git:

git add .
git commit -m "Pre-migration commit"

This allows easy rollback if needed.

Step 5️⃣: Run Dry-Run First

Preview what changes will be made without executing them:

atc-rest-api-gen migrate execute -s <spec-path> [--solution <solution-path>] --dry-run

Review the output carefully to understand all changes.

Step 6️⃣: Execute Migration

Run the migration:

atc-rest-api-gen migrate execute -s <spec-path> [--solution <solution-path>]

The migration will:

  1. Check git status - Warn if uncommitted changes exist
  2. Confirm upgrade - Prompt for .NET 10 / C# 14 upgrade
  3. Create marker files - .atc-rest-api-server, .atc-rest-api-client, .atc-rest-api-server-handlers
  4. Update project files - Add source generator package, configure AdditionalFiles
  5. Handle ATC Coding Rules - Update or set up atc-coding-rules-updater.json
  6. Clean generated code - Delete old generated files (Contracts/, Endpoints/, etc.)
  7. Rename projects - Api.Generated β†’ Api.Contracts, ApiClient.Generated β†’ ApiClient
  8. Update solution - Update project references and solution file
  9. Update GlobalUsings - Update namespace references in Domain project
  10. Migrate parameter names - Update handler code for parameter name changes

Step 7️⃣: Build and Verify

After migration, build your project:

dotnet build

The source generator will automatically generate all code at build time.

πŸ“ What Gets Changed

🏷️ Project Renames

Old Name New Name
MyProject.Api.Generated MyProject.Api.Contracts
MyProject.ApiClient.Generated MyProject.ApiClient

βž• Files Created

  • .atc-rest-api-server in Api.Contracts project
  • .atc-rest-api-client in ApiClient project (if exists)
  • .atc-rest-api-server-handlers in Domain project

βž– Files Deleted

From Api.Contracts (formerly Api.Generated):

  • Contracts/ folder
  • Endpoints/ folder
  • Resources/ folder
  • GlobalUsings.cs
  • IApiContractAssemblyMarker.cs

From ApiClient (formerly ApiClient.Generated):

  • Contracts/ folder
  • Endpoints/ folder
  • GlobalUsings.cs

πŸ“¦ Namespace Updates

// Before
global using MyProject.Api.Generated.Accounts.Handlers;
global using MyProject.Api.Generated.Accounts.Results;

// After
global using MyProject.Api.Contracts.Generated.Accounts.Handlers;
global using MyProject.Api.Contracts.Generated.Accounts.Results;

πŸ”€ Parameter Name Migration

The migration tool automatically updates handler code when parameter property names differ between generators:

// Before (old generator)
var token = parameters.Continuation;

// After (new generator)
var token = parameters.ContinuationToken;

This applies to both the Domain and Domain.Tests projects.

βͺ Rollback

If something goes wrong, use git to revert:

# Revert all changes
git checkout -- .
git clean -fd

# Or reset to pre-migration commit
git reset --hard HEAD~1

πŸ“– Command Reference

πŸ” migrate validate

Validates a project for migration readiness.

atc-rest-api-gen migrate validate -s <spec-path> [--solution <solution-path>] [options]
Option Description
-s, --specification <PATH> Path to OpenAPI specification file (required)
--solution <PATH> Solution file (.sln/.slnx) or its directory. Default: the nearest folder with a .sln/.slnx above the specification, then above the current directory
--verbose Show detailed validation output
--output-report <PATH> Save validation report to file

▢️ migrate execute

Performs the actual migration.

atc-rest-api-gen migrate execute -s <spec-path> [--solution <solution-path>] [options]
Option Description
-s, --specification <PATH> Path to OpenAPI specification file (required)
--solution <PATH> Solution file (.sln/.slnx) or its directory. Default: the nearest folder with a .sln/.slnx above the specification, then above the current directory
--dry-run Preview changes without executing
--force Skip confirmation prompts

πŸ”™ Rollback Procedures

If the migration fails or produces unexpected results, you can roll back at any point.

Full Rollback (recommended)

If you committed before migration (Step 4), simply reset to that commit:

# Discard all migration changes
git reset --hard HEAD~1

# Or if multiple commits were made during migration
git log --oneline -5    # Find the pre-migration commit
git reset --hard <commit-hash>

Partial Rollback

If the migration partially succeeded and you want to keep some changes:

# See what changed
git diff --stat

# Selectively discard files
git checkout -- src/MyProject.Api.Generated/   # Restore old generated project
git checkout -- MyProject.slnx                  # Restore old solution file

Post-Rollback Verification

After rolling back, verify everything works:

# Restore packages
dotnet restore

# Build
dotnet build

# Run tests
dotnet test

# Verify the old CLI generation still works
atc-rest-api-generator generate server -s api.yaml -o src/MyProject.Api.Generated

When NOT to Roll Back

If the migration succeeded but you see build errors:

  1. Check the Troubleshooting section below first β€” most errors are namespace mismatches
  2. Run dotnet clean && dotnet build to clear stale caches
  3. Check that all AdditionalFiles entries in .csproj are correct

πŸ’‘ Tip: Always migrate on a dedicated branch (git checkout -b migration/source-generator). This makes rollback trivial β€” just switch back to your main branch.


πŸ”§ Troubleshooting

❌ "Target directory already exists"

If you see this error, it means the migration was partially completed before. Either:

  • Delete the target directory and re-run migration
  • Or manually complete the migration

⚠️ Build errors after migration

  1. Check that all namespace references are updated
  2. Verify the OpenAPI spec file path in .csproj is correct
  3. Ensure marker files are included as AdditionalFiles
  4. Clean and rebuild: dotnet clean && dotnet build

πŸ”€ Parameter name mismatches

If handler code has errors like "Property 'Continuation' not found", the parameter name migration may not have caught all usages. Search for the old name and update manually:

# Find remaining usages
grep -r "parameters.Continuation" src/

πŸ†• Client Testability Additions

These changes affect the C# client output. Almost all of them are additive β€” no generated type was renamed or removed, so existing code keeps compiling as-is. The one exception is the new ATC_API_SCH021 warning, which can break the build of a project that treats warnings as errors β€” see Spec-declared ProblemDetails is now reported.

TypedClient β€” generated I{ClientName} interface

The TypedClient mode now emits an interface alongside the client, in the same namespace, declaring every public operation:

public interface IMyApiClient
{
	Task<MeteringPointsApiResponse> GetMeteringPointsAsync(
		GetMeteringPointsParameters parameters,
		CancellationToken cancellationToken = default);
}

public sealed class MyApiClient : IMyApiClient { /* ... */ }

There is no opt-in flag β€” the interface is always generated.

What to do: change constructor parameters and DI resolutions from MyApiClient to IMyApiClient so the client can be mocked in tests. This is optional but recommended.

- public sealed class MeteringPointReportService(MyApiClient client)
+ public sealed class MeteringPointReportService(IMyApiClient client)

When Microsoft.Extensions.Http is referenced, an Add{ClientName}() extension is also emitted and registers the interface:

services.AddMyApiClient(client => client.BaseAddress = new Uri("https://api.example.com"));

var client = provider.GetRequiredService<IMyApiClient>();

ℹ️ The generated client has two public constructors, so it is registered via an explicit typed-client factory rather than AddHttpClient<TInterface, TImplementation>. If you previously hand-rolled AddHttpClient<IMyApiClient, MyApiClient>(), replace it with the generated AddMyApiClient() β€” the manual form fails at resolve time, not compile time.

EndpointPerOperation β€” generated result factories

Every {Operation}EndpointResult now exposes static factory methods, one per documented response:

GetMeteringPointsEndpointResult.Ok(content);            // 200 + body
GetMeteringPointsEndpointResult.Unauthorized(problem);  // 401 + body
GetMeteringPointsEndpointResult.Forbidden();            // 403, no body

What to do: if you wrote a local EndpointResultFactory helper to make results in tests, you can delete it and call the generated factories instead.

- var ok = EndpointResultFactory.Ok(response);
+ var ok = GetMeteringPointsEndpointResult.Ok(response);

EndpointPerOperation β€” result classes are now partial

Each generated {Operation}EndpointResult is declared partial instead of sealed, so you can add members to it from your own code without wrapping it:

// Your file β€” same namespace as the generated result
public partial class GetMeteringPointsEndpointResult
{
	public int MeteringPointCount
		=> OkContent?.Result?.Count ?? 0;
}

This needs no marker-file flag. It is unrelated to generatePartialModels, which controls the models only and remains opt-in and off by default.

What to do: nothing. Removing sealed is source- and binary-compatible; the only visible effect is that the type can now be derived from and extended.

ℹ️ The generated typed client is still sealed. Depend on its generated I{ClientName} interface rather than deriving from the class.

EndpointPerOperation β€” spec-declared ProblemDetails is now reported

In EndpointPerOperation mode the generator emits its own ProblemDetails and ValidationProblemDetails. If your specification also declares a schema with one of those names, that schema was previously materialized into {project}.Generated.{Segment}.Models and then never used β€” C# resolves the unqualified name against the enclosing namespace first, so the built-in always won and the spec type was silently discarded.

The spec-declared schema is now skipped rather than emitted, and the override is reported as ATC_API_SCH021 (see Analyzer Rules).

What to do: if you own the specification, rename the schema. If the spec is a third party's and your project builds with TreatWarningsAsErrors, suppress the rule:

<PropertyGroup>
  <!-- Third-party spec declares ProblemDetails; the built-in type is used instead. -->
  <NoWarn>$(NoWarn);ATC_API_SCH021</NoWarn>
</PropertyGroup>

Switching the client to a TypedClient mode is the other option: that mode has no built-in ProblemDetails, so a spec-declared one is generated and used.

ℹ️ This also removes an ambiguity that earlier versions could produce. With only one ProblemDetails in scope, call sites no longer need to fully qualify it.

See Working with C# Client Testing for the full testing guidance.

πŸ“š See Also

🏠 Home

πŸ’Ό Why This Tool?

πŸ“– Getting Started

βš™οΈ Features

🌐 Frontend

πŸ“‹ Reference


πŸ”— Resources

Clone this wiki locally