CoreEx.CodeGen is a development-time code generation tool — it is never deployed at runtime. It reads a ref-data.yaml file and generates the complete reference-data layer (contract, controller, provider, repository, mapper — plus service and write endpoints for mutable entities) as .g.cs files.
Create a console project (e.g. MyApp.CodeGen) and add a Program.cs with one line:
await CoreEx.CodeGen.CodeGenConsole.Create().RunAsync(args);Place ref-data.yaml alongside Program.cs.
collectionSortOrder: Code # default sort for all reference data collections
entities:
- name: Status
idType: Int32 # Id property type; defaults to String. Must be one of: String, Guid, Int32, Int64.
properties:
- name: IsExternal
type: bool
- name: Country
- name: CurrencyRun the project (dotnet run) to regenerate all .g.cs files after changing the YAML.
| Output | Layer | What changes it |
|---|---|---|
{Name}.g.cs (Contracts project root) |
Contracts | ref-data.yaml entity/property config |
Controllers/ReferenceDataController.g.cs |
Api | ref-data.yaml route/entity config |
ReferenceDataProvider.g.cs (Application project root) — IReferenceDataProvider for the orchestrator |
Application | ref-data.yaml entity config |
Controllers/{Name}Controller.g.cs — mutable entities only |
Api | mutability / mutableAttribute |
Interfaces/IReferenceDataService.g.cs + ReferenceDataService.g.cs — only if any entity is mutable |
Application | mutability / validator |
Repositories/IReferenceDataRepository.g.cs |
Application | ref-data.yaml entity/repository config |
Repositories/ReferenceDataRepository.g.cs |
Infrastructure | ref-data.yaml repository config |
Mapping/{Name}Mapper.g.cs |
Infrastructure | ref-data.yaml property/mapping config |
Repositories/Mapping are the default dataRepositoriesPath/dataMappingPath folder names — both configurable in ref-data.yaml.
Entities are read-only by default (mutability: None). Set mutability per entity to opt in:
repository: EntityFramework
getNamed: true # Root; default false — emits GetNamedAsync on ReferenceDataController.
entities:
- name: Brand
mutability: CreateUpdateDelete # None (default) | CreateUpdate | CreateUpdateDelete
validator: BrandValidator # Optional; default ReferenceDataValidator<Brand>; needs a default constructor.
mutableAttribute: '[Authorize(Policy = "ReferenceDataAdmin")]' # Replaces the default [Authorize].
- name: Category # Read-only.None |
CreateUpdate |
CreateUpdateDelete |
|
|---|---|---|---|
List via ReferenceDataController (cached orchestrator) |
✅ | ✅ | ✅ |
{Name}Controller: GET {id}, POST (inactive), PUT, PATCH, POST {id}/activate, POST {id}/deactivate |
— | ✅ | ✅ |
DELETE {id} (204; active values rejected with 400 — deactivate first) |
— | — | ✅ |
IReferenceDataService/ReferenceDataService methods, repository write ops, bidirectional mapper |
— | ✅ | ✅ (+ delete) |
Events (created/updated/activated/deactivated .v1, deleted) + orchestrator cache invalidation |
— | ✅ | ✅ |
- Service and service interface are generated only when at least one entity is mutable.
- A mutable entity requires
repository: EntityFrameworkorCosmos— codegen throws otherwise (Noneis read-only only).CosmosusesCosmosDbReferenceData(duplicate codes rely on a/typeDiscriminator+/codeunique key on the container, which must also be registered withCosmosDbContainerOptions.WithReferenceDataOutboxEvent()so the co-located outbox events — which carry neither path — do not collide with each other) and the generated service re-gets the item after the transaction so the returnedETagis final. codeis immutable after create; create always yields an inactive item; activate/deactivate are no-ops (no event) if already in that state.- Mutable controllers default to
[Authorize]. SetmutableAttributeto a project-specific authorization policy/role, or to[AllowAnonymous]only when the write API is intentionally public; this replaces the default attribute. Ensure the API project'sGlobalUsing.csimportsMicrosoft.AspNetCore.Authorization. attribute(root/entity) decorates only the read-onlyReferenceDataController; usemutableAttributefor the write endpoints.- The host needs
IUnitOfWorkregistered; the generated service wraps each write and its outbox event in one transaction. - No usage/cascade check on delete or deactivate. Other tables usually store the code (e.g. Product.BrandCode), so deleting or deactivating a value still in use leaves that data silently invalid. This is the consumer's responsibility — use the PreCheckAsync hook on the generated ReferenceDataService to veto the operation.
- PreCheckAsync is a private Func<IReferenceData, EventAction, CancellationToken, Task> set from a hand-written partial class ReferenceDataService via partial void OnInitialization(). It is a lightweight allow/deny veto only — no side effects, cascades or other writes; if more is needed, hand-write the data/repository logic instead of stretching the hook. It runs for Activated/Deactivated/Deleted only (not create/update), before the transaction (so not atomic, by design), and one delegate serves all mutable entities — switch on type/action. See the README for an example.
- If the table lacks
Description/StartsOn/EndsOncolumns (EFIgnored), a non-null value for them is rejected on create/update with a 400 (not-supported) — add the columns if needed.
- Do not edit
*.g.csfiles — they are overwritten on every generation run. Editref-data.yamlor the Handlebars templates in theCoreEx.CodeGenpackage instead. - Do not set
mutabilityon an entity whoserepositoryis notEntityFrameworkorCosmos, and do not hand-write mutation endpoints/services that duplicate the generated ones — turn onmutabilityinstead. - Do not assume delete/deactivate protects referenced data — add the checks yourself in
PreCheckAsync(never in the.g.cs). - Do not add
CoreEx.CodeGenas a runtime dependency — it is a development tool only.
- README — full YAML schema, script structure, and template customisation reference.
- Tooling — how
*.CodeGenand*.Databaseprojects are used together in the sample solution, including run order and generated-file ownership. - Contracts layer — shows generated reference-data contracts (
[ReferenceData]) and howref-data.yamldrives the controller/service/repository layer.