-
Notifications
You must be signed in to change notification settings - Fork 1
Working with How To Guides
Practical recipes for common tasks when working with the ATC REST API Source Generator.
The generator creates handler interfaces (e.g., IListPetsHandler). You implement them, and the generated endpoints call your implementations. Here's how to test them.
public class ListPetsHandler : IListPetsHandler
{
private readonly IPetRepository repository;
public ListPetsHandler(IPetRepository repository)
{
this.repository = repository;
}
public async Task<ListPetsResult> ExecuteAsync(
ListPetsParameters parameters,
CancellationToken cancellationToken)
{
var pets = await repository.GetAllAsync(parameters.Limit, cancellationToken);
return ListPetsResult.Ok(pets);
}
}public class ListPetsHandlerTests
{
[Fact]
public async Task ExecuteAsync_ReturnsOk_WithPets()
{
// Arrange
var mockRepo = Substitute.For<IPetRepository>();
mockRepo.GetAllAsync(10, Arg.Any<CancellationToken>())
.Returns(new List<Pet> { new("Buddy", "Dog") });
var handler = new ListPetsHandler(mockRepo);
var parameters = new ListPetsParameters(Limit: 10);
// Act
var result = await handler.ExecuteAsync(parameters, CancellationToken.None);
// Assert
result.StatusCode.Should().Be(200);
}
}- Test the handler directly β not through HTTP. The handler is a plain class with no HTTP dependencies.
- Use
NSubstituteorMoqfor mocking dependencies (repositories, services). - The generated
*Parametersrecords are simple to construct β just use the constructor. - The generated
*Resulttypes have static factory methods (Ok(),NotFound(),BadRequest()) for assertions.
The generator auto-discovers FluentValidation validators and registers them in the DI container.
Create a validator class in your project that validates a generated parameter type:
using FluentValidation;
public class CreatePetParametersValidator : AbstractValidator<CreatePetParameters>
{
public CreatePetParametersValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(100);
RuleFor(x => x.Tag)
.MaximumLength(50)
.When(x => x.Tag is not null);
}
}dotnet add package FluentValidation.DependencyInjectionExtensionsThe generator scans your assembly for validators and generates ServiceCollectionExtensions that registers them:
// Generated automatically
services.AddSingleton<IValidator<CreatePetParameters>, CreatePetParametersValidator>();- The Domain Generator (
ApiServerDomainGenerator) scans your assembly at compile time - It finds all types implementing
IValidator<T>whereTmatches a generated parameter type - Registration code is generated in
ServiceCollectionExtensions.g.cs
- Name validators with the
Validatorsuffix by convention (e.g.,CreatePetParametersValidator) - Validators are registered as singletons β don't inject scoped services into them
- The generated endpoints automatically apply validation before calling your handler
Set up GitHub Actions to automatically regenerate clients when the OpenAPI spec changes.
The project includes a ready-to-use template at docs/github-action-regenerate.yml. Copy it to your repository:
cp docs/github-action-regenerate.yml .github/workflows/regenerate-client.yml- Triggers on push to
mainwhen.yaml/.ymlfiles change - Installs the
atc-rest-api-genCLI tool - Regenerates the TypeScript client
- Optionally runs
npx tsc --noEmitto verify the output compiles - Opens a PR with the regenerated files
Edit the workflow file to match your project:
- name: Regenerate TypeScript client
run: |
atc-rest-api-gen generate client-typescript \
-s path/to/your/api.yaml \
-o src/api \
--hooks ReactQuery \
--zodSince the C# server uses a Roslyn Source Generator, regeneration happens automatically on dotnet build. No GitHub Action needed β just commit the updated YAML and build.
For local development, add a script to package.json:
{
"scripts": {
"generate-api": "dotnet run --project path/to/Cli.csproj -- generate client-typescript -s api.yaml -o src/api --hooks ReactQuery"
}
}Use generatePartialModels to extend generated records with custom logic without modifying generated files.
In your .atc-rest-api-client or .atc-rest-api-server marker file:
{
"generatePartialModels": true
}The generator produces partial records:
// Auto-generated β do not edit
public partial record Pet(
long Id,
string Name,
string? Tag);Create a file in your project with the matching namespace and partial type:
namespace MyApi.Generated.Models;
public partial record Pet
{
/// <summary>
/// Display-friendly name with tag.
/// </summary>
public string DisplayName => Tag is not null ? $"{Name} ({Tag})" : Name;
/// <summary>
/// Validates business rules beyond what the API contract covers.
/// </summary>
public bool IsValid() => !string.IsNullOrWhiteSpace(Name) && Id > 0;
}| Extension | Example |
|---|---|
| Computed properties |
DisplayName, FullAddress, IsExpired
|
| Methods |
Validate(), ToDto(), Clone()
|
| Operator overloads | Equality, comparison |
| Interface implementations |
IComparable<T>, custom interfaces |
| Nested types | Companion builders, mappers |
- Keep partial extensions in a separate folder (e.g.,
Models/Extensions/) so they don't get confused with generated files - Generated files have the
// <auto-generated />header β your extensions don't - If you don't need extensions, leave
generatePartialModelsoff (default) for simpler code - The
x-implementsextension on schemas can also add interface implementations without partial classes
generatePartialModels covers models. In EndpointPerOperation client mode, the generated {Operation}EndpointResult classes are partial unconditionally β no flag needed:
// Your file β same namespace as the generated result
public partial class GetPetByIdEndpointResult
{
/// <summary>
/// True when the pet was found and carries a tag.
/// </summary>
public bool IsTaggedPet => IsOk && OkContent?.Tag is not null;
}The generated typed client class stays sealed; depend on its generated I{ClientName} interface instead of deriving from it.
By default, handler scaffolds have no constructor. Enable injectLogger to get structured logging out of the box.
In your .atc-rest-api-server-handlers marker file:
{
"injectLogger": true
}With injectLogger: true, every handler scaffold gets:
public sealed class GetPetByIdHandler : IGetPetByIdHandler
{
private readonly ILogger<GetPetByIdHandler> logger;
public GetPetByIdHandler(ILogger<GetPetByIdHandler> logger)
{
this.logger = logger;
}
public Task<GetPetByIdResult> ExecuteAsync(
GetPetByIdParameters parameters,
CancellationToken cancellationToken = default)
{
// TODO: Implement getPetById logic
throw new NotImplementedException("getPetById not implemented");
}
}You can then use logger directly in your implementation:
public async Task<GetPetByIdResult> ExecuteAsync(
GetPetByIdParameters parameters,
CancellationToken cancellationToken = default)
{
logger.LogInformation("Getting pet {PetId}", parameters.PetId);
var pet = await repository.GetByIdAsync(parameters.PetId, cancellationToken);
if (pet is null)
{
logger.LogWarning("Pet {PetId} not found", parameters.PetId);
return GetPetByIdResult.NotFound();
}
return GetPetByIdResult.Ok(pet);
}π‘ Note:
injectLoggeronly affects new handler files. Existing handlers are never overwritten β the Domain Generator skips files that already exist on disk.
- Working with OpenAPI β OpenAPI specification patterns
- Working with Security β Authentication and authorization
- Working with TypeScript Client β TypeScript client generation
- Development Notes β Contributing to the generator itself
π Home
- πΌ FAQ Business Value
- π Getting Started with Basic
- π οΈ Getting Started with CLI
- π Migration Guide
- β¬οΈ Upgrading to v2
- π Working with OpenAPI
- π³οΈ Working with Nullability
- π οΈ Working with CLI
- π How-To Guides
- π Working with Security
- π¦ Working with Rate Limiting
- π Working with Resilience
- ποΈ Working with Caching
- π’ Working with Versioning
- β Working with Validations
- π Working with Webhooks
- βοΈ Working with Aspire
- π£οΈ Working with Endpoint Definitions
- π Working with Multi-Part Specs
- π§ͺ Working with Code Coverage
- π Working with C# Client
- π§ͺ Working with C# Client Testing
- π¦ Working with TypeScript Client
- πͺ Showcase Demo
- π§ͺ Working with E2E Testing
- βοΈ Working with Configuration
- π Marker Files
- π API Reference
- π Analyzer Rules
- β FAQ and Troubleshooting
- πΊοΈ Roadmap
- π§ Development Notes
- π¦ GitHub Repository
- π₯ NuGet Package
- π Report Issues