Skip to main content
Templates are the core mechanism for code generation in Intent Architect. They transform metadata from designers into actual code files.

Template Basics

A template consists of three main components:
  1. Template Class: Contains the generation logic
  2. Template Partial Class: Your customizations and business logic
  3. Template Registration: Defines when and how templates are instantiated

Creating a Basic Template

1
Add Template in Designer
2
In the Module Builder designer:
3
  • Right-click your module
  • Select New Template > File Template
  • Configure:
    • Name: EntityTemplate
    • Templating Method: C# String Interpolation
    • File Extension: cs
  • 4
    Configure Template Settings
    5
    Apply the Template Settings stereotype:
    6
  • Default Location: Entities
  • Enabled By Default: true
  • Template Output: Single File
  • 7
    Add Template Registration
    8
  • Right-click the template
  • Select New Template Registration > File Per Model
  • Configure:
    • Model Type: Select your API model (e.g., Class)
  • 9
    Run Software Factory
    10
    Generate the template infrastructure:
    11
    intent run-software-factory
    
    12
    This creates:
    13
  • Templates/EntityTemplate/EntityTemplateTemplate.cs (generated)
  • Templates/EntityTemplate/EntityTemplateTemplatePartial.cs (yours)
  • Templates/EntityTemplate/EntityTemplateTemplateRegistration.cs (generated)
  • 14
    Implement Template Logic
    15
    Edit the partial class:
    16
    using Intent.Engine;
    using Intent.Modules.Common.CSharp.Builder;
    using Intent.Modules.Common.CSharp.Templates;
    using Intent.Templates;
    using MyModule.Api;
    
    namespace MyModule.Templates.EntityTemplate
    {
        partial class EntityTemplateTemplate : CSharpTemplateBase<ClassModel>
        {
            public const string TemplateId = "MyModule.EntityTemplate";
    
            public EntityTemplateTemplate(
                IOutputTarget outputTarget, 
                ClassModel model) 
                : base(TemplateId, outputTarget, model)
            {
            }
    
            protected override CSharpFileConfig DefineFileConfig()
            {
                return new CSharpFileConfig(
                    className: $"{Model.Name}",
                    @namespace: $"{OutputTarget.GetNamespace()}.Domain.Entities",
                    relativeLocation: $"{OutputTarget.GetNamespace()}/Domain/Entities");
            }
    
            public override string TransformText()
            {
                return $@"
    using System;
    
    namespace {Namespace}
    {{
        public class {ClassName}
        {{
            {GenerateProperties()}
        }}
    }}";
            }
    
            private string GenerateProperties()
            {
                return string.Join("\n        ", 
                    Model.Properties.Select(prop => 
                        $"public {GetTypeName(prop.Type)} {prop.Name} {{ get; set; }}"));
            }
        }
    }
    

    Template Types

    Modern approach using C# string interpolation:
    public override string TransformText()
    {
        return $@"
    using System;
    using System.Collections.Generic;
    
    namespace {Namespace}
    {{
        public class {ClassName}
        {{
            public {ClassName}()
            {{
                {GetConstructorBody()}
            }}
    
            {GetProperties()}
            
            {GetMethods()}
        }}
    }}";
    }
    

    T4 Templates (Legacy)

    Text Template Transformation Toolkit approach:
    EntityTemplate.tt
    <#@ template language="C#" inherits="CSharpTemplateBase<ClassModel>" #>
    <#@ import namespace="System.Linq" #>
    using System;
    using System.Collections.Generic;
    
    namespace <#= Namespace #>
    {
        public class <#= ClassName #>
        {
    <#  foreach (var property in Model.Properties) { #>
            public <#= GetTypeName(property.Type) #> <#= property.Name #> { get; set; }
    <#  } #>
    
            public void Process()
            {
    <#  if (Model.HasStereotype("Auditable")) { #>
                UpdateAuditFields();
    <#  } #>
                // Implementation
            }
        }
    }
    

    Custom File Templates

    For non-C# files (JSON, YAML, XML, etc.):
    ConfigTemplate.cs
    using Intent.Engine;
    using Intent.Templates;
    
    public class ConfigTemplate : IntentTemplateBase
    {
        public const string TemplateId = "MyModule.ConfigTemplate";
    
        public ConfigTemplate(IOutputTarget outputTarget, object model = null)
            : base(TemplateId, outputTarget, model)
        {
        }
    
        public override ITemplateFileConfig GetTemplateFileConfig()
        {
            return new TemplateFileConfig(
                fileName: "appsettings",
                fileExtension: "json");
        }
    
        public override string TransformText()
        {
            return $@"{{
      ""Application"": {{
        ""Name"": ""{ExecutionContext.GetApplicationConfig().Name}"",
        ""Environment"": ""Production""
      }},
      ""ConnectionStrings"": {{
        ""Default"": ""{GetConnectionString()}""
      }}
    }}";
        }
    
        private string GetConnectionString()
        {
            return ExecutionContext.Settings.GetSetting(
                "ConnectionString", 
                "Server=localhost;Database=mydb");
        }
    }
    

    Template Registration Patterns

    File Per Model Registration

    Generates one file for each model element:
    public class EntityTemplateRegistration 
        : FilePerModelTemplateRegistration<ClassModel>
    {
        private readonly IMetadataManager _metadataManager;
    
        public EntityTemplateRegistration(IMetadataManager metadataManager)
        {
            _metadataManager = metadataManager;
        }
    
        public override string TemplateId => EntityTemplate.TemplateId;
    
        public override ITemplate CreateTemplateInstance(
            IOutputTarget outputTarget, 
            ClassModel model)
        {
            return new EntityTemplate(outputTarget, model);
        }
    
        public override IEnumerable<ClassModel> GetModels(IApplication application)
        {
            // Query metadata for entities
            return _metadataManager
                .Domain(application)
                .GetClassModels()
                .Where(x => x.HasStereotype("Entity"))
                .OrderBy(x => x.Name);
        }
    }
    

    Single File Registration

    One file per output target:
    public class StartupTemplateRegistration 
        : SingleFileListModelTemplateRegistration
    {
        public override string TemplateId => StartupTemplate.TemplateId;
    
        public override ITemplate CreateTemplateInstance(IOutputTarget outputTarget)
        {
            return new StartupTemplate(outputTarget, null);
        }
    }
    

    Single File List Model Registration

    One file processing multiple models:
    public class DependencyInjectionTemplateRegistration 
        : SingleFileListModelTemplateRegistration<ClassModel>
    {
        private readonly IMetadataManager _metadataManager;
    
        public DependencyInjectionTemplateRegistration(
            IMetadataManager metadataManager)
        {
            _metadataManager = metadataManager;
        }
    
        public override string TemplateId => 
            DependencyInjectionTemplate.TemplateId;
    
        public override ITemplate CreateTemplateInstance(
            IOutputTarget outputTarget,
            IEnumerable<ClassModel> models)
        {
            return new DependencyInjectionTemplate(outputTarget, models);
        }
    
        public override IEnumerable<ClassModel> GetModels(IApplication application)
        {
            return _metadataManager
                .Services(application)
                .GetClassModels()
                .Where(x => x.HasStereotype("Service"));
        }
    }
    

    Custom Registration

    Full control over instantiation:
    public class CustomTemplateRegistration : ITemplateRegistration
    {
        private readonly IMetadataManager _metadataManager;
    
        public CustomTemplateRegistration(IMetadataManager metadataManager)
        {
            _metadataManager = metadataManager;
        }
    
        public string TemplateId => CustomTemplate.TemplateId;
    
        public void DoRegistration(
            ITemplateInstanceRegistry registry, 
            IApplication application)
        {
            // Complex logic for template instantiation
            var apiOutputTargets = application.OutputTargets
                .Where(x => x.HasRole("Api"));
    
            foreach (var outputTarget in apiOutputTargets)
            {
                var models = _metadataManager
                    .Services(application)
                    .GetClassModels()
                    .Where(x => ShouldGenerateForModel(x, outputTarget));
    
                if (models.Any())
                {
                    registry.RegisterTemplate(
                        TemplateId,
                        () => new CustomTemplate(outputTarget, models));
                }
            }
        }
    
        private bool ShouldGenerateForModel(ClassModel model, IOutputTarget target)
        {
            return model.HasStereotype("WebApi") && 
                   target.GetProject().Name.Contains("Api");
        }
    }
    

    Using the CSharp Builder

    The CSharp Builder provides a fluent API for generating C# code:

    Building Classes

    using Intent.Modules.Common.CSharp.Builder;
    
    public override string TransformText()
    {
        var file = new CSharpFile(Namespace, RelativeLocation)
            .AddUsing("System")
            .AddUsing("System.Collections.Generic")
            .AddUsing("System.Linq");
    
        var @class = file.AddClass(ClassName, @class =>
        {
            @class.AddAttribute("[GeneratedCode]");
            @class.ImplementsInterface("IEntity");
            
            // Add constructor
            @class.AddConstructor(ctor =>
            {
                ctor.AddParameter("string", "id");
                ctor.AddStatement("Id = id;");
            });
    
            // Add properties
            foreach (var prop in Model.Properties)
            {
                @class.AddProperty(
                    GetTypeName(prop.Type),
                    prop.Name,
                    property =>
                    {
                        if (prop.HasStereotype("Required"))
                        {
                            property.AddAttribute("[Required]");
                        }
                    });
            }
    
            // Add methods
            @class.AddMethod("void", "Validate", method =>
            {
                method.AddStatement("if (string.IsNullOrEmpty(Id))");
                method.AddStatement("{");
                method.AddStatement("    throw new ValidationException();", s => s.SeparatedFromPrevious());
                method.AddStatement("}");
            });
        });
    
        return file.ToString();
    }
    

    Building Interfaces

    var file = new CSharpFile(Namespace, RelativeLocation)
        .AddUsing("System.Collections.Generic");
    
    var @interface = file.AddInterface($"I{ClassName}", @interface =>
    {
        @interface.AddMethod($"Task<{Model.Name}>", "GetByIdAsync", method =>
        {
            method.AddParameter("Guid", "id");
        });
    
        @interface.AddMethod($"Task<IEnumerable<{Model.Name}>>", "GetAllAsync");
    
        @interface.AddMethod("Task", "SaveAsync", method =>
        {
            method.AddParameter(Model.Name, "entity");
        });
    });
    
    return file.ToString();
    

    Building Methods with Complex Logic

    @class.AddMethod("Task<Result>", "ProcessAsync", method =>
    {
        method.Async();
        method.AddParameter("Request", "request");
        method.AddParameter("CancellationToken", "cancellationToken", 
            p => p.WithDefaultValue("default"));
    
        // Method body
        method.AddStatement("var validator = new RequestValidator();");
        method.AddStatement("var validationResult = await validator.ValidateAsync(request, cancellationToken);",
            s => s.SeparatedFromPrevious());
        
        method.AddStatement("if (!validationResult.IsValid)", s => s.SeparatedFromPrevious());
        method.AddStatement("{");
        method.AddStatement("    return Result.Failure(validationResult.Errors);");
        method.AddStatement("}");
        
        method.AddStatement("var result = await _service.ExecuteAsync(request, cancellationToken);",
            s => s.SeparatedFromPrevious());
        method.AddStatement("return Result.Success(result);");
    });
    

    Template Dependencies

    Templates can depend on other templates:
    public class ControllerTemplate : CSharpTemplateBase<ClassModel>
    {
        public override IEnumerable<ITemplateDependency> GetTemplateDependencies()
        {
            return new[]
            {
                // Depend on service template for the same model
                TemplateDependency.OnModel<IClassProvider>(
                    ServiceTemplate.TemplateId, 
                    Model),
                
                // Depend on DTO templates
                TemplateDependency.OnModel<IClassProvider>(
                    DtoTemplate.TemplateId, 
                    Model),
            };
        }
    
        public override string TransformText()
        {
            var service = GetTypeName(ServiceTemplate.TemplateId, Model);
            var dto = GetTypeName(DtoTemplate.TemplateId, Model);
    
            return $@"
    namespace {Namespace}
    {{
        public class {ClassName}Controller
        {{
            private readonly {service} _service;
    
            public {ClassName}Controller({service} service)
            {{
                _service = service;
            }}
    
            public async Task<{dto}> Get(Guid id)
            {{
                return await _service.GetByIdAsync(id);
            }}
        }}
    }}";
        }
    }
    

    Accessing Metadata

    Via Template Model

    public override string TransformText()
    {
        var properties = Model.Properties
            .Where(p => !p.HasStereotype("Ignore"))
            .OrderBy(p => p.Name);
    
        var associations = Model.AssociatedClasses
            .Where(a => a.IsNavigable);
    
        var stereotypeValue = Model.GetStereotypeProperty<string>(
            "Entity", "TableName", Model.Name);
    
        // Use metadata...
    }
    

    Via Metadata Manager

    private IEnumerable<ClassModel> GetRelatedEntities()
    {
        return ExecutionContext.MetadataManager
            .Domain(ExecutionContext.GetApplicationConfig().Id)
            .GetClassModels()
            .Where(x => x.HasStereotype("Entity"))
            .Where(x => IsRelatedTo(Model, x));
    }
    

    Via Application Settings

    private string GetOutputPath()
    {
        return ExecutionContext.Settings.GetSetting(
            "MyModule.OutputPath",
            "./Generated");
    }
    
    private bool IsFeatureEnabled(string featureName)
    {
        return ExecutionContext.Settings
            .GetSetting($"MyModule.{featureName}.Enabled", "true")
            .Equals("true", StringComparison.OrdinalIgnoreCase);
    }
    

    Template Output Configuration

    File Location and Naming

    protected override CSharpFileConfig DefineFileConfig()
    {
        return new CSharpFileConfig(
            className: $"{Model.Name}Controller",
            @namespace: $"{OutputTarget.GetNamespace()}.Api.Controllers",
            relativeLocation: $"{OutputTarget.GetNamespace()}/Api/Controllers")
        {
            // Override file name
            FileName = $"{Model.Name}.Controller",
            
            // Override extension (default is from template config)
            FileExtension = "cs"
        };
    }
    

    Multiple Output Files

    public override IEnumerable<ITemplateDependency> GetAdditionalOutputs()
    {
        return Model.Operations.Select(operation =>
            TemplateDependency.OnModel<IClassProvider>(
                OperationTemplate.TemplateId,
                operation));
    }
    

    Code Management Attributes

    IntentManaged Attributes

    Control how code is managed:
    public override string TransformText()
    {
        return $@"
    using Intent.RoslynWeaver.Attributes;
    
    [assembly: DefaultIntentManaged(Mode.Fully)]
    
    namespace {Namespace}
    {{
        [IntentManaged(Mode.Merge, Signature = Mode.Fully)]
        public class {ClassName}
        {{
            [IntentManaged(Mode.Fully)]
            public string Id {{ get; set; }}
    
            [IntentManaged(Mode.Ignore)]
            public void CustomMethod()
            {{
                // This method won't be managed
            }}
        }}
    }}";
    }
    
    Management modes:
    • Fully: Completely managed, regenerated each time
    • Merge: Body can be customized, signature regenerated
    • Ignore: Not managed, won’t be touched

    Best Practices

    Keep Templates Focused

    // Good: Single responsibility
    public class EntityTemplate { }
    public class EntityDtoTemplate { }
    public class EntityRepositoryTemplate { }
    
    // Bad: Doing too much
    public class EntityEverythingTemplate { }
    

    Extract Complex Logic

    // Helper class
    public static class EntityHelper
    {
        public static bool IsAggregateRoot(ClassModel model)
        {
            return model.HasStereotype("Aggregate Root") ||
                   model.IncomingAssociations.Any(a => 
                       a.HasStereotype("Aggregate"));
        }
    
        public static string GetTableName(ClassModel model)
        {
            return model.GetStereotypeProperty<string>(
                "Entity", "TableName") ?? 
                Pluralize(model.Name);
        }
    }
    
    // Use in template
    public override string TransformText()
    {
        var isAggregateRoot = EntityHelper.IsAggregateRoot(Model);
        var tableName = EntityHelper.GetTableName(Model);
        // ...
    }
    

    Use Consistent Naming

    // Consistent patterns
    public class {Model.Name}Template { }          // Template classes
    public class {Model.Name}TemplatePartial { }   // Partial classes  
    public class {Model.Name}TemplateRegistration { } // Registrations
    

    Cache Expensive Operations

    private IEnumerable<ClassModel> _relatedEntities;
    public IEnumerable<ClassModel> RelatedEntities
    {
        get
        {
            return _relatedEntities ??= ExecutionContext.MetadataManager
                .Domain(ExecutionContext.GetApplicationConfig().Id)
                .GetClassModels()
                .Where(x => IsRelatedTo(Model, x))
                .ToList();
        }
    }
    

    Testing Templates

    Unit Testing

    [Fact]
    public void EntityTemplate_GeneratesCorrectOutput()
    {
        // Arrange
        var model = CreateTestModel();
        var outputTarget = CreateTestOutputTarget();
        var template = new EntityTemplate(outputTarget, model);
    
        // Act
        var output = template.TransformText();
    
        // Assert
        Assert.Contains($"class {model.Name}", output);
        Assert.Contains("public string Id { get; set; }", output);
    }
    

    Integration Testing

    Create test applications with sample metadata and verify output.

    Troubleshooting

    • Verify GetModels() returns elements in registration
    • Check template is enabled in module settings
    • Ensure output target is configured
    • Review Software Factory output for errors
    • Check all using statements are included
    • Verify type names are correctly resolved
    • Ensure proper escaping of special characters
    • Test with various metadata configurations
    • Rebuild the module project
    • Clear Intent Architect cache
    • Reinstall module in test application
    • Check template version in .imodspec

    Next Steps

    Creating Decorators

    Learn to modify template outputs with decorators

    Factory Extensions

    Hook into Software Factory lifecycle

    Testing Modules

    Strategies for testing your modules

    Designer Configuration

    Create custom visual designers

    Build docs developers (and LLMs) love