Skip to main content
Factory Extensions allow you to hook into various stages of the Software Factory execution pipeline, enabling cross-template modifications and complex orchestration.

What are Factory Extensions?

Factory Extensions are components that:
  • Execute at specific points in the Software Factory lifecycle
  • Modify multiple templates across the application
  • Handle cross-cutting concerns that span templates
  • Respond to and publish events
  • Configure dependency injection and services
Factory Extensions are more powerful than decorators but should be used judiciously as they affect the entire generation process.

Factory Extension Lifecycle

The Software Factory execution follows this lifecycle:
1

BeforeMetadataLoad

Executed before metadata is loaded from designers.
2

AfterMetadataLoad

Executed after metadata is loaded but before template registration.
3

BeforeTemplateRegistrations

Executed before templates are registered. Subscribe to events here.
4

AfterTemplateRegistrations

Executed after all templates are registered. Modify templates here.
5

BeforeTemplateExecution

Executed before templates generate output.
6

AfterTemplateExecution

Executed after all templates have generated output.

Creating a Factory Extension

Basic Structure

Create a class that inherits from FactoryExtensionBase:
DependencyInjectionFactoryExtension.cs
using Intent.Engine;
using Intent.Eventing;
using Intent.Modules.Common.Plugins;
using Intent.Plugins.FactoryExtensions;
using Intent.RoslynWeaver.Attributes;

[assembly: DefaultIntentManaged(Mode.Fully)]
[assembly: IntentTemplate("Intent.ModuleBuilder.Templates.FactoryExtension", Version = "1.0")]

namespace Intent.Modules.Application.DependencyInjection.MediatR.FactoryExtensions
{
    [IntentManaged(Mode.Fully, Body = Mode.Merge)]
    public class DependencyInjectionFactoryExtension : FactoryExtensionBase
    {
        public override string Id => "Intent.Application.DependencyInjection.MediatR";

        [IntentManaged(Mode.Ignore)]
        public override int Order => 0;

        protected override void OnBeforeTemplateRegistrations(IApplication application)
        {
            // Subscribe to events
        }

        protected override void OnAfterTemplateRegistrations(IApplication application)
        {
            // Modify templates
        }
    }
}

Execution Order

Control when your extension runs relative to others:
public override int Order => 100; // Higher values execute first

Lifecycle Hooks

OnBeforeTemplateRegistrations

Use this to subscribe to events before templates are created:
protected override void OnBeforeTemplateRegistrations(IApplication application)
{
    application.EventDispatcher.Subscribe<ContainerRegistrationRequest>(HandleContainerRegistration);
    application.EventDispatcher.Subscribe<InfrastructureRegisteredEvent>(HandleInfrastructureEvent);
}

private void HandleContainerRegistration(ContainerRegistrationRequest @event)
{
    if (@event.Concern != "MediatR")
    {
        return;
    }

    @event.MarkAsHandled();
    _containerRegistrationRequests.Add(@event);
}

OnAfterTemplateRegistrations

Modify templates after they’ve all been registered:
protected override void OnAfterTemplateRegistrations(IApplication application)
{
    var diTemplate = application.FindTemplateInstance<ICSharpFileBuilderTemplate>(
        TemplateRoles.Application.DependencyInjection);
    
    if (diTemplate == null)
    {
        return;
    }

    diTemplate.CSharpFile.AfterBuild(file =>
    {
        var method = file.Classes.First().FindMethod("AddApplication");
        
        method?.AddInvocationStatement("services.AddMediatR", invocation =>
        {
            invocation.AddArgument(new CSharpLambdaBlock("cfg"), arg =>
            {
                var lambda = (CSharpLambdaBlock)arg;
                lambda.AddStatement("cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());");
            });
        });
    });
}

OnBeforeTemplateExecution

Perform actions just before templates execute:
protected override void OnBeforeTemplateExecution(IApplication application)
{
    application.EventDispatcher.Publish(new EnvironmentVariableRegistrationRequest(
        "MY_ENV_VAR",
        "default-value",
        new[] { "Development" },
        "WebApi"));
}

OnAfterTemplateExecution

Perform cleanup or final modifications:
protected override void OnAfterTemplateExecution(IApplication application)
{
    // Generate summary reports
    // Clean up temporary files
    // Validate output
}

Event System

Publishing Events

application.EventDispatcher.Publish(new CustomEvent
{
    TemplateName = "MyTemplate",
    Action = "SomeAction"
});

Subscribing to Events

protected override void OnBeforeTemplateRegistrations(IApplication application)
{
    application.EventDispatcher.Subscribe<ContainerRegistrationRequest>(HandleEvent);
}

private void HandleEvent(ContainerRegistrationRequest @event)
{
    // Handle the event
    @event.MarkAsHandled();
}

Common Events

Request to register a service in the DI container.
application.EventDispatcher.Publish(new ContainerRegistrationRequest
{
    Concern = "MediatR",
    ConcreteType = "typeof(MyBehavior<,>)",
    Priority = 0
});
Remove a NuGet package from a project.
application.EventDispatcher.Publish(new RemoveNugetPackageEvent(
    "OldPackage", 
    template.OutputTarget));
Signal that infrastructure has been registered.
application.EventDispatcher.Publish(new InfrastructureRegisteredEvent());
Register an environment variable.
application.EventDispatcher.Publish(
    new EnvironmentVariableRegistrationRequest(
        "CONNECTION_STRING",
        "Server=localhost;Database=MyDb",
        new[] { "Development", "Staging" },
        "Infrastructure"));

Real-World Examples

MediatR Dependency Injection

This example shows how to wire up MediatR in the DI container:
DependencyInjectionFactoryExtension.cs
public class DependencyInjectionFactoryExtension : FactoryExtensionBase
{
    private readonly IList<ContainerRegistrationRequest> _registrationRequests = 
        new List<ContainerRegistrationRequest>();

    public override string Id => "Intent.Application.DependencyInjection.MediatR";

    protected override void OnBeforeTemplateRegistrations(IApplication application)
    {
        application.EventDispatcher.Subscribe<ContainerRegistrationRequest>(HandleEvent);
    }

    private void HandleEvent(ContainerRegistrationRequest @event)
    {
        if (@event.Concern != "MediatR")
        {
            return;
        }

        @event.MarkAsHandled();
        _registrationRequests.Add(@event);
    }

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var template = application.FindTemplateInstance<ICSharpFileBuilderTemplate>(
            TemplateRoles.Application.DependencyInjection);
        
        if (template == null)
        {
            return;
        }

        // Add NuGet package
        template.ExecutionContext.EventDispatcher.Publish(
            new RemoveNugetPackageEvent("MediatR.Extensions.Microsoft.DependencyInjection", 
                template.OutputTarget));
        template.AddNugetDependency(NugetPackages.MediatR(template.OutputTarget));

        template.CSharpFile.AfterBuild(file =>
        {
            file.AddUsing("Microsoft.Extensions.DependencyInjection");

            var method = file.Classes.First().FindMethod("AddApplication");

            method?.AddInvocationStatement("services.AddMediatR", invocation =>
            {
                invocation.AddArgument(new CSharpLambdaBlock("cfg"), arg =>
                {
                    var options = (CSharpLambdaBlock)arg;
                    options.AddStatement(
                        "cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());");

                    // Register behaviors from events
                    foreach (var registration in _registrationRequests.OrderBy(x => x.Priority))
                    {
                        foreach (var dependency in registration.TemplateDependencies)
                        {
                            ((IntentTemplateBase)template).AddTemplateDependency(dependency);
                        }

                        foreach (var ns in registration.RequiredNamespaces)
                        {
                            template.AddUsing(ns);
                        }

                        options.AddStatement(
                            $"cfg.AddOpenBehavior({registration.ConcreteType});");
                    }
                });
            });
        });
    }
}

AutoMapper DTO Mapping

Modify DTO templates to implement AutoMapper mapping interfaces:
AutoMapperDtoFactoryExtension.cs
public class AutoMapperDtoFactoryExtension : FactoryExtensionBase
{
    public override string Id => "Intent.Application.Dtos.AutoMapper";

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        if (!application.Settings.GetAutoMapperSettings().ProfileLocation().IsProfileInDto())
        {
            return;
        }

        var templates = application.FindTemplateInstances<DtoModelTemplate>(
            TemplateDependency.OnTemplate(TemplateRoles.Application.Contracts.Dto));
        
        foreach (var template in templates)
        {
            template.CSharpFile.AfterBuild(file =>
            {
                var @class = file.TypeDeclarations.First();
                var model = template.Model;

                if (!model.HasMapFromDomainMapping())
                {
                    return;
                }

                var entityTemplate = GetEntityTemplate(template, model);

                file.AddUsing("AutoMapper");

                @class.ImplementsInterface(
                    $"{template.GetTypeName(MapFromInterfaceTemplate.TemplateId)}" +
                    $"<{template.GetTypeName(entityTemplate)}>");

                @class.AddMethod("void", "Mapping", method =>
                {
                    method.AddParameter("Profile", "profile");

                    method.AddMethodChainStatement(
                        $"profile.CreateMap<{template.GetTypeName(entityTemplate)}, {template.ClassName}>()",
                        statement =>
                        {
                            ImplementMapping(statement, template, model);
                        });
                });
            });
        }
    }
}

Environment Variable Configuration

Publish environment variables for different profiles:
EnvironmentVarsExtension.cs
public class EnvironmentVarsExtension : FactoryExtensionBase
{
    private record EnvironmentVariable(
        string EnvName, 
        string EnvValue, 
        string ProfileName, 
        string RoleName);

    private EnvironmentVariable _environmentVariable = 
        new(string.Empty, string.Empty, string.Empty, string.Empty);

    public override string Id => "Intent.EnvironmentVariableTest";

    public override void Configure(IDictionary<string, string> settings)
    {
        base.Configure(settings);
        _environmentVariable = new EnvironmentVariable(
            settings["Environment Var Name"],
            settings["Environment Var Value"],
            settings["Target Profile"],
            settings["Target Role"]);
    }

    protected override void OnBeforeTemplateExecution(IApplication application)
    {
        application.EventDispatcher.Publish(new EnvironmentVariableRegistrationRequest(
            _environmentVariable.EnvName,
            _environmentVariable.EnvValue,
            new[] { _environmentVariable.ProfileName },
            _environmentVariable.RoleName));
    }
}

Configuration

Factory extensions can accept configuration from the module:
public override void Configure(IDictionary<string, string> settings)
{
    base.Configure(settings);
    
    _connectionString = settings["ConnectionString"];
    _enableCaching = bool.Parse(settings["EnableCaching"]);
}
Define settings in the .imodspec:
<factoryExtensions>
  <factoryExtension id="unique-guid" typeId="unique-type-guid">
    <config>
      <add key="ConnectionString" description="Database connection string" />
      <add key="EnableCaching" description="Enable caching" default="true" />
    </config>
  </factoryExtension>
</factoryExtensions>

Template Discovery

Find Single Template

var template = application.FindTemplateInstance<ICSharpFileBuilderTemplate>(
    TemplateRoles.Application.DependencyInjection);

Find Multiple Templates

var templates = application.FindTemplateInstances<DtoModelTemplate>(
    TemplateDependency.OnTemplate(TemplateRoles.Application.Contracts.Dto));

Find by Template ID

var template = application.FindTemplateInstance(
    "Intent.Application.MediatR.CommandHandler",
    model);

Modifying Multiple Templates

Apply changes across multiple templates:
protected override void OnAfterTemplateRegistrations(IApplication application)
{
    var repositoryTemplates = application.FindTemplateInstances<IClassProvider>(
        TemplateDependency.OnTemplate("Repository"));

    foreach (var template in repositoryTemplates)
    {
        if (template is ICSharpFileBuilderTemplate csharpTemplate)
        {
            csharpTemplate.CSharpFile.AfterBuild(file =>
            {
                var @class = file.Classes.FirstOrDefault();
                @class?.AddAttribute("[GeneratedCode(\"Intent.Architect\", \"1.0\")]");
            });
        }
    }
}

Best Practices

Prefer event-driven communication over direct template dependencies:
// Good: Event-driven
application.EventDispatcher.Publish(new MyEvent());

// Avoid: Direct coupling
var template = application.FindTemplateInstance<SpecificTemplate>();
template.DoSomething();
Always verify templates exist before modifying them:
var template = application.FindTemplateInstance<MyTemplate>(role);
if (template == null)
{
    return; // Or log warning
}
Modify templates in AfterBuild to ensure the template structure is complete:
template.CSharpFile.AfterBuild(file =>
{
    // Modifications here
});
Provide sensible defaults and validate settings:
public override void Configure(IDictionary<string, string> settings)
{
    base.Configure(settings);
    
    if (settings.TryGetValue("MySetting", out var value))
    {
        _mySetting = value;
    }
    else
    {
        _mySetting = "default-value";
    }
}
Only override the lifecycle hooks you actually need. Each hook adds execution time.

Testing Factory Extensions

FactoryExtensionTests.cs
[TestClass]
public class DependencyInjectionExtensionTests
{
    [TestMethod]
    public void RegistersMediatRCorrectly()
    {
        // Arrange
        var application = CreateTestApplication();
        var extension = new DependencyInjectionFactoryExtension();
        
        // Simulate event
        application.EventDispatcher.Publish(new ContainerRegistrationRequest
        {
            Concern = "MediatR",
            ConcreteType = "typeof(MyBehavior)"
        });
        
        // Act
        extension.OnAfterTemplateRegistrations(application);
        
        // Assert
        var diTemplate = application.FindTemplateInstance<ICSharpFileBuilderTemplate>(
            TemplateRoles.Application.DependencyInjection);
        var output = diTemplate.TransformText();
        
        Assert.IsTrue(output.Contains("services.AddMediatR"));
    }
}

Common Patterns

Service Registration

Registering services in DI containers across applications

Configuration Management

Adding settings to appsettings.json or other config files

Cross-Template Coordination

Coordinating changes across multiple related templates

Package Management

Adding or removing NuGet packages based on configuration

Next Steps

Testing

Learn how to test factory extensions

Decorators

Use decorators for simpler modifications

Template Development

Create templates that work with extensions

Creating Modules

Package extensions into distributable modules

Real-World Examples

Explore these factory extensions in the source code:
  • DependencyInjectionFactoryExtension - Modules/Intent.Modules.Application.DependencyInjection.MediatR/FactoryExtensions/
  • AutoMapperDtoFactoryExtension - Modules/Intent.Modules.Application.Dtos.AutoMapper/FactoryExtensions/
  • EnvironmentVarsExtension - InternalTestModules/Intent.Modules.Internal.EnvironmentVariableTest/FactoryExtensions/

Build docs developers (and LLMs) love