---
name: step-04-backend
description: Generate backend code using MCP scaffold_extension
prev_step: steps/step-03-roles.md
next_step: steps/step-05-frontend.md
---

# Step 4: Backend Generation

## MANDATORY EXECUTION RULES

- ALWAYS use MCP `scaffold_extension` tool - NEVER use templates
- ALWAYS generate Entity + Service + Controller as a unit
- NEVER skip controller generation - API is required
- YOU ARE AN ORCHESTRATOR calling MCP, not a generator

## YOUR TASK

Call the SmartStack MCP `scaffold_extension` tool with type "feature" to generate:
1. Domain Entity with EF Configuration
2. Application Service (Interface + Implementation)
3. API Controller with NavRoute attribute
4. DTOs (Response, Create, Update)

---

## AVAILABLE STATE

From previous steps:

| Variable | Description |
|----------|-------------|
| `{level}` | context, application, module, or section |
| `{code}` | kebab-case identifier |
| `{full_path}` | Complete navigation path (navRoute) |
| `{labels}` | Object with fr, en, it, de |

---

## EXECUTION SEQUENCE

### 1. Derive Entity Name

Convert `{code}` to PascalCase for entity name:

```
code: "products"        → entityName: "Product"
code: "order-items"     → entityName: "OrderItem"
code: "user-profiles"   → entityName: "UserProfile"
```

### 2. Determine Table Prefix and Controller Folder

Based on navigation context:

| Context | Table Prefix | Controller Folder |
|---------|--------|-------------------|
| platform.administration | `auth_` or `cfg_` | `Admin` |
| platform.support | `support_` | `Support` |
| business.* | `ref_` or domain-specific | `Business` |
| personal.* | `usr_` | `User` |

Store: `{controller_folder}` = the Controller Folder from the table above.

### 3. Call MCP scaffold_extension

```
Tool: mcp__smartstack__scaffold_extension
Args:
  type: "feature"
  name: "{entityName}"  # PascalCase
  options:
    navRoute: "{full_path}"
    tablePrefix: "{prefix}"
    schema: "core"
    withDtos: true
    withValidation: true
    withRepository: false  # Use service pattern
    skipComponent: true    # Frontend in next step
    dryRun: false
```

### 4. Parse MCP Response

The tool generates (paths organized by navRoute hierarchy `{context}.{application}.{module}`):
- `Domain/{Context}/{Application}/{Module}/{EntityName}.cs` - Entity with BaseEntity
- `Infrastructure/Persistence/Configurations/{Module}/{EntityName}Configuration.cs` - EF Config
- `Application/Common/Interfaces/I{EntityName}Service.cs` - Service Interface
- `Infrastructure/Services/{Module}/{EntityName}Service.cs` - Service Implementation
- `Api/Controllers/{controller_folder}/{Application}/{EntityName}Controller.cs` - REST Controller
- `Application/{Context}/{Application}/{Module}/DTOs/{EntityName}ResponseDto.cs` - Response DTO
- `Application/{Context}/{Application}/{Module}/DTOs/Create{EntityName}Dto.cs` - Create request
- `Application/{Context}/{Application}/{Module}/DTOs/Update{EntityName}Dto.cs` - Update request

### 5. Present Output to User

```markdown
## Backend Code Generated

### Domain Layer
- `Domain/{Context}/{Application}/{Module}/{EntityName}.cs`

### Infrastructure Layer
- `Persistence/Configurations/{Module}/{EntityName}Configuration.cs`
- `Services/{Module}/{EntityName}Service.cs`

### Application Layer
- `Common/Interfaces/I{EntityName}Service.cs`
- `{Context}/{Application}/{Module}/DTOs/{EntityName}ResponseDto.cs`
- `{Context}/{Application}/{Module}/DTOs/Create{EntityName}Dto.cs`
- `{Context}/{Application}/{Module}/DTOs/Update{EntityName}Dto.cs`

### API Layer
- `Controllers/{controller_folder}/{Application}/{EntityName}Controller.cs`
  - NavRoute: `{full_path}`
  - Endpoints:
    - GET /api/{code} - List all
    - GET /api/{code}/{id} - Get by ID
    - POST /api/{code} - Create
    - PUT /api/{code}/{id} - Update
    - DELETE /api/{code}/{id} - Delete

### Next Steps
1. Add DbSet to ICoreDbContext: `public DbSet<{EntityName}> {EntityName}s => Set<{EntityName}>();`
2. Register service in DI: `services.AddScoped<I{EntityName}Service, {EntityName}Service>();`
3. Run: `dotnet ef migrations add core_vX.X.X_XXX_Add{EntityName}`
```

### 6. Entity Seeding (Optional)

> Ask the user if they want to seed initial data for the entity.
> This creates runtime seed data (applied at startup via DevDataSeeder), following the same
> architecture as core (SeedData provider pattern).

```yaml
questions:
  - header: "Seed Data"
    question: "Would you like to generate initial seed data for {EntityName}?"
    options:
      - label: "Yes - Generate SeedData provider"
        description: "Creates {EntityName}SeedData.cs + registers in DevDataSeeder (same pattern as core)"
      - label: "No - Skip seeding"
        description: "Entity starts empty (can add seed data later)"
    multiSelect: false
```

#### If User Selects YES:

**Reference:** See `templates-seed.md` section "TEMPLATE: ENTITY SEED DATA (SeedData Provider)"

**Generate the following files:**

1. **`Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs`**

   Follow the SmartStack.app pattern (same architecture as core):
   - Static class with `internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()`
   - Use deterministic GUIDs (NEVER `Guid.NewGuid()`)
   - Include 3-5 sample entities with varied, realistic data
   - Internal record class `{EntityName}SeedItem` with all entity properties
   - For multi-tenant entities: organize by tenant using `GetTenant1{EntityName}s()` helper methods
   - Reference existing tenant/user IDs from `TenantSeedData`/`UserSeedData` for foreign keys

   ```csharp
   // Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs

   public static class {EntityName}SeedData
   {
       public static readonly Guid Sample1Id = Guid.Parse("...");
       public static readonly Guid Sample2Id = Guid.Parse("...");
       public static readonly Guid Sample3Id = Guid.Parse("...");

       internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
       {
           return new[]
           {
               new {EntityName}SeedItem { Id = Sample1Id, /* ... */ },
               new {EntityName}SeedItem { Id = Sample2Id, /* ... */ },
               new {EntityName}SeedItem { Id = Sample3Id, /* ... */ }
           };
       }
   }

   internal class {EntityName}SeedItem
   {
       public Guid Id { get; init; }
       // ... all required entity properties
   }
   ```

2. **Update `Infrastructure/Persistence/Seeding/DevDataSeeder.cs`**
   - Add `using` for the new SeedData namespace
   - Add `await Seed{EntityName}sAsync(cancellationToken);` in `SeedAsync()` method
   - Add private seeding method:

   ```csharp
   private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
   {
       _logger.LogInformation("Seeding demo {EntityName}s...");

       var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
       if (existingCount > 0)
       {
           _logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
           return;
       }

       var createdCount = 0;
       foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
       {
           var entity = {EntityName}.Create(/* map seedItem properties */);
           typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
           _context.{EntityName}s.Add(entity);
           createdCount++;
       }

       if (createdCount > 0)
           await _context.SaveChangesAsync(cancellationToken);

       _logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
   }
   ```

#### If User Selects NO:

Skip this section and proceed to storing entity info.

```markdown
> Skipping entity seed data. You can generate it later using the SeedData provider pattern
> documented in templates-seed.md.
```

### 7. Store Entity Info

Store entity information for frontend generation:

```
{entity_name} = "{EntityName}"
{entity_code} = "{code}"
{api_route} = "/api/{code}"
{has_seed_data} = true/false  // Whether SeedData provider was generated
```

---

## CONTROLLER NAVROUTE AND FOLDER HIERARCHY

### Controller File Location

Controllers are organized by **context short name** and **application** (matching SmartStack.app pattern):

```
Api/Controllers/
├── Admin/                     ← platform.administration
│   ├── {Application}/         ← Application sub-folder (e.g., AI/, Communications/, Tenants/)
│   │   └── {EntityName}Controller.cs
│   └── {EntityName}Controller.cs  ← Direct controllers (no application sub-folder)
├── Business/                  ← business.*
│   └── {EntityName}Controller.cs
├── Support/                   ← platform.support
│   └── {EntityName}Controller.cs
├── User/                      ← personal.*
│   └── {EntityName}Controller.cs
└── {SharedControllers}.cs     ← Root-level (Auth, Config, Navigation, etc.)
```

### Context-to-Folder Mapping

| NavRoute Context | Controller Folder | Example |
|-----------------|-------------------|---------|
| `platform.administration` | `Admin` | `Controllers/Admin/TenantsController.cs` |
| `platform.administration.{app}` | `Admin/{App}` | `Controllers/Admin/AI/AiPromptsController.cs` |
| `platform.support` | `Support` | `Controllers/Support/TicketsController.cs` |
| `business.*` | `Business` | `Controllers/Business/MyTicketsController.cs` |
| `personal.*` | `User` | `Controllers/User/PreferencesController.cs` |

### Controller Attributes

The generated controller uses NavRoute attribute:

```csharp
[NavRoute("{full_path}")]
[ApiController]
[Authorize]
public class {EntityName}Controller : ControllerBase
{
    [HttpGet]
    [RequirePermission(Permissions.{Context}.{Application}.{Module}.Read)]
    public async Task<ActionResult<IEnumerable<{EntityName}Dto>>> GetAll() { ... }

    // ... other CRUD methods
}
```

This ensures:
- API route is derived from navigation path
- Permissions use the constants from Permissions.cs
- Frontend can discover API path from NavRoute registry
- Controller file is in the correct context/application folder

---

## MCP RESPONSE HANDLING

### Success Case

If MCP returns successfully:
- Display all generated files
- Show DbSet and DI registration instructions
- Ask about entity seeding (section 6)
- Store entity info for frontend
- Proceed to step-05-frontend.md

### Error Case

If MCP call fails:
- Display error message
- Suggest checking entity name format
- Do NOT proceed automatically

---

## POST-GENERATION VERIFICATION (MANDATORY)

**Before proceeding to step-05, run these checks on ALL generated backend files:**

### Check 1: Backend Build (BLOCKING)

```bash
dotnet build {SolutionName}.sln --no-restore
```

IF build fails:
- Read the error output carefully
- Fix compilation errors in generated files (common: missing `using`, wrong namespace, type mismatch)
- Re-run build until it succeeds
- DO NOT proceed to step-05 until the backend builds successfully

### Check 2: DbSet Registration (BLOCKING)

Verify the entity DbSet is registered in the DbContext interface:

```
Search for: DbSet<{EntityName}>
In: **/I{DbContextName}.cs or **/{DbContextName}.cs
```

IF NOT found: Add the DbSet declaration immediately:
```csharp
public DbSet<{EntityName}> {EntityName}s => Set<{EntityName}>();
```

### Check 3: DI Registration (BLOCKING)

Verify the service is registered in the DI container:

```
Search for: I{EntityName}Service
In: **/DependencyInjection.cs
```

IF NOT found: Add the service registration immediately:
```csharp
services.AddScoped<I{EntityName}Service, {EntityName}Service>();
```

### Check 4: RequirePermission Attributes (BLOCKING)

Verify ALL controller actions have `[RequirePermission]` attributes:

```
Search for: [HttpGet], [HttpPost], [HttpPut], [HttpDelete]
In: **/Controllers/**/{EntityName}Controller.cs
```

Each HTTP action MUST have a corresponding `[RequirePermission(Permissions.{Context}.{Application}.{Module}.{Action})]`.
IF any action is missing the attribute: Add it immediately.

### Check 5: Entity Configuration (BLOCKING)

Verify the EF Core configuration file exists and has correct structure:

```
Search for: {EntityName}Configuration
In: **/Persistence/Configurations/**/*Configuration.cs
```

Verify:
- `builder.ToTable("{table_prefix}{EntityName}s")` uses the correct table prefix
- `builder.HasKey(e => e.Id)` is present
- `builder.HasIndex` is defined for unique fields (e.g., Code)

IF configuration is incomplete: Fix it before continuing.

### Check 5b: Seeding Infrastructure (BLOCKING - first entity only)

**If this is the FIRST entity created in this project (no existing DevDataSeeder):**

Scaffold the full seeding infrastructure following the SmartStack.app architecture:

1. **Create `Infrastructure/Persistence/Seeding/Data/SeedConstants.cs`**

   ```csharp
   namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;

   /// <summary>
   /// Shared constants for seed data across all modules.
   /// </summary>
   public static class SeedConstants
   {
       public static readonly DateTime SeedDate = new(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
   }
   ```

2. **Create `Infrastructure/Persistence/Seeding/DevDataSeeder.cs`**

   ```csharp
   using Microsoft.EntityFrameworkCore;
   using Microsoft.Extensions.Logging;
   using SmartStack.Application.Common.Interfaces;

   namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;

   /// <summary>
   /// Seeds development/demo data at application startup.
   /// Only runs when EnableDevSeeding = true.
   /// </summary>
   public class DevDataSeeder : IDevDataSeeder
   {
       private readonly ExtensionsDbContext _context;
       private readonly ILogger<DevDataSeeder> _logger;

       public DevDataSeeder(ExtensionsDbContext context, ILogger<DevDataSeeder> logger)
       {
           _context = context;
           _logger = logger;
       }

       public async Task SeedAsync(CancellationToken cancellationToken = default)
       {
           _logger.LogInformation("Starting development data seeding...");
           // Add Seed{Entity}sAsync calls here as entities are created
           _logger.LogInformation("Development data seeding complete.");
       }
   }
   ```

3. **Register DevDataSeeder in `Infrastructure/DependencyInjection.cs`:**
   ```csharp
   services.AddScoped<IDevDataSeeder, DevDataSeeder>();
   ```

**If DevDataSeeder already exists:** Skip this check.

### Check 5c: SqlObjects Directory (BLOCKING - first entity only)

**If this is the FIRST entity in the project (no existing SqlObjectHelper):**

Scaffold the SQL objects infrastructure:

1. **Create `Infrastructure/Persistence/SqlObjects/SqlObjectHelper.cs`**

   ```csharp
   using System.Reflection;
   using Microsoft.EntityFrameworkCore.Migrations;

   namespace {BaseNamespace}.Infrastructure.Persistence.SqlObjects;

   /// <summary>
   /// Helper for applying SQL objects (Functions, Views, Stored Procedures) from embedded resources.
   /// SQL files use CREATE OR ALTER for idempotency — safe to re-run on any migration or squash.
   /// </summary>
   public static class SqlObjectHelper
   {
       private static readonly Assembly Assembly = typeof(SqlObjectHelper).Assembly;
       private const string ResourcePrefix = "{BaseNamespace}.Infrastructure.Persistence.SqlObjects.";

       public static void ApplyAll(MigrationBuilder migrationBuilder)
       {
           ApplyAllFunctions(migrationBuilder);
       }

       public static void ApplyAllFunctions(MigrationBuilder migrationBuilder)
       {
           ApplyCategory(migrationBuilder, "Functions");
       }

       public static void ApplyOne(MigrationBuilder migrationBuilder, string resourceName)
       {
           var sql = ReadSqlObject(resourceName);
           migrationBuilder.Sql(sql);
       }

       public static string ReadSqlObject(string resourceName)
       {
           var fullName = ResourcePrefix + resourceName;
           using var stream = Assembly.GetManifestResourceStream(fullName)
               ?? throw new InvalidOperationException(
                   $"SQL object resource '{fullName}' not found. " +
                   $"Ensure the .sql file is marked as EmbeddedResource in the .csproj.");
           using var reader = new StreamReader(stream);
           return reader.ReadToEnd();
       }

       private static void ApplyCategory(MigrationBuilder migrationBuilder, string category)
       {
           var prefix = ResourcePrefix + category + ".";
           var resources = Assembly.GetManifestResourceNames()
               .Where(n => n.StartsWith(prefix, StringComparison.Ordinal)
                        && n.EndsWith(".sql", StringComparison.Ordinal))
               .OrderBy(n => n, StringComparer.Ordinal);

           foreach (var resource in resources)
           {
               using var stream = Assembly.GetManifestResourceStream(resource)!;
               using var reader = new StreamReader(stream);
               migrationBuilder.Sql(reader.ReadToEnd());
           }
       }
   }
   ```

2. **Create empty directory `Infrastructure/Persistence/SqlObjects/Functions/`**
   - Where `.sql` files for TVFs and scalar functions will be stored

3. **Ensure `.csproj` has EmbeddedResource glob for SQL files:**
   ```xml
   <ItemGroup>
     <EmbeddedResource Include="Persistence\SqlObjects\**\*.sql" />
   </ItemGroup>
   ```

**If SqlObjectHelper already exists:** Skip this check.

---

## SUCCESS METRICS

- MCP scaffold_extension called successfully
- Entity, Service, Controller generated
- DTOs generated
- NavRoute attribute included
- Entity seeding offered to user (SeedData.cs + DevDataSeeder if accepted)
- Entity info stored for frontend
- Post-generation verification passed (all 7 checks: 5 original + 5b Seeding + 5c SqlObjects)
- Proceeded to step-05-frontend.md

## FAILURE MODES

- MCP call failed (display error, stop)
- Invalid entity name (must be PascalCase)
- Invalid navRoute format

---

## NEXT STEP

After backend code is generated and entity seeding is handled,
proceed to `./step-05-frontend.md`
