Skip to main content

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; }
}
Priority
int
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);
}
1

Retrieve Decorators

GetDecorators() returns all registered decorators sorted by Priority
2

Execute Property Function

The provided lambda is executed on each decorator
3

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, 100Execution order:
  1. Decorator with Priority -10 (first)
  2. Decorator with Priority 0
  3. Decorator with Priority 5
  4. 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

Build docs developers (and LLMs) love