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:
BeforeMetadataLoad
Executed before metadata is loaded from designers.
AfterMetadataLoad
Executed after metadata is loaded but before template registration.
BeforeTemplateRegistrations
Executed before templates are registered. Subscribe to events here.
AfterTemplateRegistrations
Executed after all templates are registered. Modify templates here.
BeforeTemplateExecution
Executed before templates generate output.
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
ContainerRegistrationRequest
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 ));
InfrastructureRegisteredEvent
Signal that infrastructure has been registered. application . EventDispatcher . Publish ( new InfrastructureRegisteredEvent ());
EnvironmentVariableRegistrationRequest
Register an environment variable. application . EventDispatcher . Publish (
new EnvironmentVariableRegistrationRequest (
"CONNECTION_STRING" ,
"Server=localhost;Database=MyDb" ,
new [] { "Development" , "Staging" },
"Infrastructure" ));
Real-World Examples
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
Use events for loose coupling
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
}
Use AfterBuild for modifications
Modify templates in AfterBuild to ensure the template structure is complete: template . CSharpFile . AfterBuild ( file =>
{
// Modifications here
});
Handle settings gracefully
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
[ 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/