Skip to content

Template Metadata Composition

This document explains how ConnectSoft templates compose template metadata (template.json) from base template and specialized template overlays. It is written for architects and engineers who need to understand or create template extend files and the pack script behavior.

Template metadata follows the same composition pattern as code and documentation: base template provides canonical metadata, and specialized templates provide extend files that are merged at generation time.

Important

The final template.json is always generated from base + extend files. Never manually duplicate base parameters in specialized template repos. Use extend files to add or override metadata.

Goals

Template metadata composition is designed to achieve:

  • No Duplication - Base parameters are never duplicated in specialized templates
  • Composition - Final template.json is composed from base + one or more overlays
  • Flexibility - Specialized templates can override or extend base metadata
  • Recipe Support - Multiple overlays can be combined (Identity + Worker)
  • Maintainability - Base parameter changes propagate automatically

Base Template Folder Structure

The base template defines the canonical template metadata structure:

MicroserviceTemplate.Base/
└── template/
    ├── template.json          # Base template metadata
    ├── ide.host.json          # IDE-specific host configuration
    └── dotnetcli.host.json    # CLI-specific host configuration

Base template.json Structure

The base template.json defines:

Top-Level Fields:

  • name - Template display name
  • shortName - Template short name for dotnet new
  • description - Template description
  • tags - Template tags and classifications
  • author - Template author
  • classifications - Template classifications

Symbols (Parameters):

  • ServiceName - Name of the generated service
  • RootNamespace - Root namespace for generated code
  • UseMassTransit - Whether to use MassTransit messaging
  • UseNHibernate - Whether to use NHibernate persistence
  • PersistenceType - Type of persistence (SqlServer, PostgreSQL, etc.)
  • MessagingType - Type of messaging (MassTransit, NServiceBus, None)
  • Common infrastructure flags

Post-Actions:

  • Restore NuGet packages
  • Run build
  • Run tests (optional)
  • Setup git repository

Example Base template.json:

{
  "$schema": "http://json.schemastore.org/template",
  "author": "ConnectSoft",
  "classifications": ["ConnectSoft", "Microservice", "Clean Architecture"],
  "name": "ConnectSoft Microservice Base",
  "shortName": "cs-microservice-base",
  "description": "Base microservice template with Clean Architecture and DDD",
  "tags": {
    "language": "C#",
    "type": "project",
    "connectsoft-template": "microservice-base"
  },
  "symbols": {
    "ServiceName": {
      "type": "parameter",
      "datatype": "string",
      "defaultValue": "MyService",
      "description": "Name of the microservice",
      "displayName": "Service Name",
      "replaces": "ServiceName"
    },
    "RootNamespace": {
      "type": "parameter",
      "datatype": "string",
      "defaultValue": "MyCompany.MyService",
      "description": "Root namespace for generated code",
      "displayName": "Root Namespace",
      "replaces": "RootNamespace"
    },
    "UseMassTransit": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Enable MassTransit messaging",
      "displayName": "Use MassTransit"
    },
    "PersistenceType": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        { "choice": "SqlServer", "description": "SQL Server" },
        { "choice": "PostgreSQL", "description": "PostgreSQL" }
      ],
      "defaultValue": "SqlServer",
      "description": "Persistence technology",
      "displayName": "Persistence Type"
    }
  },
  "postActions": [
    {
      "id": "restore",
      "description": "Restore NuGet packages",
      "manualInstructions": [
        { "text": "Run 'dotnet restore' to restore packages" }
      ],
      "actionId": "210D431B-AF52-49D1-90D9-CCB93A37EFD7"
    },
    {
      "id": "build",
      "description": "Build the solution",
      "manualInstructions": [
        { "text": "Run 'dotnet build' to build the solution" }
      ],
      "actionId": "D396686C-DE45-4ED5-9664-E8E6BEF12E5D"
    }
  ],
  "primaryOutputs": [
    {
      "path": "src/Host/Host.csproj"
    }
  ]
}

Extend Files in Specialized Templates

Specialized templates define extend files that provide deltas (additions and overrides) to the base template.json.

Extend File Structure

Extend files use a structured format to specify what to override, add, or modify:

IdentityBackendTemplate/
└── template/
    ├── identity.template.extend.json    # Identity overlay metadata
    └── worker.template.extend.json      # Optional: Worker overlay metadata

Extend File Format

Extend files contain sections for different types of modifications:

{
  "identityOverrides": {
    // Override top-level fields from base template.json
  },
  "symbolOverrides": {
    // Override existing symbols (parameters) from base
  },
  "symbolAdds": {
    // Add new symbols (parameters) not in base
  },
  "postActionsAdds": [
    // Add additional post-actions
  ],
  "primaryOutputsAdds": [
    // Add additional primary outputs
  ]
}

Merge Rules

The pack script applies merge rules in a specific order to compose the final template.json.

Merge Process Flow

flowchart TD
    BASE[Load Base template.json]
    EXTEND1[Load identity.template.extend.json]
    EXTEND2[Load worker.template.extend.json]

    MERGE[Merge Process]

    STEP1[Apply identityOverrides]
    STEP2[Apply symbolOverrides]
    STEP3[Apply symbolAdds]
    STEP4[Apply postActionsAdds]
    STEP5[Apply primaryOutputsAdds]

    OUTPUT[Final template.json]

    BASE --> MERGE
    EXTEND1 --> MERGE
    EXTEND2 --> MERGE

    MERGE --> STEP1
    STEP1 --> STEP2
    STEP2 --> STEP3
    STEP3 --> STEP4
    STEP4 --> STEP5
    STEP5 --> OUTPUT

    style BASE fill:#BBDEFB
    style EXTEND1 fill:#C8E6C9
    style EXTEND2 fill:#FFF9C4
    style OUTPUT fill:#A5D6A7
Hold "Alt" / "Option" to enable pan & zoom

identityOverrides

Purpose: Override top-level fields from base template.json.

Fields that can be overridden:

  • name - Template display name
  • shortName - Template short name
  • description - Template description
  • tags - Template tags (merged, not replaced)
  • author - Template author
  • classifications - Template classifications (merged)

Example:

{
  "identityOverrides": {
    "name": "ConnectSoft Identity Backend Service",
    "shortName": "cs-identity-backend",
    "description": "Identity Backend built on ConnectSoft base microservice with user management, authentication, and authorization.",
    "tags": {
      "domain": "identity",
      "connectsoft-template": "identity-backend"
    },
    "classifications": ["ConnectSoft", "Microservice", "Identity", "Authentication"]
  }
}

Merge Behavior:

  • Simple fields (name, shortName, description) are replaced
  • Tags are merged (base tags + identity tags)
  • Classifications are merged (base + identity)

symbolOverrides

Purpose: Override existing symbols (parameters) from base template.json.

What can be overridden:

  • defaultValue - Default value for the parameter
  • description - Parameter description
  • displayName - Display name for the parameter
  • choices - Available choices (for choice parameters)

Example:

{
  "symbolOverrides": {
    "ServiceName": {
      "defaultValue": "IdentityBackendService",
      "description": "Name of the Identity Backend microservice"
    },
    "RootNamespace": {
      "defaultValue": "ConnectSoft.IdentityBackend",
      "description": "Root namespace for Identity Backend code"
    },
    "PersistenceType": {
      "defaultValue": "SqlServer",
      "description": "Persistence technology for Identity data (SQL Server recommended)"
    }
  }
}

Merge Behavior:

  • Only specified fields are overridden
  • Unspecified fields retain base values
  • Choices arrays are replaced (not merged)

symbolAdds

Purpose: Add new symbols (parameters) not present in base template.json.

Example:

{
  "symbolAdds": {
    "UseExternalIdp": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "Configure external identity provider (Azure AD, Google, etc.)",
      "displayName": "Use External IdP",
      "replaces": "UseExternalIdp"
    },
    "RequireEmailConfirmation": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Require email confirmation for new user registrations",
      "displayName": "Require Email Confirmation",
      "replaces": "RequireEmailConfirmation"
    },
    "IdentityProviderType": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        { "choice": "Local", "description": "Local identity provider" },
        { "choice": "AzureAD", "description": "Azure Active Directory" },
        { "choice": "Google", "description": "Google OAuth" }
      ],
      "defaultValue": "Local",
      "description": "Type of identity provider to use",
      "displayName": "Identity Provider Type",
      "replaces": "IdentityProviderType"
    }
  }
}

Merge Behavior:

  • New symbols are added to the symbols collection
  • No conflicts should exist (validation checks for duplicates)

postActionsAdds

Purpose: Add additional post-actions beyond those in base template.json.

Example:

{
  "postActionsAdds": [
    {
      "id": "run-identity-migrations",
      "description": "Run Identity database migrations",
      "manualInstructions": [
        { "text": "Run 'dotnet ef database update' in Identity.Infrastructure project" }
      ],
      "actionId": "D396686C-DE45-4ED5-9664-E8E6BEF12E5D",
      "args": {
        "executable": "dotnet",
        "args": "ef database update --project src/Identity.Infrastructure/Identity.Infrastructure.csproj"
      }
    },
    {
      "id": "setup-identity-seed-data",
      "description": "Seed Identity database with initial data",
      "manualInstructions": [
        { "text": "Run the seed data script: dotnet run --project src/Identity.Infrastructure -- seed" }
      ]
    }
  ]
}

Merge Behavior:

  • Post-actions are appended to base post-actions
  • Order: base post-actions first, then overlay post-actions

primaryOutputsAdds

Purpose: Add additional primary outputs beyond those in base template.json.

Example:

{
  "primaryOutputsAdds": [
    {
      "path": "src/Identity.Api/Identity.Api.csproj"
    },
    {
      "path": "src/Identity.Domain/Identity.Domain.csproj"
    }
  ]
}

Merge Behavior:

  • Primary outputs are appended to base primary outputs

pack-template Script Behavior

The pack-template.ps1 script orchestrates the merge process.

Script Workflow

flowchart TD
    START[Start pack-template.ps1]
    LOADBASE[Load base template.json]
    LOADEXTEND[Load extend files<br/>identity.template.extend.json<br/>worker.template.extend.json]

    COPY[Copy base template folder<br/>to artifacts/template-working/]

    MERGE[Merge Process]

    APPLY1[Apply identityOverrides]
    APPLY2[Apply symbolOverrides]
    APPLY3[Apply symbolAdds]
    APPLY4[Apply postActionsAdds]
    APPLY5[Apply primaryOutputsAdds]

    VALIDATE[Validate merged template.json]
    WRITE[Write merged template.json<br/>to artifacts/template-working/]
    PACK[Pack as NuGet package<br/>or Factory artifact]

    START --> LOADBASE
    LOADBASE --> LOADEXTEND
    LOADEXTEND --> COPY
    COPY --> MERGE
    MERGE --> APPLY1
    APPLY1 --> APPLY2
    APPLY2 --> APPLY3
    APPLY3 --> APPLY4
    APPLY4 --> APPLY5
    APPLY5 --> VALIDATE
    VALIDATE --> WRITE
    WRITE --> PACK

    style START fill:#E3F2FD
    style MERGE fill:#FFE0B2
    style PACK fill:#A5D6A7
Hold "Alt" / "Option" to enable pan & zoom

Script Pseudocode

# pack-template.ps1 (conceptual)

# 1. Load base template.json
$baseTemplate = Get-Content "base-template/template/template.json" | ConvertFrom-Json

# 2. Load extend files (from recipe or command line)
$identityExtend = Get-Content "template/identity.template.extend.json" | ConvertFrom-Json
$workerExtend = Get-Content "template/worker.template.extend.json" | ConvertFrom-Json

# 3. Copy base template folder to working directory
Copy-Item "base-template/template/*" -Destination "artifacts/template-working/" -Recurse

# 4. Apply identityOverrides
if ($identityExtend.identityOverrides) {
    $baseTemplate.name = $identityExtend.identityOverrides.name
    $baseTemplate.shortName = $identityExtend.identityOverrides.shortName
    # ... merge tags, classifications
}

# 5. Apply symbolOverrides
if ($identityExtend.symbolOverrides) {
    foreach ($symbolName in $identityExtend.symbolOverrides.PSObject.Properties.Name) {
        $override = $identityExtend.symbolOverrides.$symbolName
        if ($baseTemplate.symbols.$symbolName) {
            # Merge override into base symbol
            foreach ($prop in $override.PSObject.Properties.Name) {
                $baseTemplate.symbols.$symbolName.$prop = $override.$prop
            }
        }
    }
}

# 6. Apply symbolAdds
if ($identityExtend.symbolAdds) {
    foreach ($symbolName in $identityExtend.symbolAdds.PSObject.Properties.Name) {
        $baseTemplate.symbols.$symbolName = $identityExtend.symbolAdds.$symbolName
    }
}

# 7. Apply worker overlay (if present)
# Repeat steps 4-6 for worker extend file

# 8. Apply postActionsAdds
if ($identityExtend.postActionsAdds) {
    $baseTemplate.postActions += $identityExtend.postActionsAdds
}

# 9. Validate merged template.json
# Check for required fields, valid JSON schema, etc.

# 10. Write merged template.json
$baseTemplate | ConvertTo-Json -Depth 100 | 
    Set-Content "artifacts/template-working/template.json"

# 11. Pack as NuGet package or Factory artifact
dotnet pack artifacts/template-working/ -o artifacts/packages/

Multi-Overlay Metadata (Identity + Worker)

When combining multiple overlays, extend files are applied in recipe order.

Recipe Example

# Recipe: identity-worker-service.yaml
templateId: identity-worker-service
displayName: "Identity Backend with Worker"
layers:
  - base: microservice/base
  - overlay: microservice/overlays/identity-backend
  - overlay: microservice/overlays/worker

Merge Order

  1. Load base template.json
  2. Apply Identity overlay:
    • identityOverrides → override name, shortName, description
    • symbolOverrides → override ServiceName default
    • symbolAdds → add UseExternalIdp, RequireEmailConfirmation
    • postActionsAdds → add identity migrations post-action
  3. Apply Worker overlay:
    • identityOverrides → override name, description (further customization)
    • symbolAdds → add WorkerEnabled, WorkerScheduleInterval
    • postActionsAdds → add worker setup post-action

Result

Final template.json contains:

  • Top-level fields: Overridden by Identity, then further customized by Worker
  • Symbols: Base symbols + Identity additions + Worker additions
  • Post-actions: Base post-actions + Identity post-actions + Worker post-actions

Multi-Overlay Diagram

flowchart LR
    BASE[Base template.json]
    IDENTITY[Identity Overlay<br/>identity.template.extend.json]
    WORKER[Worker Overlay<br/>worker.template.extend.json]

    MERGE[Merge Process]

    FINAL[Final template.json<br/>Base + Identity + Worker]

    BASE --> MERGE
    IDENTITY --> MERGE
    WORKER --> MERGE
    MERGE --> FINAL

    style BASE fill:#BBDEFB
    style IDENTITY fill:#C8E6C9
    style WORKER fill:#FFF9C4
    style FINAL fill:#A5D6A7
Hold "Alt" / "Option" to enable pan & zoom

Complete Example: Identity Template Extend File

Here's a complete example of an identity template extend file:

{
  "identityOverrides": {
    "name": "ConnectSoft Identity Backend Service",
    "shortName": "cs-identity-backend",
    "description": "Identity Backend microservice with user management, authentication, authorization, and multi-tenant support. Built on ConnectSoft base microservice template.",
    "tags": {
      "domain": "identity",
      "connectsoft-template": "identity-backend",
      "features": "authentication,authorization,user-management"
    },
    "classifications": [
      "ConnectSoft",
      "Microservice",
      "Identity",
      "Authentication",
      "Authorization"
    ]
  },
  "symbolOverrides": {
    "ServiceName": {
      "defaultValue": "IdentityBackendService",
      "description": "Name of the Identity Backend microservice"
    },
    "RootNamespace": {
      "defaultValue": "ConnectSoft.IdentityBackend",
      "description": "Root namespace for Identity Backend code"
    }
  },
  "symbolAdds": {
    "UseExternalIdp": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "Configure external identity provider (Azure AD, Google, Facebook, etc.)",
      "displayName": "Use External IdP",
      "replaces": "UseExternalIdp"
    },
    "RequireEmailConfirmation": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Require email confirmation for new user registrations",
      "displayName": "Require Email Confirmation",
      "replaces": "RequireEmailConfirmation"
    },
    "IdentityProviderType": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        { "choice": "Local", "description": "Local identity provider with email/password" },
        { "choice": "AzureAD", "description": "Azure Active Directory" },
        { "choice": "Google", "description": "Google OAuth 2.0" },
        { "choice": "Facebook", "description": "Facebook OAuth 2.0" }
      ],
      "defaultValue": "Local",
      "description": "Type of identity provider to use",
      "displayName": "Identity Provider Type",
      "replaces": "IdentityProviderType"
    },
    "MultiTenantEnabled": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Enable multi-tenant support (tenant isolation)",
      "displayName": "Multi-Tenant Enabled",
      "replaces": "MultiTenantEnabled"
    }
  },
  "postActionsAdds": [
    {
      "id": "run-identity-migrations",
      "description": "Run Identity database migrations",
      "manualInstructions": [
        { "text": "Navigate to Identity.Infrastructure project" },
        { "text": "Run 'dotnet ef database update' to apply migrations" }
      ],
      "actionId": "D396686C-DE45-4ED5-9664-E8E6BEF12E5D",
      "args": {
        "executable": "dotnet",
        "args": "ef database update --project src/Identity.Infrastructure/Identity.Infrastructure.csproj"
      }
    },
    {
      "id": "setup-identity-seed-data",
      "description": "Seed Identity database with initial admin user and roles",
      "manualInstructions": [
        { "text": "Run the seed data script: dotnet run --project src/Identity.Infrastructure -- seed" },
        { "text": "Default admin credentials: admin@example.com / Admin123!" }
      ]
    },
    {
      "id": "configure-external-idp",
      "condition": "(UseExternalIdp == true)",
      "description": "Configure external identity provider",
      "manualInstructions": [
        { "text": "Update appsettings.json with external IdP credentials" },
        { "text": "Set IdentityProviderType in configuration" }
      ]
    }
  ],
  "primaryOutputsAdds": [
    {
      "path": "src/Identity.Api/Identity.Api.csproj"
    },
    {
      "path": "src/Identity.Domain/Identity.Domain.csproj"
    },
    {
      "path": "src/Identity.Infrastructure/Identity.Infrastructure.csproj"
    }
  ]
}

Best Practices

Do's

Use extend files - Never duplicate base parameters in specialized templates
Override only what's needed - Don't override base parameters unnecessarily
Use descriptive names - Make extend file names clear (identity.template.extend.json)
Validate extend files - Ensure JSON is valid and follows schema
Test merge process - Verify merged template.json is correct
Document custom parameters - Add clear descriptions for new symbols

Don'ts

Don't duplicate base parameters - Use symbolOverrides to change defaults, not duplicate
Don't modify base template.json - Base is canonical, use extend files
Don't create conflicting symbols - Ensure symbol names don't conflict
Don't skip validation - Always validate merged template.json
Don't hard-code values - Use parameters and symbols, not hard-coded strings