Skip to main content

Templates

Templates are the core code generation components in Intent Architect. They transform metadata models into source code files.

IntentTemplateBase Class

All templates inherit from IntentTemplateBase or its generic variants, which provide the foundational template infrastructure.

Class Hierarchy

public abstract class IntentTemplateBase : T4TemplateBase, 
    IIntentTemplate, 
    IConfigurableTemplate,
    IHasTemplateDependencies, 
    ITemplatePostConfigurationHook, 
    ITemplatePostCreationHook,
    IAfterTemplateRegistrationExecutionHook,
    ITemplateBeforeExecutionHook
{
    protected IntentTemplateBase(string templateId, IOutputTarget outputTarget)
    {
        ExecutionContext = outputTarget.ExecutionContext;
        OutputTarget = outputTarget;
        Id = templateId;
        BindingContext = new TemplateBindingContext(this);
    }

    public string Id { get; }
    public ISoftwareFactoryExecutionContext ExecutionContext { get; }
    public IOutputTarget OutputTarget { get; }
    public IFileMetadata FileMetadata { get; private set; }

    public abstract ITemplateFileConfig GetTemplateFileConfig();
    public virtual string RunTemplate() { ... }
}

Generic Template Variants

Model-Bound Templates

public abstract class IntentTemplateBase<TModel> : IntentTemplateBase, 
    IIntentTemplate<TModel>
{
    protected IntentTemplateBase(string templateId, IOutputTarget outputTarget, TModel model) 
        : base(templateId, outputTarget)
    {
        Model = model;
    }

    public TModel Model { get; }
}
Model-bound templates generate one file per model instance (e.g., one class file per domain entity).

Templates with Decorators

public abstract class IntentTemplateBase<TModel, TDecorator> : IntentTemplateBase<TModel>,
    IHasDecorators<TDecorator>
    where TDecorator : ITemplateDecorator
{
    private readonly List<TDecorator> _decorators = [];

    public IEnumerable<TDecorator> GetDecorators()
    {
        return _decorators.OrderBy(x => x.Priority);
    }

    public void AddDecorator(TDecorator decorator)
    {
        _decorators.Add(decorator);
    }

    protected string GetDecoratorsOutput(Func<TDecorator, string> propertyFunc)
    {
        return GetDecorators().Aggregate(propertyFunc);
    }
}

Key Properties

Id
string
Unique identifier for the template instance (e.g., "Intent.Common.CSharp.ClassTemplate")
ExecutionContext
ISoftwareFactoryExecutionContext
Provides access to application-wide services, metadata, and other templates
OutputTarget
IOutputTarget
Determines where the generated file will be placed (e.g., which Visual Studio project)
Model
TModel
The metadata model instance bound to this template (for model-bound templates)
FileMetadata
IFileMetadata
Metadata about the output file (path, name, merge behavior, etc.)

Template Lifecycle Hooks

Templates can override lifecycle methods to participate in the Software Factory execution:
public class MyTemplate : IntentTemplateBase<ClassModel>
{
    // Called after template creation
    public override void OnCreated()
    {
        base.OnCreated();
        // Initialize template state
    }

    // Called after all templates are configured
    public override void OnConfigured()
    {
        base.OnConfigured();
        // Discover and track dependencies
    }

    // Called after all templates are registered
    public override void AfterTemplateRegistration()
    {
        base.AfterTemplateRegistration();
        // Final setup before execution
    }

    // Called before template execution
    public override void BeforeTemplateExecution()
    {
        base.BeforeTemplateExecution();
        // Prepare for code generation
    }
}
1

OnCreated

Template instance is created and constructor has executed
2

OnConfigured

All templates have been created and can now be discovered
3

AfterTemplateRegistration

All templates are registered and dependency graph is complete
4

BeforeTemplateExecution

About to execute RunTemplate() to generate code

Template Configuration

Templates specify their output file configuration:
public override ITemplateFileConfig GetTemplateFileConfig()
{
    return new CSharpFileConfig(
        className: $"{Model.Name}",
        @namespace: $"{OutputTarget.GetNamespace()}",
        relativeLocation: $"{OutputTarget.GetLocation()}"
    );
}

Template Dependencies

Templates can declare dependencies on other templates:
public class ServiceTemplate : IntentTemplateBase<ServiceModel>
{
    public override void OnCreated()
    {
        base.OnCreated();
        
        // Dependency on a specific template instance
        AddTemplateDependency("Intent.Application.Dtos.DtoTemplate", Model.ReturnType);
        
        // Dependency on a template by ID only
        AddTemplateDependency("Intent.Infrastructure.DependencyInjection");
    }
}
Template dependencies must be added during OnCreated() or OnConfigured() to ensure proper execution order.

Type Resolution

Templates use the type resolution system to generate type names:
public class ServiceTemplate : IntentTemplateBase<ServiceModel>
{
    public ServiceTemplate(string templateId, IOutputTarget outputTarget, ServiceModel model) 
        : base(templateId, outputTarget, model)
    {
        // Register type sources
        AddTypeSource("Intent.Application.Dtos.DtoTemplate");
        AddTypeSource("Intent.Domain.Entities.EntityTemplate");
    }

    public string GetReturnType()
    {
        // Resolves the type name and tracks dependency
        return GetTypeName(Model.ReturnType);
    }
}
  1. Template calls GetTypeName(typeReference)
  2. System searches registered type sources for a template that outputs this type
  3. If found, the template is added as a dependency
  4. The fully qualified type name is returned
  5. Collection formatters and nullable formatters are applied

Controlling Template Execution

public override bool CanRunTemplate()
{
    // Don't generate for abstract classes
    if (Model.IsAbstract)
    {
        return false;
    }
    return base.CanRunTemplate();
}

public override bool CanDiscoverTemplate()
{
    // Other templates can still find and reference this template
    return true;
}

CanRun

Controls whether the template generates output

IsDiscoverable

Controls whether other templates can find this template

Template Registration

Templates are registered via registration classes:
public class ServiceTemplateRegistration : FilePerModelTemplateRegistration<ServiceModel>
{
    public override string TemplateId => "Intent.Application.ServiceTemplate";

    public override ITemplate CreateTemplateInstance(IOutputTarget outputTarget, ServiceModel model)
    {
        return new ServiceTemplate(TemplateId, outputTarget, model);
    }

    public override IEnumerable<ServiceModel> GetModels(IApplication application)
    {
        return application.MetadataManager
            .Services(application)
            .GetServiceModels();
    }
}
FilePerModelTemplateRegistration<TModel> creates one template instance per model returned by GetModels().

Correlation IDs

Templates use correlation IDs to track files between executions:
public override string GetCorrelationId()
{
    if (Model is IMetadataModel model)
    {
        return $"{Id}#{model.Id}";
    }
    return Id;
}
Correlation IDs allow the Software Factory to detect when files should be deleted (if their model was removed) or updated.

Example Template

Here’s a complete example of a C# class template:
public class ClassTemplate : IntentTemplateBase<ClassModel, IClassDecorator>
{
    public const string TemplateId = "Intent.Common.CSharp.ClassTemplate";

    public ClassTemplate(IOutputTarget outputTarget, ClassModel model) 
        : base(TemplateId, outputTarget, model)
    {
        AddTypeSource("Intent.Common.CSharp.InterfaceTemplate");
    }

    public override ITemplateFileConfig GetTemplateFileConfig()
    {
        return new CSharpFileConfig(
            className: Model.Name,
            @namespace: OutputTarget.GetNamespace()
        );
    }

    public string GetBaseTypes()
    {
        var baseTypes = new List<string>();
        
        if (Model.ParentClass != null)
        {
            baseTypes.Add(GetTypeName(Model.ParentClass));
        }
        
        baseTypes.AddRange(Model.Interfaces.Select(GetTypeName));
        
        return baseTypes.Any() ? " : " + string.Join(", ", baseTypes) : "";
    }
}

Best Practices

Single Responsibility

Each template should generate one type of file

Use Type Resolution

Always use GetTypeName() instead of hardcoding type names

Declare Dependencies

Explicitly declare all template dependencies

Lifecycle Hooks

Use appropriate lifecycle hooks for initialization

Decorators

Extend template output

Metadata System

Work with models and metadata

Factory Extensions

Hook into execution lifecycle

Build docs developers (and LLMs) love