C#/.NET CRUD Scaffolder Skill
This skill automates the creation of a full CRUD (Create, Read, Update, Delete) endpoint for a new entity in a C#/.NET application. It adheres to Clean Architecture principles and the patterns previously discussed (CQRS, Dapper, Unit of Work, FluentValidation).
Workflow
Follow these steps precisely to generate the code.
Step 1: Gather Entity Requirements
- •
Confirm Entity Name: Ask the user for the name of the entity in PascalCase (e.g.,
Product,CustomerOrder). - •
Confirm Properties: Ask the user for a list of properties for the entity. Each property must include:
- •C# Type: (e.g.,
string,int,decimal,DateTime,bool). - •Name: (in PascalCase, e.g.,
Name,Description,Price). - •Nullability: Indicate if the type is nullable (e.g.,
string?,int?). - •Validation Rules (Optional): Ask for basic validation rules like
required,maxLength,pattern.
Example User Input: "I want to create a
Productentity with these properties:- •
string Name(required, max 100) - •
string? Description(max 500) - •
decimal Price(required) - •
int Stock(required)"
- •C# Type: (e.g.,
Step 2: Derive Variables
From the entity name, derive the following variations to be used as placeholders:
- •
{{EntityName}}: The original name (e.g.,Product). - •
{{EntityNamePlural}}: The plural version (e.g.,Products). - •
{{EntityNameLowerCase}}: The camelCase version (e.g.,product). - •
{{EntityNamePluralLowerCase}}: The plural camelCase or all-lowercase version for routes (e.g.,products).
Step 3: Generate Code from Templates
For each file to be generated, you will load the corresponding template from the assets/ directory, process the placeholders, and generate the final code.
Placeholder Replacement Logic:
- •Simple Placeholders: Replace
{{EntityName}},{{EntityNamePlural}}, etc., with the derived variables. - •
{{Properties}}: Iterate through the user-defined property list. For each property, generate a line:public {{Type}} {{Name}} { get; set; }. - •
{{ValidationRules}}: Iterate through properties. GenerateFluentValidationrules.- •If
requiredandstring:RuleFor(x => x.{{Name}}).NotEmpty().WithMessage("{{Name}} is required."); - •If
maxLength:RuleFor(x => x.{{Name}}).MaximumLength({{length}}).WithMessage("{{Name}} cannot exceed {{length}} characters."); - •If
requiredand notstring:RuleFor(x => x.{{Name}}).NotEmpty().WithMessage("{{Name}} is required.");
- •If
- •
{{ColumnNames}}(for SQL INSERT): Generate a comma-separated list of property names (e.g.,Name, Description, Price, Stock). - •
{{AtColumnNames}}(for SQL INSERT VALUES): Generate a comma-separated list of@prefixed property names (e.g.,@Name, @Description, @Price, @Stock). - •
{{UpdateSetStatements}}(for SQL UPDATE): Generate a comma-separated list ofColumn = @Columnstatements (e.g.,Name = @Name, Description = @Description, Price = @Price). - •
{{MapRequestToCommandProperties}}: Generate property assignments from arequestobject to acommandobject. - •
{{MapCommandToEntityProperties}}: Generate property assignments from acommandobject to anentityobject. - •
{{MapEntityToResponseProperties}}: Generate property assignments from anentityobject to aresponseobject. - •
{{FilterConditions}}(for Paged Query): Generateifstatements for filtering based on query parameters. This might require asking the user which properties are filterable. For a v1, you can leave this blank or add a simple example.
Step 4: Write Generated Files
Using the project structure defined in our plan, construct the full file path for each generated file and use the write_file tool to save the content.
File Generation Plan:
- •Entity:
- •Template:
assets/templates/Entity.cs.tpl - •Output:
src/SeuProjeto.Domain/Entities/{{EntityName}}.cs
- •Template:
- •Repository Interface:
- •Template:
assets/templates/RepositoryInterface.cs.tpl - •Output:
src/SeuProjeto.Domain/Interfaces/Repositories/I{{EntityName}}Repository.cs
- •Template:
- •Create Command DTOs:
- •Template:
assets/templates/CreateRequest.cs.tpl - •Output:
src/SeuProjeto.Domain/Requests/Create{{EntityName}}Request.cs - •Template:
assets/templates/CreateCommand.cs.tpl - •Output:
src/SeuProjeto.Domain/Commands/Create{{EntityName}}Command.cs
- •Template:
- •Create Command Validator:
- •Template:
assets/templates/CreateValidator.cs.tpl - •Output:
src/SeuProjeto.Domain/Validators/Create{{EntityName}}CommandValidator.cs
- •Template:
- •Create Command Handler:
- •Template:
assets/templates/CreateCommandHandler.cs.tpl - •Output:
src/SeuProjeto.Domain/CommandHandlers/Create{{EntityName}}CommandHandler.cs
- •Template:
- •Update DTOs, Command, Validator, Handler: (Repeat for
Updatetemplates) - •Delete Command, Validator, Handler: (Repeat for
Deletetemplates) - •GetById Query, Handler, Response: (Repeat for
GetByIdandResponsetemplates) - •GetPaged Query, Handler: (Repeat for
GetPagedtemplates) - •Repository Implementation:
- •Template:
assets/templates/Repository.cs.tpl - •Output:
src/SeuProjeto.Infrastructure/Repositories/{{EntityName}}SqlServerRepository.cs
- •Template:
- •Controller:
- •Template:
assets/templates/Controller.cs.tpl - •Output:
src/SeuProjeto.Api/Controllers/{{EntityNamePlural}}Controller.cs
- •Template:
- •Common Files (Copy if they don't exist):
- •Copy files from
assets/commonto appropriate locations if they are not already present in the user's project.
- •Copy files from
Step 5: Final Instructions for User
After generating all files, inform the user of the final manual step:
"The CRUD endpoint for {{EntityName}} has been generated. The final step is to register the new services in your Dependency Injection container (e.g., in Program.cs or Startup.cs). Please add the following lines:
// For {{EntityName}}
services.AddScoped<I{{EntityName}}Repository, {{EntityName}}SqlServerRepository>();
services.AddScoped<Create{{EntityName}}CommandHandler>();
services.AddScoped<Update{{EntityName}}CommandHandler>();
services.AddScoped<Delete{{EntityName}}CommandHandler>();
services.AddScoped<Get{{EntityName}}ByIdQueryHandler>();
services.AddScoped<Get{{EntityNamePlural}}PagedQueryHandler>();
"