Skip to main content
Templates are the foundation of Intent Architect modules. They generate code files based on metadata from designers.

Template Types

Intent Architect supports several template registration patterns:

FilePerModel

Generates one file per metadata model instance (e.g., one class per entity)

SingleFileListModel

Generates one file for all model instances (e.g., a registration file)

FilePerModelNoMetadata

Generates files without specific metadata models

SingleFileNoModel

Generates a single file without metadata dependencies

Creating a Template

Step 1: Define the Template Class

Create a template that inherits from CSharpTemplateBase:
CommandHandlerTemplatePartial.cs
using Intent.Engine;
using Intent.Modules.Common.CSharp.Builder;
using Intent.Modules.Common.CSharp.Templates;
using Intent.RoslynWeaver.Attributes;
using Intent.Templates;

[assembly: DefaultIntentManaged(Mode.Merge)]
[assembly: IntentTemplate("Intent.ModuleBuilder.CSharp.Templates.CSharpTemplatePartial", Version = "1.0")]

namespace Intent.Modules.Application.MediatR.Templates.CommandHandler
{
    [IntentManaged(Mode.Merge, Signature = Mode.Merge)]
    public partial class CommandHandlerTemplate : CSharpTemplateBase<CommandModel, CommandHandlerDecorator>, 
        ICSharpFileBuilderTemplate
    {
        public const string TemplateId = "Intent.Application.MediatR.CommandHandler";

        [IntentManaged(Mode.Merge, Signature = Mode.Fully)]
        public CommandHandlerTemplate(IOutputTarget outputTarget, CommandModel model) 
            : base(TemplateId, outputTarget, model)
        {
            SetDefaultCollectionFormatter(CSharpCollectionFormatter.CreateList());
            CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath());
            Configure(this, model);
        }

        public CSharpFile CSharpFile { get; }

        public override string TransformText()
        {
            return CSharpFile.ToString();
        }

        protected override CSharpFileConfig DefineFileConfig()
        {
            return new CSharpFileConfig(
                className: $"{Model.Name}Handler",
                @namespace: this.GetNamespace(),
                relativeLocation: this.GetFolderPath());
        }
    }
}

Step 2: Configure the Template Output

Use the CSharpFile builder to construct the output:
Configure Method
internal static void Configure(ICSharpFileBuilderTemplate template, CommandModel model)
{
    template.AddNugetDependency(NugetPackages.MediatR(template.OutputTarget));
    template.AddTypeSource(TemplateRoles.Application.Command);

    template.CSharpFile
        .AddUsing("System")
        .AddUsing("System.Threading")
        .AddUsing("System.Threading.Tasks")
        .AddUsing("MediatR")
        .AddClass($"{model.Name}Handler", @class =>
        {
            @class.AddMetadata("handler", true);
            @class.AddMetadata("model", model);
            @class.ImplementsInterface($"IRequestHandler<{GetCommandName(template, model)}>" );
            @class.AddAttribute("IntentManaged(Mode.Merge, Signature = Mode.Fully)");
            
            @class.AddConstructor(ctor =>
            {
                ctor.AddAttribute(CSharpIntentManagedAttribute.Merge());
            });
            
            @class.AddMethod(GetReturnType(template, model), "Handle", method =>
            {
                method.RegisterAsProcessingHandlerForModel(model);
                method.TryAddXmlDocComments(model.InternalElement);
                method.Async();
                method.AddAttribute(CSharpIntentManagedAttribute.Fully().WithBodyMerge());
                method.AddParameter(GetCommandName(template, model), "request");
                method.AddParameter("CancellationToken", "cancellationToken");

                method.AddStatement("// TODO: Implement handler functionality");
                method.AddStatement("throw new NotImplementedException();");
            });
        });
}

Step 3: Create the Template Registration

CommandHandlerTemplateRegistration.cs
using Intent.Engine;
using Intent.Modules.Common.Registrations;
using Intent.RoslynWeaver.Attributes;
using Intent.Templates;

[assembly: DefaultIntentManaged(Mode.Merge)]
[assembly: IntentTemplate("Intent.ModuleBuilder.TemplateRegistration.FilePerModel", Version = "1.0")]

namespace Intent.Modules.Application.MediatR.Templates.CommandHandler
{
    [IntentManaged(Mode.Merge, Body = Mode.Merge, Signature = Mode.Fully)]
    public class CommandHandlerTemplateRegistration : FilePerModelTemplateRegistration<CommandModel>
    {
        private readonly IMetadataManager _metadataManager;

        public CommandHandlerTemplateRegistration(IMetadataManager metadataManager)
        {
            _metadataManager = metadataManager;
        }

        public override string TemplateId => CommandHandlerTemplate.TemplateId;

        [IntentManaged(Mode.Fully)]
        public override ITemplate CreateTemplateInstance(
            IOutputTarget outputTarget, 
            CommandModel model)
        {
            return new CommandHandlerTemplate(outputTarget, model);
        }

        [IntentManaged(Mode.Merge, Body = Mode.Ignore, Signature = Mode.Fully)]
        public override IEnumerable<CommandModel> GetModels(IApplication application)
        {
            return _metadataManager.Services(application).GetCommandModels();
        }
    }
}

Using the CSharpFile Builder

The CSharpFile builder provides a fluent API for constructing C# files:

Adding Usings

CSharpFile
    .AddUsing("System")
    .AddUsing("System.Linq")
    .AddUsing("System.Collections.Generic");

Creating Classes

CSharpFile.AddClass("MyClass", @class =>
{
    @class.WithBaseType("BaseClass");
    @class.ImplementsInterface("IMyInterface");
    @class.AddAttribute("[Serializable]");
    @class.Abstract();
    @class.Partial();
});

Adding Properties

@class.AddProperty("string", "Name", property =>
{
    property.WithAccessModifier(AccessModifier.Public);
    property.Getter();
    property.Setter();
    property.WithInitialValue("\"Default\"");
});

Adding Methods

@class.AddMethod("void", "ProcessData", method =>
{
    method.AddParameter("string", "data");
    method.AddParameter("int", "count", param => param.WithDefaultValue("10"));
    method.Async();
    method.AddAttribute("[Obsolete]");
    
    method.AddStatement("// Implementation here");
    method.AddStatement("Console.WriteLine(data);");
});

Adding Constructors

@class.AddConstructor(ctor =>
{
    ctor.AddParameter("IService", "service", param =>
    {
        param.IntroduceReadonlyField();
    });
    ctor.AddParameter("ILogger", "logger", param =>
    {
        param.IntroduceReadonlyField();
    });
});

Creating Interfaces

CSharpFile.AddInterface("IRepository", @interface =>
{
    @interface.AddMethod("Task<T>", "GetByIdAsync", method =>
    {
        method.AddGenericParameter("T");
        method.AddParameter("Guid", "id");
    });
});

Template Decorators

Templates can be extended by decorators. Define a base decorator class:
CommandHandlerDecorator.cs
using Intent.RoslynWeaver.Attributes;
using Intent.Templates;

[assembly: DefaultIntentManaged(Mode.Ignore)]
[assembly: IntentTemplate("Intent.ModuleBuilder.Templates.TemplateDecoratorContract", Version = "1.0")]

namespace Intent.Modules.Application.MediatR.Templates.CommandHandler
{
    [IntentManaged(Mode.Merge)]
    public abstract class CommandHandlerDecorator : ITemplateDecorator
    {
        public int Priority { get; protected set; } = 0;
    }
}
See the Decorators guide for implementing decorators.

Managing Dependencies

Adding NuGet Packages

template.AddNugetDependency(new NugetPackageInfo(
    "MediatR",
    "12.0.0",
    template.OutputTarget));

Adding Template Dependencies

template.AddTemplateDependency(TemplateRoles.Application.Command);

Type Resolution

// Add type sources
template.AddTypeSource(TemplateRoles.Application.Contracts.Dto);
template.AddTypeSource(TemplateRoles.Domain.Enum);

// Get type names
var typeName = template.GetTypeName(model.TypeReference);
var commandName = template.GetTypeName(CommandTemplate.TemplateId, model);

Template Metadata

Add metadata to track elements for other templates and decorators:
@class.AddMetadata("model", model);
@class.AddMetadata("handler", true);
method.RegisterAsProcessingHandlerForModel(model);

Advanced Features

Conditional Output

public override bool CanRunTemplate()
{
    return base.CanRunTemplate() && 
           !ExecutionContext.Settings.GetCQRSSettings()
               .ConsolidateCommandQueryAssociatedFilesIntoSingleFile();
}

Multiple Files from One Template

Implement ITemplateBeforeExecutionHook:
public void BeforeTemplateExecution()
{
    if (ShouldGenerateAdditionalFile())
    {
        ExecutionContext.EventDispatcher.Publish(
            new AdditionalFileRequest(/* ... */));
    }
}

File Merging with RoslynWeaver

Configure merge behavior:
public override RoslynMergeConfig ConfigureRoslynMerger()
{
    return new RoslynMergeConfig(
        new TemplateMetadata(Id, "2.0"),
        new CustomMigration());
}

private class CustomMigration : ITemplateMigration
{
    public string Execute(string currentText)
    {
        // Transform existing code during upgrades
        return currentText.Replace("OldPattern", "NewPattern");
    }

    public TemplateMigrationCriteria Criteria => 
        TemplateMigrationCriteria.Upgrade(1, 2);
}

Helper Methods and Extensions

Create extension methods for common operations:
TemplateExtensions.cs
public static class TemplateExtensions
{
    public static string GetCommandNamespace(this ICSharpFileBuilderTemplate template)
    {
        return template.GetNamespace(additionalFolders: "Commands");
    }

    public static string GetCommandFolderPath(this ICSharpFileBuilderTemplate template)
    {
        return template.GetFolderPath(additionalFolders: "Commands");
    }
}

Testing Templates

Create unit tests for template output:
CommandHandlerTemplateTests.cs
[TestClass]
public class CommandHandlerTemplateTests
{
    [TestMethod]
    public void GeneratesCorrectHandlerClass()
    {
        // Arrange
        var model = CreateTestCommandModel();
        var outputTarget = CreateTestOutputTarget();
        var template = new CommandHandlerTemplate(outputTarget, model);

        // Act
        var output = template.TransformText();

        // Assert
        Assert.IsTrue(output.Contains("class CreateOrderHandler"));
        Assert.IsTrue(output.Contains("IRequestHandler<CreateOrderCommand>"));
    }
}

Best Practices

Always use CSharpFile builder instead of string concatenation. It provides:
  • Automatic using statement management
  • Proper indentation
  • Type resolution
  • Metadata tracking
Each template should generate one type of file. Use multiple templates for different file types.
Use metadata to communicate between templates and decorators:
@class.AddMetadata("model", model);
method.RegisterAsProcessingHandlerForModel(model);
Check for null models, empty collections, and optional features:
if (model.TypeReference?.Element != null)
{
    // Handle return type
}
Use template settings and module settings to allow customization:
var settings = ExecutionContext.Settings.GetYourSettings();
if (settings.EnableFeatureX())
{
    // Add feature X code
}

Common Patterns

Repository Pattern Template

template.CSharpFile.AddInterface($"I{model.Name}Repository", @interface =>
{
    @interface.AddMethod($"Task<{model.Name}>", "GetByIdAsync", method =>
    {
        method.AddParameter("Guid", "id");
        method.AddParameter("CancellationToken", "cancellationToken", 
            p => p.WithDefaultValue("default"));
    });
    
    @interface.AddMethod("Task", "AddAsync", method =>
    {
        method.AddParameter(model.Name, "entity");
        method.AddParameter("CancellationToken", "cancellationToken", 
            p => p.WithDefaultValue("default"));
    });
});

Service Implementation Template

template.CSharpFile.AddClass($"{model.Name}Service", @class =>
{
    @class.ImplementsInterface($"I{model.Name}Service");
    
    @class.AddConstructor(ctor =>
    {
        foreach (var dependency in model.Dependencies)
        {
            ctor.AddParameter(
                template.GetTypeName(dependency),
                dependency.Name.ToCamelCase(),
                p => p.IntroduceReadonlyField());
        }
    });
    
    foreach (var operation in model.Operations)
    {
        @class.AddMethod(
            template.GetTypeName(operation.ReturnType),
            operation.Name,
            method => ConfigureMethod(template, method, operation));
    }
});

Next Steps

Decorators

Learn how to extend templates with decorators

Factory Extensions

Hook into the Software Factory lifecycle

Testing

Write comprehensive tests for templates

Creating Modules

Package templates into distributable modules

Real-World Examples

Explore these templates in the source code:
  • CommandHandlerTemplate - Modules/Intent.Modules.Application.MediatR/Templates/CommandHandler/
  • DtoModelTemplate - Modules/Intent.Modules.Application.Dtos/Templates/DtoModel/
  • RepositoryTemplate - Modules/Intent.Modules.EntityFrameworkCore/Templates/Repository/

Build docs developers (and LLMs) love