Skip to main content
Factory extensions allow you to hook into the Software Factory’s execution lifecycle and perform custom logic at specific points during code generation.

What are Factory Extensions?

Factory extensions are classes that:
  • Execute at specific phases of Software Factory execution
  • Can modify application configuration
  • Access all templates and metadata
  • Perform validation and verification
  • Generate additional outputs or perform cleanup
  • Integrate with external systems

Software Factory Lifecycle

The Software Factory executes in these phases:
1
Start
2
Software Factory initializes, loads configuration
3
AfterMetadataLoad
4
Metadata from designers is loaded and available
5
AfterTemplateRegistrations
6
All templates are registered but not yet executed
7
BeforeTemplateExecution
8
About to execute templates and generate code
9
AfterTemplateExecution
10
All templates have executed, files generated
11
End
12
Execution complete, cleanup can occur

Creating a Factory Extension

1
Create in Module Builder Designer
2
  • Right-click your module
  • Select New Factory Extension
  • Name it (e.g., ValidationFactoryExtension)
  • 3
    Run Software Factory
    4
    Generate the factory extension code:
    5
    intent run-software-factory
    
    6
    Creates:
    7
  • FactoryExtensions/ValidationFactoryExtension.cs
  • Entry in .imodspec file
  • 8
    Implement the Extension
    9
    Edit the generated class:
    10
    using Intent.Engine;
    using Intent.Modules.Common;
    using Intent.Modules.Common.Plugins;
    using Intent.Plugins.FactoryExtensions;
    
    namespace MyModule.FactoryExtensions
    {
        public class ValidationFactoryExtension : FactoryExtensionBase
        {
            public override string Id => "MyModule.ValidationFactoryExtension";
    
            public override int Order => 0;
    
            protected override void OnAfterTemplateRegistrations(
                IApplication application)
            {
                // Validate that required templates are present
                var requiredTemplates = new[] 
                { 
                    "Intent.Common.CSharp.Templates.Startup",
                    "Intent.AspNetCore.Controllers.Controller"
                };
    
                foreach (var templateId in requiredTemplates)
                {
                    var templates = application.FindTemplateInstances(templateId);
                    if (!templates.Any())
                    {
                        throw new Exception(
                            $"Required template '{templateId}' is not registered.");
                    }
                }
            }
    
            protected override void OnBeforeTemplateExecution(
                IApplication application)
            {
                // Perform additional setup before generation
                ConfigureOutputPaths(application);
                ValidateMetadata(application);
            }
    
            protected override void OnAfterTemplateExecution(
                IApplication application)
            {
                // Post-generation tasks
                GenerateAdditionalFiles(application);
                LogSummary(application);
            }
    
            private void ConfigureOutputPaths(IApplication application)
            {
                // Custom path configuration
            }
    
            private void ValidateMetadata(IApplication application)
            {
                // Metadata validation logic
            }
    
            private void GenerateAdditionalFiles(IApplication application)
            {
                // Generate supplementary files
            }
    
            private void LogSummary(IApplication application)
            {
                // Log generation summary
            }
        }
    }
    

    Lifecycle Hook Methods

    OnStart

    Executes at the very beginning:
    protected override void OnStart(IApplication application)
    {
        Logging.Log.Info("Starting code generation...");
        
        // Initialize external connections
        InitializeDatabase();
        
        // Load external configuration
        LoadExternalSettings(application);
    }
    

    OnAfterMetadataLoad

    After metadata is loaded, before template registration:
    protected override void OnAfterMetadataLoad(IApplication application)
    {
        // Validate metadata is correct
        var entities = application.MetadataManager
            .Domain(application)
            .GetClassModels()
            .Where(x => x.HasStereotype("Entity"));
    
        foreach (var entity in entities)
        {
            if (!entity.Properties.Any(p => p.Name == "Id"))
            {
                Logging.Log.Warning(
                    $"Entity '{entity.Name}' is missing an Id property.");
            }
        }
    
        // Transform metadata if needed
        NormalizeEntityNames(application);
    }
    

    OnAfterTemplateRegistrations

    After all templates are registered:
    protected override void OnAfterTemplateRegistrations(
        IApplication application)
    {
        // Register additional templates dynamically
        var serviceTemplates = application
            .FindTemplateInstances("MyModule.ServiceTemplate");
    
        foreach (var serviceTemplate in serviceTemplates)
        {
            // Register corresponding test template
            var testTemplate = new ServiceTestTemplate(
                serviceTemplate.OutputTarget,
                serviceTemplate.Model);
                
            application.RegisterTemplate(testTemplate);
        }
    
        // Verify template dependencies
        ValidateTemplateDependencies(application);
    }
    

    OnBeforeTemplateExecution

    Just before templates execute:
    protected override void OnBeforeTemplateExecution(
        IApplication application)
    {
        // Set up output directories
        EnsureOutputDirectoriesExist(application);
    
        // Configure templates
        ConfigureTemplateSettings(application);
    
        // Perform final validation
        if (!ValidateConfiguration(application))
        {
            throw new InvalidOperationException(
                "Configuration validation failed.");
        }
    }
    

    OnAfterTemplateExecution

    After all templates have executed:
    protected override void OnAfterTemplateExecution(
        IApplication application)
    {
        // Generate index files
        GenerateIndexFiles(application);
    
        // Run post-processing
        FormatGeneratedCode(application);
    
        // Generate reports
        GenerateCodeGenerationReport(application);
    
        // Update external systems
        SyncWithExternalSystem(application);
    }
    

    OnEnd

    Final cleanup:
    protected override void OnEnd(IApplication application)
    {
        // Close external connections
        CloseDatabase();
    
        // Log completion
        Logging.Log.Info("Code generation completed successfully.");
    
        // Cleanup temporary files
        CleanupTempFiles();
    }
    

    Common Factory Extension Patterns

    Template Validation

    Ensure required templates are present:
    public class TemplateValidationExtension : FactoryExtensionBase
    {
        public override string Id => "MyModule.TemplateValidation";
    
        protected override void OnAfterTemplateRegistrations(
            IApplication application)
        {
            var requiredTemplates = new Dictionary<string, string>
            {
                ["MyModule.EntityTemplate"] = "Entity templates are required",
                ["MyModule.RepositoryTemplate"] = "Repository templates are required"
            };
    
            foreach (var (templateId, message) in requiredTemplates)
            {
                if (!application.FindTemplateInstances(templateId).Any())
                {
                    Logging.Log.Warning(message);
                }
            }
        }
    }
    

    Metadata Transformation

    Modify metadata before generation:
    public class MetadataTransformExtension : FactoryExtensionBase
    {
        public override string Id => "MyModule.MetadataTransform";
    
        protected override void OnAfterMetadataLoad(IApplication application)
        {
            var classes = application.MetadataManager
                .Domain(application)
                .GetClassModels();
    
            foreach (var @class in classes)
            {
                // Auto-add Id property if missing
                if (!@class.Properties.Any(p => p.Name == "Id"))
                {
                    // Note: Metadata is typically read-only
                    // This is for illustration
                    Logging.Log.Info(
                        $"Class {@class.Name} should have an Id property");
                }
    
                // Normalize naming conventions
                NormalizePropertyNames(@class);
            }
        }
    
        private void NormalizePropertyNames(ClassModel @class)
        {
            // Normalization logic
        }
    }
    

    Dynamic Template Registration

    Register templates based on conditions:
    public class DynamicTemplateExtension : FactoryExtensionBase
    {
        public override string Id => "MyModule.DynamicTemplates";
    
        protected override void OnAfterTemplateRegistrations(
            IApplication application)
        {
            // Find all entities that need repositories
            var entities = application.MetadataManager
                .Domain(application)
                .GetClassModels()
                .Where(x => x.HasStereotype("Entity"))
                .Where(x => x.GetStereotypeProperty<bool>(
                    "Entity", "RequireRepository", true));
    
            // Dynamically register repository templates
            foreach (var entity in entities)
            {
                var outputTarget = application.OutputTargets
                    .FirstOrDefault(x => x.HasRole("Infrastructure"));
    
                if (outputTarget != null)
                {
                    var template = new RepositoryTemplate(
                        outputTarget, entity);
                    application.RegisterTemplate(template);
                }
            }
        }
    }
    

    Cross-Cutting Concerns

    Add logging, caching, or validation:
    public class CrossCuttingExtension : FactoryExtensionBase
    {
        public override string Id => "MyModule.CrossCutting";
        public override int Order => 100; // Execute after others
    
        protected override void OnBeforeTemplateExecution(
            IApplication application)
        {
            // Apply cross-cutting concerns to all service templates
            var serviceTemplates = application
                .FindTemplateInstances<IServiceTemplate>()
                .ToList();
    
            foreach (var template in serviceTemplates)
            {
                // Add logging
                if (ShouldAddLogging(template))
                {
                    template.AddLoggingSupport();
                }
    
                // Add caching
                if (ShouldAddCaching(template))
                {
                    template.AddCachingSupport();
                }
    
                // Add validation
                if (ShouldAddValidation(template))
                {
                    template.AddValidationSupport();
                }
            }
        }
    
        private bool ShouldAddLogging(IServiceTemplate template)
        {
            return template.Model.HasStereotype("RequiresLogging");
        }
    
        private bool ShouldAddCaching(IServiceTemplate template)
        {
            return template.Model.HasStereotype("Cacheable");
        }
    
        private bool ShouldAddValidation(IServiceTemplate template)
        {
            return template.Model.Properties
                .Any(p => p.HasStereotype("Required"));
        }
    }
    

    Code Generation Reports

    Generate summary reports:
    public class ReportingExtension : FactoryExtensionBase
    {
        public override string Id => "MyModule.Reporting";
    
        private readonly List<string> _generatedFiles = new();
        private DateTime _startTime;
    
        protected override void OnStart(IApplication application)
        {
            _startTime = DateTime.Now;
        }
    
        protected override void OnAfterTemplateExecution(
            IApplication application)
        {
            // Collect generated file information
            var templates = application.FindTemplateInstances<ITemplate>();
            
            foreach (var template in templates)
            {
                var fileMetadata = template.GetMetadata();
                _generatedFiles.Add(
                    $"{fileMetadata.GetFilePath()} ({fileMetadata.FileMetadata.CodeGenType})");
            }
    
            // Generate report
            GenerateReport(application);
        }
    
        private void GenerateReport(IApplication application)
        {
            var duration = DateTime.Now - _startTime;
            var reportPath = Path.Combine(
                application.OutputRootDirectory,
                "generation-report.txt");
    
            var report = $@"
    Code Generation Report
    =====================
    Application: {application.ApplicationName}
    Generated: {DateTime.Now}
    Duration: {duration.TotalSeconds:F2} seconds
    Files Generated: {_generatedFiles.Count}
    
    Generated Files:
    {string.Join("\n", _generatedFiles.Select(f => $"  - {f}"))}
    ";
    
            File.WriteAllText(reportPath, report);
            Logging.Log.Info($"Report generated: {reportPath}");
        }
    }
    

    External System Integration

    Integrate with external tools or APIs:
    public class ExternalIntegrationExtension : FactoryExtensionBase
    {
        public override string Id => "MyModule.ExternalIntegration";
    
        private HttpClient _httpClient;
    
        protected override void OnStart(IApplication application)
        {
            _httpClient = new HttpClient
            {
                BaseAddress = new Uri(
                    application.Settings.GetSetting(
                        "MyModule.ApiEndpoint", 
                        "https://api.example.com"))
            };
        }
    
        protected override void OnAfterMetadataLoad(IApplication application)
        {
            // Fetch external data
            var externalConfig = FetchExternalConfiguration().Result;
            
            // Store for use by templates
            application.EventDispatcher.Publish(
                new ExternalConfigLoadedEvent(externalConfig));
        }
    
        protected override void OnAfterTemplateExecution(
            IApplication application)
        {
            // Send generation results to external system
            var generatedFiles = CollectGeneratedFiles(application);
            SendToExternalSystem(generatedFiles).Wait();
        }
    
        protected override void OnEnd(IApplication application)
        {
            _httpClient?.Dispose();
        }
    
        private async Task<ExternalConfig> FetchExternalConfiguration()
        {
            var response = await _httpClient.GetAsync("/api/config");
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsAsync<ExternalConfig>();
        }
    
        private async Task SendToExternalSystem(List<FileInfo> files)
        {
            var payload = new { files };
            await _httpClient.PostAsJsonAsync("/api/generated", payload);
        }
    
        private List<FileInfo> CollectGeneratedFiles(IApplication application)
        {
            // Collect file information
            return new List<FileInfo>();
        }
    }
    

    Execution Order

    Control execution order with the Order property:
    public class EarlyExtension : FactoryExtensionBase
    {
        public override int Order => -100; // Executes early
        // ...
    }
    
    public class NormalExtension : FactoryExtensionBase
    {
        public override int Order => 0; // Default order
        // ...
    }
    
    public class LateExtension : FactoryExtensionBase
    {
        public override int Order => 100; // Executes late
        // ...
    }
    
    Lower numbers execute first. Use this to control dependencies between extensions.

    Accessing Application Context

    Application Settings

    var setting = application.Settings.GetSetting(
        "MyModule.SettingName", 
        "DefaultValue");
    
    var enableFeature = application.Settings
        .GetSetting("MyModule.EnableFeature", "true") == "true";
    

    Metadata Manager

    var metadataManager = application.MetadataManager;
    
    // Access domain metadata
    var entities = metadataManager
        .Domain(application)
        .GetClassModels()
        .Where(x => x.HasStereotype("Entity"));
    
    // Access services metadata
    var services = metadataManager
        .Services(application)
        .GetClassModels();
    

    Output Targets

    var apiTarget = application.OutputTargets
        .FirstOrDefault(x => x.HasRole("Api"));
    
    var infrastructureTarget = application.OutputTargets
        .FirstOrDefault(x => x.HasRole("Infrastructure"));
    
    foreach (var target in application.OutputTargets)
    {
        Logging.Log.Info($"Output Target: {target.Name}");
    }
    

    Finding Templates

    // Find all instances of a template
    var templates = application.FindTemplateInstances(
        "MyModule.EntityTemplate");
    
    // Find with interface
    var serviceTemplates = application
        .FindTemplateInstances<IServiceTemplate>();
    
    // Find specific instance
    var template = application.FindTemplateInstance<IClassProvider>(
        TemplateDependency.OnModel("MyModule.EntityTemplate", entity));
    

    Event Dispatcher

    // Publish custom event
    application.EventDispatcher.Publish(
        new CustomValidationEvent(validationResults));
    
    // Subscribe to events
    application.EventDispatcher.Subscribe<TemplateRegistrationEvent>(
        evt => HandleTemplateRegistration(evt));
    

    Error Handling

    Throwing Exceptions

    Stop execution with clear error messages:
    protected override void OnAfterMetadataLoad(IApplication application)
    {
        var entities = GetEntities(application);
        
        if (!entities.Any())
        {
            throw new InvalidOperationException(
                "No entities found. At least one entity is required.");
        }
    
        var duplicates = entities
            .GroupBy(e => e.Name)
            .Where(g => g.Count() > 1)
            .Select(g => g.Key);
    
        if (duplicates.Any())
        {
            throw new InvalidOperationException(
                $"Duplicate entity names found: {string.Join(", ", duplicates)}");
        }
    }
    

    Logging Warnings

    Warn without stopping execution:
    protected override void OnBeforeTemplateExecution(
        IApplication application)
    {
        var entities = GetEntities(application);
    
        foreach (var entity in entities)
        {
            if (!entity.Properties.Any())
            {
                Logging.Log.Warning(
                    $"Entity '{entity.Name}' has no properties.");
            }
    
            if (entity.Name.Length > 50)
            {
                Logging.Log.Warning(
                    $"Entity name '{entity.Name}' is very long (>50 chars).");
            }
        }
    }
    

    Best Practices

    Keep Extensions Focused

    Each extension should have a single responsibility:
    // Good: Focused extensions
    public class ValidationExtension : FactoryExtensionBase { }
    public class ReportingExtension : FactoryExtensionBase { }
    public class IntegrationExtension : FactoryExtensionBase { }
    
    // Bad: Do-everything extension
    public class EverythingExtension : FactoryExtensionBase { }
    

    Use Appropriate Lifecycle Hooks

    Execute logic at the right time:
    // Metadata validation -> OnAfterMetadataLoad
    // Template validation -> OnAfterTemplateRegistrations  
    // Template setup -> OnBeforeTemplateExecution
    // Post-processing -> OnAfterTemplateExecution
    // Cleanup -> OnEnd
    

    Handle Errors Gracefully

    protected override void OnAfterTemplateExecution(
        IApplication application)
    {
        try
        {
            GenerateReport(application);
        }
        catch (Exception ex)
        {
            Logging.Log.Warning(
                $"Failed to generate report: {ex.Message}");
            // Don't throw - report generation is not critical
        }
    }
    

    Log Important Actions

    protected override void OnStart(IApplication application)
    {
        Logging.Log.Info("Starting MyModule processing...");
    }
    
    protected override void OnAfterTemplateExecution(
        IApplication application)
    {
        var fileCount = CountGeneratedFiles(application);
        Logging.Log.Info($"Generated {fileCount} files.");
    }
    

    Testing Factory Extensions

    Unit Testing

    [Fact]
    public void ValidationExtension_ThrowsWhenNoEntities()
    {
        // Arrange
        var mockApp = new Mock<IApplication>();
        mockApp.Setup(a => a.MetadataManager.Domain(It.IsAny<IApplication>()))
            .Returns(new EmptyDomainMetadata());
    
        var extension = new ValidationExtension();
    
        // Act & Assert
        Assert.Throws<InvalidOperationException>(
            () => extension.OnAfterMetadataLoad(mockApp.Object));
    }
    

    Integration Testing

    Test with actual application:
    [Fact]
    public async Task Extension_GeneratesReport()
    {
        // Arrange
        var application = CreateTestApplication();
        application.InstallModule("MyModule.WithExtension");
    
        // Act
        await application.RunSoftwareFactory();
    
        // Assert
        var reportPath = Path.Combine(
            application.OutputRoot, 
            "generation-report.txt");
        Assert.True(File.Exists(reportPath));
    }
    

    Troubleshooting

    • Verify extension is registered in .imodspec
    • Check module is installed in application
    • Ensure no exceptions in extension constructor
    • Review Software Factory logs for errors
    • Check Order property values
    • Lower numbers execute first
    • Use negative numbers for early execution
    • Review other extensions’ order values
    • Ensure accessing in correct lifecycle phase
    • Templates available after OnAfterTemplateRegistrations
    • Use correct template ID or interface
    • Check template is actually registered

    Next Steps

    Creating Templates

    Learn to create code generation templates

    Creating Decorators

    Modify template outputs with decorators

    Testing Modules

    Test your factory extensions

    Designer Configuration

    Configure visual designers

    Build docs developers (and LLMs) love