Skip to main content

Factory Extensions

Factory Extensions allow modules to participate in the Software Factory execution lifecycle by hooking into specific phases of code generation. They enable cross-cutting concerns like logging, file manipulation, validation, and custom processing.

FactoryExtensionBase Class

All factory extensions inherit from FactoryExtensionBase:
using Intent.Engine;
using Intent.Plugins;

public abstract class FactoryExtensionBase : IExecutionLifeCycle, 
    IFactoryExtension, 
    ISupportsConfiguration
{
    public abstract string Id { get; }
    public virtual int Order { get; set; }

    public virtual void Configure(IDictionary<string, string> settings)
    {
        if (settings.ContainsKey(nameof(Order)) && 
            !string.IsNullOrWhiteSpace(settings[nameof(Order)]))
        {
            Order = int.Parse(settings[nameof(Order)]);
        }
    }

    public virtual void OnStep(IApplication application, string step) { }
    protected virtual void OnStart(IApplication application) { }
    protected virtual void OnBeforeMetadataLoad(IApplication application) { }
    protected virtual void OnAfterMetadataLoad(IApplication application) { }
    protected virtual void OnBeforeTemplateRegistrations(IApplication application) { }
    protected virtual void OnAfterTemplateRegistrations(IApplication application) { }
    protected virtual void OnBeforeTemplateExecution(IApplication application) { }
    protected virtual void OnAfterTemplateExecution(IApplication application) { }
    protected virtual void OnBeforeCommitChanges(IApplication application) { }
    protected virtual void OnAfterCommitChanges(IApplication application) { }
}

Lifecycle Phases

The Software Factory executes in distinct phases, each with a corresponding hook:
1

Start

OnStart() - First phase, before any metadata is loaded
2

Before Metadata Load

OnBeforeMetadataLoad() - Before loading designer files
3

After Metadata Load

OnAfterMetadataLoad() - Metadata models are available
4

Before Template Registrations

OnBeforeTemplateRegistrations() - Before templates are registered
5

After Template Registrations

OnAfterTemplateRegistrations() - All templates are registered and discoverable
6

Before Template Execution

OnBeforeTemplateExecution() - Before templates generate code
7

After Template Execution

OnAfterTemplateExecution() - All code has been generated
8

Before Commit Changes

OnBeforeCommitChanges() - Before files are written to disk
9

After Commit Changes

OnAfterCommitChanges() - Final phase, files have been written

Creating a Factory Extension

Basic Implementation

public class LoggingFactoryExtension : FactoryExtensionBase
{
    public override string Id => "MyCompany.LoggingFactoryExtension";

    protected override void OnStart(IApplication application)
    {
        Logging.Log.Info("Software Factory execution started");
    }

    protected override void OnAfterTemplateExecution(IApplication application)
    {
        var templateCount = application.OutputTargets
            .SelectMany(x => x.Templates)
            .Count();
            
        Logging.Log.Info($"Generated {templateCount} files");
    }

    protected override void OnAfterCommitChanges(IApplication application)
    {
        Logging.Log.Info("Software Factory execution completed");
    }
}

Real-World Examples

File Builder Factory Extension

This example from the source code shows how to work with file builders:
public class CSharpFileBuilderFactoryExtension : FactoryExtensionBase
{
    public override string Id => "Intent.Common.CSharp.CSharpFileBuilderFactoryExtension";
    public override int Order => -100; // Execute early

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        // Access all templates that implement ICSharpFileBuilderTemplate
        var templates = application.OutputTargets
            .SelectMany(x => x.Templates)
            .OfType<ICSharpFileBuilderTemplate>()
            .ToList();

        foreach (var template in templates)
        {
            // Configure file builder for each template
            ConfigureCSharpFile(template.CSharpFile, application);
        }
    }

    private void ConfigureCSharpFile(ICSharpFile file, IApplication application)
    {
        // Apply application-wide C# style settings
        var settings = application.Settings.GetCSharpStyleSettings();
        file.ApplySettings(settings);
    }
}

Decorator Execution Hooks

From Intent.Common, this extension manages decorator lifecycle:
public class DecoratorExecutionHooksFactoryExtension : FactoryExtensionBase
{
    public override string Id => "Intent.Common.DecoratorExecutionHooks";

    protected override void OnBeforeTemplateExecution(IApplication application)
    {
        var templatesWithDecorators = application.OutputTargets
            .SelectMany(x => x.Templates)
            .OfType<IHasDecorators>()
            .ToList();

        foreach (var template in templatesWithDecorators)
        {
            var decorators = template.GetDecorators()
                .OfType<IDecoratorExecutionHooks>()
                .ToList();

            foreach (var decorator in decorators)
            {
                decorator.BeforeTemplateExecution(template);
            }
        }
    }

    protected override void OnAfterTemplateExecution(IApplication application)
    {
        var outputs = application.OutputTargets
            .SelectMany(x => x.Templates)
            .OfType<IHasDecorators>()
            .Select(t => new { Template = t, Output = t.RunTemplate() })
            .ToList();

        foreach (var output in outputs)
        {
            var decorators = output.Template.GetDecorators()
                .OfType<IDecoratorExecutionHooks>()
                .ToList();

            foreach (var decorator in decorators)
            {
                decorator.AfterTemplateExecution(output.Template, output.Output);
            }
        }
    }
}

Accessing Application Services

The IApplication parameter provides access to:
application.MetadataManager
IMetadataManager
Access to all loaded metadata models
application.OutputTargets
IEnumerable<IOutputTarget>
All output targets (projects) and their templates
application.Settings
IApplicationSettings
Application and module settings
application.EventDispatcher
ISoftwareFactoryEventDispatcher
Publish and subscribe to events

Working with Templates

protected override void OnAfterTemplateRegistrations(IApplication application)
{
    // Get all templates of a specific type
    var serviceTemplates = application.OutputTargets
        .SelectMany(x => x.Templates)
        .Where(t => t.Id == "Intent.Application.ServiceTemplate")
        .ToList();

    // Access template metadata
    foreach (var template in serviceTemplates)
    {
        var fileConfig = template.GetTemplateFileConfig();
        Logging.Log.Info($"Template will generate: {fileConfig.FileName}");
    }
}

Working with Metadata

protected override void OnAfterMetadataLoad(IApplication application)
{
    // Access domain models
    var domainModels = application.MetadataManager
        .Domain(application)
        .GetDomainModels();

    // Validate metadata
    foreach (var model in domainModels)
    {
        if (string.IsNullOrWhiteSpace(model.Comment))
        {
            Logging.Log.Warning($"Domain class '{model.Name}' has no documentation");
        }
    }
}

Execution Order

Factory extensions execute in order determined by their Order property:
public class HighPriorityExtension : FactoryExtensionBase
{
    public override string Id => "MyCompany.HighPriorityExtension";
    public override int Order => -100; // Executes early
}

public class LowPriorityExtension : FactoryExtensionBase
{
    public override string Id => "MyCompany.LowPriorityExtension";
    public override int Order => 100; // Executes late
}
Lower order values execute first. Default order is 0.

Configuration

Factory extensions can be configured in the module’s .imodspec file:
<factoryExtensions>
  <factoryExtension id="Intent.Common.CSharp.CSharpFileBuilderFactoryExtension" />
  <factoryExtension id="MyCompany.CustomExtension" externalReference="guid-here">
    <config>
      <add key="Order" value="50" />
      <add key="EnableLogging" value="true" />
    </config>
  </factoryExtension>
</factoryExtensions>
Access configuration in the extension:
public class CustomExtension : FactoryExtensionBase
{
    private bool _enableLogging;

    public override string Id => "MyCompany.CustomExtension";

    public override void Configure(IDictionary<string, string> settings)
    {
        base.Configure(settings);
        
        if (settings.ContainsKey("EnableLogging"))
        {
            _enableLogging = bool.Parse(settings["EnableLogging"]);
        }
    }

    protected override void OnStart(IApplication application)
    {
        if (_enableLogging)
        {
            Logging.Log.Info("Custom extension started");
        }
    }
}

Common Use Cases

Validation

protected override void OnAfterMetadataLoad(IApplication application)
{
    var validator = new MetadataValidator(application);
    var errors = validator.Validate();
    
    if (errors.Any())
    {
        foreach (var error in errors)
        {
            Logging.Log.Error(error);
        }
        throw new Exception("Metadata validation failed");
    }
}

File Generation

protected override void OnAfterTemplateExecution(IApplication application)
{
    // Generate a summary file
    var summary = new StringBuilder();
    summary.AppendLine("Generated Files:");
    
    foreach (var target in application.OutputTargets)
    {
        foreach (var template in target.Templates)
        {
            var config = template.GetTemplateFileConfig();
            summary.AppendLine($"  - {config.FileName}");
        }
    }
    
    File.WriteAllText("generation-summary.txt", summary.ToString());
}

Post-Processing

protected override void OnAfterCommitChanges(IApplication application)
{
    // Run code formatters
    var csharpFiles = Directory.GetFiles(
        application.RootLocation, 
        "*.cs", 
        SearchOption.AllDirectories);
    
    foreach (var file in csharpFiles)
    {
        FormatCSharpFile(file);
    }
}

Template Lifecycle Hooks Extension

Factory extensions can invoke template lifecycle hooks:
public class TemplateLifeCycleHooksFactoryExtension : FactoryExtensionBase
{
    public override string Id => "Intent.Common.TemplateLifeCycleHooks";

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var templates = application.OutputTargets
            .SelectMany(x => x.Templates)
            .OfType<IAfterTemplateRegistrationExecutionHook>()
            .ToList();

        foreach (var template in templates)
        {
            template.AfterTemplateRegistration();
        }
    }

    protected override void OnBeforeTemplateExecution(IApplication application)
    {
        var templates = application.OutputTargets
            .SelectMany(x => x.Templates)
            .OfType<ITemplateBeforeExecutionHook>()
            .ToList();

        foreach (var template in templates)
        {
            template.BeforeTemplateExecution();
        }
    }
}

Best Practices

Use Appropriate Hooks

Choose the right lifecycle phase for your operation

Set Order Carefully

Consider dependencies when setting execution order

Handle Errors

Wrap operations in try-catch to provide clear error messages

Log Activities

Use logging to help users understand what’s happening

Performance Considerations

Factory extensions execute for every Software Factory run. Keep operations efficient, especially in frequently-called hooks like OnAfterTemplateExecution.

Optimization Tips

  • Cache expensive computations
  • Filter templates early to avoid unnecessary processing
  • Use parallel processing for independent operations
  • Avoid file I/O in early hooks when possible
protected override void OnAfterTemplateExecution(IApplication application)
{
    // Bad: Processes every template
    foreach (var template in application.OutputTargets.SelectMany(x => x.Templates))
    {
        // Expensive operation
    }

    // Good: Filter first
    var relevantTemplates = application.OutputTargets
        .SelectMany(x => x.Templates)
        .OfType<IRequiresProcessing>()
        .ToList();

    foreach (var template in relevantTemplates)
    {
        // Expensive operation only on relevant templates
    }
}

Templates

Work with templates in factory extensions

Modules

Package factory extensions in modules

Decorators

Coordinate with decorator execution