Skip to main content
Chapi Assistant includes a powerful code generator that creates complete CRUD modules following Clean Architecture principles. It automatically detects your project structure and generates code for all layers.

Understanding Module Generation

When you generate a module, Chapi creates:
  • API Layer: Controllers or Endpoints (depending on architecture)
  • Application Layer: Use cases and business logic
  • Domain Layer: Entities and interfaces
  • Infrastructure Layer: Repository implementations

Architecture Detection

Chapi automatically detects your project’s architectural style:
Detected when API/Endpoints/ folder exists:
API/
└── Endpoints/
    └── YourModule/
        ├── Get.cs
        ├── Post.cs
        └── GetById.cs
Features:
  • FastEndpoints or Ardalis.Endpoints
  • Generic repository pattern
  • Scrutor auto-discovery for DI
  • No manual DI registration needed

Generating a Module

1

Invoke Module Generator

Use the module generation service:
await _moduleGeneratorService.GenerateModuleAsync(
    projectDirectory: "C:\\Projects\\MyApi",
    moduleName: "Product",
    dbName: "ApplicationDb"
);
Or via AI Assistant:
"Generate a Product module for ApplicationDb"
2

Module Name Processing

Chapi automatically formats your module name:
// Input: "product" or "Product"
// Output: "Product" (PascalCase)
moduleName = char.ToUpper(moduleName[0]) + moduleName[1..];
3

Automatic Structure Creation

Chapi creates folders for all layers:
// API Layer
Directory.CreateDirectory(Path.Combine(
    apiProjectPath, apiSubFolder, moduleName
));

// Application Layer
Directory.CreateDirectory(Path.Combine(
    projectDirectory, "Application", moduleName
));

// Domain Layer
Directory.CreateDirectory(Path.Combine(
    projectDirectory, "Domain", moduleName
));

// Infrastructure Layer
Directory.CreateDirectory(Path.Combine(
    projectDirectory, "Infrastructure", dbName, "Repositories", moduleName
));
4

Default Operations Generated

Three operations are created by default:
  • Get: List all entities
  • Post: Create new entity
  • GetById: Retrieve single entity by ID
Each operation includes:
  • Request/Response DTOs
  • Validation logic
  • Repository method
  • API endpoint

Generated Code Structure

API Layer

// API/Endpoints/Product/Get.cs
public class Get : EndpointBaseAsync
    .WithoutRequest
    .WithActionResult<List<ProductResponse>>
{
    private readonly IProductRepository _repository;

    public Get(IProductRepository repository)
    {
        _repository = repository;
    }

    [HttpGet("/api/products")]
    public override async Task<ActionResult<List<ProductResponse>>> HandleAsync(
        CancellationToken ct = default)
    {
        var products = await _repository.GetAllAsync();
        return Ok(products);
    }
}

Application Layer

// Application/Product/GetProductsUseCase.cs
public class GetProductsUseCase
{
    private readonly IProductRepository _repository;

    public GetProductsUseCase(IProductRepository repository)
    {
        _repository = repository;
    }

    public async Task<Result<List<ProductResponse>>> ExecuteAsync()
    {
        try
        {
            var products = await _repository.GetAllAsync();
            return Result<List<ProductResponse>>.Success(products);
        }
        catch (Exception ex)
        {
            return Result<List<ProductResponse>>.Fail(ex.Message);
        }
    }
}

Domain Layer

// Domain/Product/Product.cs
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public DateTime CreatedAt { get; set; }
}

// Domain/Product/IProductRepository.cs
public interface IProductRepository
{
    Task<List<Product>> GetAllAsync();
    Task<Product> GetByIdAsync(int id);
    Task<int> CreateAsync(CreateProductRequest request);
}

Infrastructure Layer

// Infrastructure/ApplicationDb/Repositories/Product/ProductRepository.cs
public class ProductRepository : IProductRepository
{
    private readonly ApplicationDbContext _context;

    public ProductRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<List<Product>> GetAllAsync()
    {
        return await _context.Products.ToListAsync();
    }

    public async Task<Product> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id);
    }

    public async Task<int> CreateAsync(CreateProductRequest request)
    {
        var product = new Product
        {
            Name = request.Name,
            Price = request.Price,
            CreatedAt = DateTime.UtcNow
        };

        _context.Products.Add(product);
        await _context.SaveChangesAsync();

        return product.Id;
    }
}

Dependency Injection

Ardalis Architecture

With Ardalis architecture, Scrutor automatically discovers and registers services:
// No manual registration needed!
// Scrutor scans assemblies and registers by convention

Classic Architecture

Chapi automatically updates DependencyInjection.cs:
// API/Config/DependencyInjection.cs
public static class DependencyInjection
{
    public static IServiceCollection AddApplicationServices(
        this IServiceCollection services)
    {
        // Auto-generated registration
        services.AddScoped<IProductRepository, ProductRepository>();
        services.AddScoped<GetProductsUseCase>();
        services.AddScoped<CreateProductUseCase>();
        services.AddScoped<GetProductByIdUseCase>();

        return services;
    }
}

Rollback Support

Chapi includes automatic rollback capabilities:
var rollbackEntry = RollbackManager.StartTransaction(
    moduleName, moduleName, operation
);

try
{
    // Generate code...
    RollbackManager.CommitTransaction(rollbackEntry);
}
catch (Exception ex)
{
    RollbackManager.RollbackTransaction(rollbackEntry);
    throw;
}
If generation fails, all changes are automatically reverted.

Implementation Details

The module generator source code from ModuleGeneratorService.cs:
public async Task GenerateModuleAsync(
    string projectDirectory, 
    string moduleName, 
    string dbName)
{
    moduleName = char.ToUpper(moduleName[0]) + moduleName[1..];
    
    // Detect architecture
    string apiProjectPath = FindApiDirectory.GetDirectory(projectDirectory);
    bool isArdalis = Directory.Exists(
        Path.Combine(apiProjectPath, "Endpoints")
    );
    string apiSubFolder = isArdalis ? "Endpoints" : "Controllers";

    // Create directories
    string apiPath = Path.Combine(apiProjectPath, apiSubFolder, moduleName);
    string appPath = Path.Combine(projectDirectory, "Application", moduleName);
    string domainPath = Path.Combine(projectDirectory, "Domain", moduleName);
    string infraPath = Path.Combine(
        projectDirectory, "Infrastructure", dbName, "Repositories", moduleName
    );

    // Generate operations
    var defaultOperations = new[] { "Get", "Post", "GetById" };
    
    foreach (var operation in defaultOperations)
    {
        if (isArdalis)
        {
            AddApiEndpointMethod.Add(
                apiPath, moduleName, operation, moduleName, rollbackEntry
            );
        }
        else
        {
            AddApiControllerMethod.Add(
                apiPath, moduleName, operation, moduleName, rollbackEntry
            );
        }

        AddApplicationMethod.Add(
            appPath, moduleName, operation, moduleName, rollbackEntry
        );
        await AddDomainMethod.Add(
            domainPath, moduleName, operation, moduleName, rollbackEntry
        );
        await AddInfrastructureMethod.Add(
            infraPath, moduleName, dbName, operation, moduleName, rollbackEntry
        );
    }
}

Best Practices

  • Use singular nouns (e.g., Product, not Products)
  • Use PascalCase
  • Choose domain-driven names
  • Keep names concise but descriptive
  • Ensure the database name matches your DbContext
  • Verify the Infrastructure folder structure
  • Check that DbContext is properly configured
After generating the default operations, you can:
  • Add custom methods to repositories
  • Create specialized use cases
  • Extend endpoints with additional logic
After generation:
  • Review generated DTOs
  • Add validation attributes
  • Customize business logic
  • Add unit tests

Troubleshooting

API Directory Not Found: Ensure your project structure follows Clean Architecture conventions with an API project.
Database Name Mismatch: The dbName parameter must match the folder name in Infrastructure/.
Generated code uses Roslyn for C# code manipulation, ensuring proper formatting and syntax.

Next Steps

Git Workflow

Commit your generated modules with Git integration

AI Setup

Use AI to enhance your generated code

Build docs developers (and LLMs) love