Templates
Templates are the core code generation mechanism in Intent Architect modules. They consume metadata from designers and produce source code files using C# classes and optional T4 text templates.
What is a Template?
A template is a C# class that implements the code generation logic for a specific output artifact. Templates can generate any text-based file, including:
C# classes and interfaces
Configuration files (JSON, XML, YAML)
Project files (.csproj)
Documentation (Markdown, HTML)
Infrastructure as Code (Terraform, Bicep)
Template Registration
Every template must be registered in the module’s .imodspec file:
< templates >
< template id = "Intent.Application.MediatR.CommandHandler"
externalReference = "4ba1d060-8722-4b1d-97e1-e7f070b8be2e" >
< config >
< add key = "ClassName" description = "Class name formula override (e.g. '${Model.Name}')" />
< add key = "Namespace" description = "Class namespace formula override (e.g. '${Project.Name}'" />
</ config >
< role > Application.Command.Handler </ role >
< location ></ location >
</ template >
</ templates >
Unique identifier for the template (used for template lookups)
GUID that uniquely identifies this template across versions
The template role - used for template resolution and type lookups
Default output location relative to the project root
Template Anatomy
A typical template consists of three files:
CommandHandlerTemplatePartial.cs
CommandHandlerTemplateRegistration.cs
CommandHandlerDecorator.cs
using Intent . Engine ;
using Intent . Modules . Common . CSharp . Templates ;
using Intent . Templates ;
namespace Intent . Modules . Application . MediatR . Templates . CommandHandler
{
public partial class CommandHandlerTemplate : CSharpTemplateBase < CommandModel , CommandHandlerDecorator >,
ICSharpFileBuilderTemplate
{
public const string TemplateId = "Intent.Application.MediatR.CommandHandler" ;
public CommandHandlerTemplate ( IOutputTarget outputTarget , CommandModel model )
: base ( TemplateId , outputTarget , model )
{
CSharpFile = new CSharpFile ( $" { this . GetCommandNamespace ()} " ,
$" { this . GetCommandFolderPath ()} " );
Configure ( this , model );
}
internal static void Configure ( ICSharpFileBuilderTemplate template , CommandModel model )
{
template . AddNugetDependency ( NugetPackages . MediatR ( template . OutputTarget ));
template . CSharpFile
. AddUsing ( "System" )
. AddUsing ( "System.Threading" )
. AddUsing ( "System.Threading.Tasks" )
. AddUsing ( "MediatR" )
. AddClass ( $" { model . Name } Handler" , @class =>
{
@class . ImplementsInterface ( GetRequestHandlerInterface ( template , model ));
@class . AddConstructor ( ctor =>
{
ctor . AddAttribute ( CSharpIntentManagedAttribute . Merge ());
});
@class . AddMethod ( GetReturnType ( template , model ), "Handle" , method =>
{
method . Async ();
method . AddParameter ( GetCommandModelName ( template , model ), "request" );
method . AddParameter ( "CancellationToken" , "cancellationToken" );
method . AddStatement ( "throw new NotImplementedException();" );
});
});
}
public CSharpFile CSharpFile { get ; }
public override string TransformText ()
{
return CSharpFile . ToString ();
}
}
}
Template Types
1. File Per Model Templates
Generate one file for each model in the metadata:
public class CommandHandlerTemplateRegistration : FilePerModelTemplateRegistration < CommandModel >
{
public override IEnumerable < CommandModel > GetModels ( IApplication application )
{
return _metadataManager . Services ( application ). GetCommandModels ();
}
}
Used for generating entity classes, DTOs, handlers, controllers, etc. - one file per metadata element.
2. Single File Templates
Generate a single file per project:
public class DependencyInjectionTemplateRegistration : SingleFileListModelTemplateRegistration
{
public override ITemplate CreateTemplateInstance ( IOutputTarget outputTarget )
{
return new DependencyInjectionTemplate ( outputTarget , null );
}
}
Used for startup configuration, dependency injection setup, solution files, etc.
3. T4 Templates (Legacy)
Older template style using T4 text templates:
<#@ template language="C#" inherits="CSharpTemplateBase<object>" #>
<#@ import namespace="Intent.Modules.Common.CSharp.Templates" #>
using System;
using System.Threading;
namespace <#= Namespace #>;
public interface <#= ClassName #>
{
Task<Uri> GetAsync(string bucketName, string key, CancellationToken cancellationToken = default);
}
T4 templates are legacy. New modules should use the CSharpFile Builder API for better maintainability and tooling support.
CSharpFile Builder API
The modern approach uses a fluent API for building C# code:
template . CSharpFile
. AddUsing ( "System" )
. AddUsing ( "System.Threading.Tasks" )
. AddUsing ( "MediatR" )
. AddClass ( $" { model . Name } Handler" , @class =>
{
@class . AddAttribute ( "IntentManaged(Mode.Merge, Signature = Mode.Fully)" );
@class . ImplementsInterface ( $"IRequestHandler< { commandModel } >" );
@class . AddConstructor ( ctor =>
{
ctor . AddParameter ( "ILogger<Handler>" , "logger" , param =>
{
param . IntroduceReadonlyField ();
});
});
@class . AddMethod ( "Task" , "Handle" , method =>
{
method . Async ();
method . AddParameter ( commandModel , "request" );
method . AddParameter ( "CancellationToken" , "cancellationToken" );
method . AddStatement ( "throw new NotImplementedException();" );
});
});
Benefits
Type Safety Compile-time validation of generated code structure
Refactoring Support IDE can refactor template code directly
IntelliSense Full IntelliSense support while writing templates
Testability Easier to unit test template logic
Template Decorators
Decorators allow modules to extend templates from other modules without modifying them:
public class MyCommandHandlerDecorator : CommandHandlerDecorator
{
public MyCommandHandlerDecorator ()
{
Priority = 10 ; // Higher priority decorators execute last
}
public override void BeforeTemplateExecution ()
{
// Add custom logic before template generates
}
}
Decorators are powerful for:
Adding attributes to generated classes
Injecting additional dependencies
Modifying method signatures
Adding custom validation logic
Template Configuration
Templates can accept user configuration:
< template id = "Intent.Application.MediatR.CommandHandler" >
< config >
< add key = "ClassName" description = "Class name formula override" />
< add key = "Namespace" description = "Class namespace formula override" />
</ config >
</ template >
Access configuration in your template:
protected override CSharpFileConfig DefineFileConfig ()
{
var className = GetConfig ( "ClassName" ) ?? $" { Model . Name } Handler" ;
var @namespace = GetConfig ( "Namespace" ) ?? GetNamespace ();
return new CSharpFileConfig (
className : className ,
@namespace : @namespace );
}
Template Roles
Roles identify what a template generates and enable type resolution:
Application Layer
Domain Layer
Infrastructure Layer
Distribution Layer
Application.Command
Application.Command.Handler
Application.Query
Application.Query.Handler
Application.Contract.Dto
Domain.Entity
Domain.ValueObject
Domain.DomainService
Domain.Repository.Interface
Infrastructure.DependencyInjection
Infrastructure.Data.DbContext
Infrastructure.Data.Repository
Distribution.WebApi.Controller
Distribution.Dto
Distribution.ExceptionFilter
Best Practices
Use CSharpFile Builder over T4
The builder API provides type safety, better tooling support, and easier testing.
Each template should generate one type of artifact. Don’t combine multiple concerns.
Use decorators to extend templates instead of modifying them directly.
Use Intent Managed Attributes
Apply IntentManaged attributes to control code merging behavior:
Mode.Fully - Completely managed by Intent
Mode.Merge - Merge with user code
Mode.Ignore - Never overwrite
Example: Complete Template
Here’s a real-world example from the codebase:
ObjectStorageInterfaceTemplate.tt
< #@ template language = "C#" inherits = "CSharpTemplateBase<object>" # >
using System ;
using System . Collections . Generic ;
using System . IO ;
using System . Threading ;
using System . Threading . Tasks ;
[ assembly : DefaultIntentManaged ( Mode . Fully )]
namespace <#= Namespace #>;
public record BulkObjectItem ( string Name , Stream DataStream );
public interface <#= ClassName #>
{
Task < Uri > GetAsync ( string bucketName , string key , CancellationToken cancellationToken = default );
IAsyncEnumerable < Uri > ListAsync ( string bucketName , CancellationToken cancellationToken = default );
Task < Uri > UploadAsync ( Uri cloudStorageLocation , Stream dataStream , CancellationToken cancellationToken = default );
IAsyncEnumerable < Uri > BulkUploadAsync ( string bucketName , IEnumerable < BulkObjectItem > objects , CancellationToken cancellationToken = default );
Task < Stream > DownloadAsync ( Uri cloudStorageLocation , CancellationToken cancellationToken = default );
Task DeleteAsync ( Uri cloudStorageLocation , CancellationToken cancellationToken = default );
}
Next Steps
Metadata Learn how templates consume metadata
Dependencies Understand how templates reference other templates