Decorators
Decorators allow modules to extend and augment the output of templates without modifying the original template code. They implement a variation of the decorator design pattern specifically for code generation.
ITemplateDecorator Interface
All decorators implement the ITemplateDecorator interface:
public interface ITemplateDecorator
{
int Priority { get ; set ; }
}
Controls the execution order of decorators. Lower numbers execute first. Default is 0.
DecoratorBase Class
The DecoratorBase class provides a standard implementation:
using Intent . Plugins ;
using Intent . Templates ;
public class DecoratorBase : ITemplateDecorator , ISupportsConfiguration
{
private const string PRIORITY = "Priority" ;
public virtual void Configure ( IDictionary < string , string > settings )
{
if ( settings . ContainsKey ( PRIORITY ) && ! string . IsNullOrWhiteSpace ( settings [ PRIORITY ]))
{
this . Priority = int . Parse ( settings [ PRIORITY ]);
}
}
public int Priority { get ; set ; } = 0 ;
}
The Configure method allows decorator priority to be set through module configuration.
Creating Decorators
1. Define a Decorator Contract
First, define an interface that specifies what the decorator can contribute:
public interface IClassDecorator : ITemplateDecorator
{
string Fields ();
string Properties ();
string Methods ();
string Constructors ();
}
2. Implement the Decorator
public class ValidationDecorator : DecoratorBase , IClassDecorator
{
private readonly ClassTemplate _template ;
public ValidationDecorator ( ClassTemplate template )
{
_template = template ;
Priority = 10 ; // Execute after lower priority decorators
}
public string Fields ()
{
return string . Empty ; // No fields to add
}
public string Properties ()
{
return string . Empty ; // No properties to add
}
public string Methods ()
{
return @"
public bool IsValid()
{
return Validate().IsValid;
}
public ValidationResult Validate()
{
var validator = new " + _template . Model . Name + @"Validator();
return validator.Validate(this);
}" ;
}
public string Constructors ()
{
return string . Empty ;
}
}
Decorator Registration
Decorators are registered using DecoratorRegistration<TTemplate, TDecorator>:
using Intent . Engine ;
using Intent . Modules . Common . Registrations ;
public class ValidationDecoratorRegistration : DecoratorRegistration < ClassTemplate , IClassDecorator >
{
public override string DecoratorId => "Intent.Validation.ClassValidationDecorator" ;
public override IClassDecorator CreateDecoratorInstance ( ClassTemplate template , IApplication application )
{
return new ValidationDecorator ( template );
}
}
Decorator registrations must be referenced in the module’s .imodspec file to be discovered by the Software Factory.
Using Decorators in Templates
Templates that support decorators inherit from IntentTemplateBase<TModel, TDecorator>:
public class ClassTemplate : IntentTemplateBase < ClassModel , IClassDecorator >
{
public ClassTemplate ( string templateId , IOutputTarget outputTarget , ClassModel model )
: base ( templateId , outputTarget , model )
{
}
public override string TransformText ()
{
return $@"
namespace { Namespace }
{{
public class { ClassName }
{{
{ GetDecoratorsOutput ( x => x . Fields ())}
{ GetDecoratorsOutput ( x => x . Properties ())}
{ GetDecoratorsOutput ( x => x . Constructors ())}
{ GetDecoratorsOutput ( x => x . Methods ())}
}}
}}" ;
}
}
GetDecoratorsOutput Method
The GetDecoratorsOutput method aggregates output from all decorators:
protected string GetDecoratorsOutput ( Func < TDecorator , string > propertyFunc )
{
return GetDecorators (). Aggregate ( propertyFunc );
}
Retrieve Decorators
GetDecorators() returns all registered decorators sorted by Priority
Execute Property Function
The provided lambda is executed on each decorator
Aggregate Results
All non-empty results are concatenated with newlines
Decorator Execution Order
Decorators execute in order of their Priority property:
public class HighPriorityDecorator : DecoratorBase , IClassDecorator
{
public HighPriorityDecorator ()
{
Priority = - 10 ; // Executes first
}
}
public class NormalPriorityDecorator : DecoratorBase , IClassDecorator
{
public NormalPriorityDecorator ()
{
Priority = 0 ; // Default priority
}
}
public class LowPriorityDecorator : DecoratorBase , IClassDecorator
{
public LowPriorityDecorator ()
{
Priority = 100 ; // Executes last
}
}
Given decorators with priorities: -10, 0, 5, 100 Execution order:
Decorator with Priority -10 (first)
Decorator with Priority 0
Decorator with Priority 5
Decorator with Priority 100 (last)
The template concatenates all outputs in this order.
Real-World Example: Dependency Injection Decorator
Here’s a practical example that adds constructor dependency injection:
public interface IServiceDecorator : ITemplateDecorator
{
string ConstructorParameters ();
string Fields ();
string ConstructorAssignments ();
}
public class RepositoryDependencyDecorator : DecoratorBase , IServiceDecorator
{
private readonly ServiceTemplate _template ;
public RepositoryDependencyDecorator ( ServiceTemplate template )
{
_template = template ;
Priority = 0 ;
}
public string ConstructorParameters ()
{
if ( ! RequiresRepository ())
return string . Empty ;
return $"I { _template . Model . RepositoryName } repository" ;
}
public string Fields ()
{
if ( ! RequiresRepository ())
return string . Empty ;
return $"private readonly I { _template . Model . RepositoryName } _repository;" ;
}
public string ConstructorAssignments ()
{
if ( ! RequiresRepository ())
return string . Empty ;
return "_repository = repository;" ;
}
private bool RequiresRepository ()
{
return _template . Model . Operations . Any ( x => x . RequiresRepository );
}
}
Conditional Decoration
Decorators can conditionally contribute based on metadata:
public class AuditDecorator : DecoratorBase , IClassDecorator
{
private readonly ClassTemplate _template ;
public AuditDecorator ( ClassTemplate template )
{
_template = template ;
}
public string Properties ()
{
// Only add audit properties if stereotype is applied
if ( ! _template . Model . HasStereotype ( "Auditable" ))
return string . Empty ;
return @"
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime? ModifiedDate { get; set; }
public string ModifiedBy { get; set; }" ;
}
public string Fields () => string . Empty ;
public string Methods () => string . Empty ;
public string Constructors () => string . Empty ;
}
Decorator Configuration
Decorators can be configured in the .imodspec file:
< decorators >
< decorator id = "Intent.Validation.ClassValidationDecorator" >
< config >
< add key = "Priority" value = "10" />
</ config >
</ decorator >
</ decorators >
Use configuration to allow users to control decorator behavior without code changes.
Accessing Template Context
Decorators receive the template instance and can access:
Model : The metadata model bound to the template
ExecutionContext : Software Factory services
OutputTarget : Target location information
Type Resolution : Use template’s GetTypeName() methods
public class DependencyDecorator : DecoratorBase , IServiceDecorator
{
private readonly ServiceTemplate _template ;
public DependencyDecorator ( ServiceTemplate template )
{
_template = template ;
}
public string ConstructorParameters ()
{
var dependencies = _template . Model . Dependencies
. Select ( dep => $" { _template . GetTypeName ( dep . Type )} { dep . Name . ToCamelCase ()} " )
. ToList ();
return string . Join ( ", " , dependencies );
}
}
Decorator Lifecycle Hooks
Decorators can implement execution hooks:
public interface IDecoratorExecutionHooks
{
void BeforeTemplateExecution ( ITemplate template );
void AfterTemplateExecution ( ITemplate template , string templateOutput );
}
public class LoggingDecorator : DecoratorBase , IClassDecorator , IDecoratorExecutionHooks
{
public void BeforeTemplateExecution ( ITemplate template )
{
// Log or prepare before template runs
}
public void AfterTemplateExecution ( ITemplate template , string templateOutput )
{
// Inspect or modify output after template runs
}
public string Fields () => string . Empty ;
public string Properties () => string . Empty ;
public string Methods () => string . Empty ;
public string Constructors () => string . Empty ;
}
Best Practices
Single Responsibility Each decorator should add one cohesive feature
Use Priorities Set appropriate priorities to control execution order
Conditional Logic Return empty strings when decorator doesn’t apply
Access Template Use template context for type resolution and metadata
Common Patterns
Adding Interfaces
public string BaseTypes ()
{
if ( _template . Model . HasStereotype ( "Entity" ))
return "IEntity" ;
return string . Empty ;
}
Adding Using Statements
public string Usings ()
{
if ( RequiresValidation ())
return "using FluentValidation;" ;
return string . Empty ;
}
Adding Attributes
public string ClassAttributes ()
{
if ( _template . Model . HasStereotype ( "Serializable" ))
return "[Serializable]" ;
return string . Empty ;
}
Templates Learn about the template system
Modules Package decorators in modules
Metadata System Query metadata for conditional decoration