Search Results for

    Show / Hide Table of Contents

    Software Factory Extensions

    Software Factory Extensions allow hooking into any of the various Software Execution phases to perform additional arbitrary actions which wouldn't make sense to be performed by a particular template. Examples of use cases for Software Factory extensions include (but are not limited to) post processing which needs to be performed after all templates have been executed or perhaps manipulating templates from other modules.

    Factory Extensions are powerful tools in Intent Architect that allow you to:

    • Manipulate code generated by other templates
    • Add cross-cutting concerns across multiple modules
    • Execute external processes during code generation
    • Register metadata providers and services
    • Coordinate complex multi-template scenarios

    This article assumes that you have a Module Builder Intent Architect application already set up, please refer to our Create Module article for details on how to make a Module Building Intent Architect application.

    Creating a Software Factory Extension

    Creating a Software Factory extension is done through the Module Builder by using the New Factory Extension context menu option:

    New Software Factory context menu option

    Choose a name for the Factory Extension which completes the work needed inside the Module Builder designer as there is nothing else which can be configured:

    Named Factory Extension in the designer

    Run the Software Factory, apply changes and then open the generated file in Visual Studio. Factory Extensions always have the same boilerplate content:

    [IntentManaged(Mode.Fully, Body = Mode.Merge)]
    public class MyFactoryExtension : FactoryExtensionBase
    {
        public override string Id => "ExtensionExample.MyFactoryExtension";
    
        [IntentManaged(Mode.Ignore)]
        public override int Order => 0;
    
        /// <summary>
        /// This is an example override which would extend the
        /// <see cref="ExecutionLifeCycleSteps.AfterTemplateRegistrations"/> phase of the Software Factory execution.
        /// See <see cref="FactoryExtensionBase"/> for all available overrides.
        /// </summary>
        /// <remarks>
        /// It is safe to update or delete this method.
        /// </remarks>
        protected override void OnAfterTemplateRegistrations(IApplication application)
        {
            // Your custom logic here.
        }
    
        /// <summary>
        /// This is an example override which would extend the
        /// <see cref="ExecutionLifeCycleSteps.BeforeTemplateExecution"/> phase of the Software Factory execution.
        /// See <see cref="FactoryExtensionBase"/> for all available overrides.
        /// </summary>
        /// <remarks>
        /// It is safe to update or delete this method.
        /// </remarks>
        protected override void OnBeforeTemplateExecution(IApplication application)
        {
            // Your custom logic here.
        }
    }
    

    As noted in the comments above each of the methods, they are merely examples of overriding the relevant base methods. If you don't need one or both of these overrides, it is safe to delete the methods.

    Understanding the Execution Lifecycle

    Factory Extensions can hook into various phases of the Software Factory execution. Each overridden method is called during a particular phase of the Software Factory execution, the following is a list of the all phases which can be hooked into in their execution order:

    Method Name Description
    OnStart Called once the Software Factory start up is complete.
    OnBeforeMetadataLoad Called before metadata loading commences. Typically used to register custom metadata providers.
    OnAfterMetadataLoad Called after metadata loading is complete.
    OnBeforeTemplateRegistrations Called before template registration and instantiation (construction) is performed.
    OnAfterTemplateRegistrations Called after template registration and instantiation (construction) has been completed. This is the most commonly overridden method for Factory Extensions.
    OnBeforeTemplateExecution Called before the RunTemplate is called on all template.
    OnAfterTemplateExecution Called after the RunTemplate called has been completed on all templates.
    OnBeforeCommitChanges Called immediately before "Changes" view is presented in the Software Factory window.
    OnAfterCommitChanges Called after the user has pressed "Apply" on the Software Factory window and all confirmed changes have been committed to the file system.

    Here's a comprehensive example showing all available lifecycle hooks:

    public class MyFactoryExtension : FactoryExtensionBase
    {
        public override string Id => "MyModule.MyFactoryExtension";
        public override int Order => 0; // Controls execution order
    
        protected override void OnStart(IApplication application)
        {
            // Called once Software Factory startup is complete
        }
    
        protected override void OnBeforeMetadataLoad(IApplication application)
        {
            // Called before metadata loading - register custom providers here
        }
    
        protected override void OnAfterMetadataLoad(IApplication application)
        {
            // Called after all metadata is loaded
        }
    
        protected override void OnBeforeTemplateRegistrations(IApplication application)
        {
            // Called before templates are registered and instantiated
        }
    
        protected override void OnAfterTemplateRegistrations(IApplication application)
        {
            // Called after all templates are instantiated
            // MOST COMMONLY USED - templates are available for manipulation
        }
    
        protected override void OnBeforeTemplateExecution(IApplication application)
        {
            // Called before templates start executing
            // COMMONLY USED - apply distributed events changes here
        }
    
        protected override void OnAfterTemplateExecution(IApplication application)
        {
            // Called after all templates have executed
        }
    
        protected override void OnBeforeCommitChanges(IApplication application)
        {
            // Called before the Changes view is presented
        }
    
        protected override void OnAfterCommitChanges(IApplication application)
        {
            // Called after user applies changes to the file system
        }
    }
    

    Template Discovery Patterns

    Understanding Template Discovery Methods

    Factory Extensions can discover templates using two primary approaches:

    1. TemplateId-based discovery - Specific and precise
    2. Role-based discovery - Flexible and decoupled

    TemplateId-Based Discovery

    Use specific Template IDs when you need to target exact templates:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        // Find specific template by exact ID
        var entityTemplate = application.FindTemplateInstance<ICSharpFileBuilderTemplate>("Intent.Entities.DomainEntity");
        
        // Find all instances of a specific template ID
        var commandTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Intent.Application.MediatR.CommandModels");
        
        // Find template for specific model instance
        if (application.TryGetTemplate<ICSharpFileBuilderTemplate>("Intent.Entities.DomainEntity", someEntityModel, out var specificEntityTemplate))
        {
            // Work with the specific template instance
        }
    }
    

    Role-Based Discovery

    Use roles for flexible, decoupled template discovery:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        // Find templates by role - more flexible than TemplateId
        var allEntities = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain.Entity");
        var allRepositories = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain.Repository");
        var allControllers = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Application.Controller");
        
        // Find templates with hierarchical roles
        var auditableEntities = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain.Entity.Auditable");
        var securedControllers = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Application.Controller.Secured");
        
        // Broad role matching - finds all templates starting with "Domain"
        var allDomainTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain");
    }
    
    Tip

    To work out the Template Id of a template you want to be able to find, either refer to the TemplateId field in the source code of the template or alternatively if it's a C# file you can open a file generated by the template and copy the value of the IntentTemplate assembly attribute at the top of the file.

    Advanced Template Filtering

    Combine discovery methods with sophisticated filtering:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        // Find templates using multiple criteria
        var aggregateRootRepositories = application.FindTemplateInstances("Domain.Repository")
            .OfType<ICSharpFileBuilderTemplate>()
            .Where(t => t.TryGetModel<IHasName>(out var named) && 
                       named.Name.EndsWith("AggregateRoot"));
    
        // Filter by model stereotypes
        var cacheableServices = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Application.Service")
            .Where(template => template.TryGetModel<IHasStereotypes>(out var model) && 
                              model.HasStereotype("Cacheable"));
    
        // Filter by template configuration
        var publicControllers = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Application.Controller")
            .Where(template => !template.TryGetModel<IHasStereotypes>(out var model) || 
                              !model.HasStereotype("RequiresAuth"));
    }
    

    Examples

    Creating a Factory Extension to manipulate files generated from other modules

    For cases where you want your module to be able to manipulate content generated by templates in other modules, most Intent Architect authored templates use the C# File Builder which allows for easy manipulation of the file without requiring direct dependencies on the template's .NET assembly. These template instances can be found and manipulated from within Software Factory extensions.

    For this example we will create a Factory Extension which finds all template instances which generate CQRS commands (from the Intent.Application.MediatR module), read a stereotype value off their model and if present then add an additional attribute to the generated class.

    Begin by creating a new Factory Extension, giving it a name, applying the Software Changes and then opening it inside of your IDE.

    In our Factory Extension we will need to ensure we have overridden the OnAfterTemplateRegistrations method and change its content to the following:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var templates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Intent.Application.MediatR.CommandModels");
    
        foreach (var template in templates)
        {
            if (template.TryGetModel<IHasStereotypes>(out var hasStereotypes))
            {
                throw new Exception("TryGetModel returned false");
            }
    
            if (hasStereotypes.HasStereotype("StereotypeNameOrId"))
            {
                var stereotypeValue = hasStereotypes.GetStereotypeProperty<string>("StereotypeNameOrId", "StereotypePropertyName");
    
                template.CSharpFile.OnBuild(file =>
                {
                    var @class = file.Classes.FirstOrDefault() ?? throw new Exception("Could not find class on file");
    
                    @class.AddAttribute($"MyCustomAttribute(\"{stereotypeValue}\")");
                });
            }
        }
    }
    

    In the above example we start with the following line:

    var templates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Intent.Application.MediatR.CommandModels");
    

    This retrieves all the template instances which generate Commands, the "Intent.Application.MediatR.CommandModels" argument allows specifying that we only want template instances for that Template ID.

    We use the generic type argument of ICSharpFileBuilderTemplate which will cast all templates instances to this type.

    When iterating over each template we:

    • Use the TryGetModel method with a generic type argument of IHasStereotypes to try get the model for the template and we cast it to an IHasStereotypes which will allow us to read the stereotypes off the model without us needing to reference a NuGet package with the specific model type.
    • Check if the appropriate stereotype is applied.
    • Read a property off the stereotype using the generic type argument of string to convert it to this type.
    • The CSharpFile.OnBuild method is called and in which we can then do anything on the file in the same way as when we're authoring the template in its own constructor.
    • Find the class on the template.
    • Add a custom attribute to the class.

    If you build and install the module, it will now update commands to add this attribute.

    Creating a Factory Extension to run an external program

    For this example we will create a Factory Extension which runs the following command:

    npm install
    

    Begin by creating a new Factory Extension, giving it a name, applying the Software Changes and then opening it inside of your IDE.

    In our Factory Extension we will need to ensure we have overridden the OnAfterCommitChanges method and change its content to the following:

    protected override void OnAfterCommitChanges(IApplication application)
    {
        try
        {
            var cmd = new Process
            {
                StartInfo =
                    {
                        FileName = "cmd.exe",
                        RedirectStandardInput = true,
                        RedirectStandardOutput = true,
                        CreateNoWindow = false,
                        UseShellExecute = false,
                        WorkingDirectory = Path.GetFullPath(application.RootLocation)
                    }
            };
            cmd.Start();
    
            cmd.StandardInput.WriteLine("npm install");
    
            cmd.StandardInput.Flush();
            cmd.StandardInput.Close();
    
            var output = cmd.StandardOutput.ReadToEnd();
            Logging.Log.Info(output);
        }
        catch (Exception e)
        {
            Logging.Log.Failure($@"Failed to execute: ""npm install"", Reason: {e.Message}");
        }
    }
    

    Install your Module to your Test Application in Intent Architect. Follow these steps if you are not sure how.

    Run the Software Factory, click on the Apply button and then observe the following at the end of the process in the console output:

    Complete

    Advanced Template Manipulation

    Working with C# File Builder Templates

    The most common use case for Factory Extensions is manipulating templates that use the C# File Builder System:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        // Find all entity templates using role-based discovery
        var entityTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain.Entity");
    
        foreach (var template in entityTemplates)
        {
            // Use OnBuild to modify the generated code structure
            template.CSharpFile.OnBuild(file =>
            {
                var @class = file.Classes.FirstOrDefault();
                if (@class == null) return;
    
                // Add auditing interface
                if (!@class.Interfaces.Any(i => i.Contains("IAuditable")))
                {
                    @class.AddInterface("IAuditable");
                }
    
                // Add auditing properties if they don't exist
                if (!@class.Properties.Any(p => p.Name == "CreatedAt"))
                {
                    @class.AddProperty("DateTime", "CreatedAt");
                    @class.AddProperty("string", "CreatedBy");
                    @class.AddProperty("DateTime?", "UpdatedAt");
                    @class.AddProperty("string", "UpdatedBy");
                }
    
                // Add auditing using directive
                file.AddUsing("System");
            });
        }
    }
    

    Working with Template Models

    Factory Extensions can access and query template models without direct dependencies:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var commandTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Application.Command");
    
        foreach (var template in commandTemplates)
        {
            // Try to get the model as a specific interface
            if (template.TryGetModel<IHasStereotypes>(out var stereotypedModel))
            {
                // Check for custom stereotypes
                if (stereotypedModel.HasStereotype("Cacheable"))
                {
                    var cacheKey = stereotypedModel.GetStereotypeProperty<string>("Cacheable", "CacheKey");
                    var expiration = stereotypedModel.GetStereotypeProperty<int>("Cacheable", "ExpirationMinutes");
    
                    template.CSharpFile.OnBuild(file =>
                    {
                        var @class = file.Classes.FirstOrDefault();
                        @class?.AddAttribute($"[Cacheable(\"{cacheKey}\", {expiration})]");
                    });
                }
            }
        }
    }
    

    Cross-Module Coordination Patterns

    Factory Extensions excel at coordinating activities across multiple templates and modules. Common coordination patterns include:

    • Entity Framework Configuration Coordination - Automatically apply query filters, configure entity mappings, and set up audit properties based on entity stereotypes
    • Service Registration Coordination - Collect services from multiple modules (repositories, application services, domain services) and register them in dependency injection containers
    • MediatR Handler Registration - Automatically register command and query handlers with MediatR using assembly scanning
    • API Configuration - Coordinate OpenAPI documentation, versioning, and security policies across controllers
    • Database Migration Coordination - Generate migration scripts based on entity changes across multiple domain modules
    • Event Bus Configuration - Register domain event handlers and configure distributed event publishing
    • Authentication & Authorization - Apply security policies consistently across controllers based on model stereotypes
    • Caching Strategy Coordination - Configure caching policies and cache invalidation across service layers
    • Validation Pipeline Setup - Register FluentValidation validators and configure validation behaviors
    • Logging and Monitoring - Apply structured logging and telemetry across all service layers

    Advanced Patterns and Real-World Use Cases

    1. Cross-Cutting Security Concerns

    Add security attributes to all controllers based on model configuration:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var controllerTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Application.Controller");
        
        foreach (var template in controllerTemplates)
        {
            if (template.TryGetModel<IHasStereotypes>(out var model))
            {
                template.CSharpFile.OnBuild(file =>
                {
                    var @class = file.Classes.FirstOrDefault();
                    if (@class == null) return;
    
                    // Add authorization based on stereotypes
                    if (model.HasStereotype("RequiresAuth"))
                    {
                        @class.AddAttribute("[Authorize]");
                        file.AddUsing("Microsoft.AspNetCore.Authorization");
                    }
    
                    if (model.HasStereotype("AdminOnly"))
                    {
                        @class.AddAttribute("[Authorize(Policy = \"AdminOnly\")]");
                    }
    
                    if (model.HasStereotype("RateLimit"))
                    {
                        var limit = model.GetStereotypeProperty<int>("RateLimit", "RequestsPerMinute");
                        @class.AddAttribute($"[RateLimit({limit})]");
                    }
                });
            }
        }
    }
    

    2. Automatic Validation Integration

    Add FluentValidation support to all commands and queries:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var commandTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Application.Command");
        var queryTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Application.Query");
        
        var validatorTemplates = new List<ICSharpFileBuilderTemplate>();
    
        foreach (var template in commandTemplates.Concat(queryTemplates))
        {
            if (template.TryGetModel<IHasName>(out var named))
            {
                // Create validator template for each command/query
                var validatorTemplate = CreateValidatorTemplate(named.Name, template);
                validatorTemplates.Add(validatorTemplate);
                
                // Add validation behavior to the original template
                template.CSharpFile.OnBuild(file =>
                {
                    var @class = file.Classes.FirstOrDefault();
                    @class?.AddAttribute("[ValidateRequest]");
                });
            }
        }
    
        // Register validators in DI
        RegisterValidators(application, validatorTemplates);
    }
    

    3. Distributed Events Integration

    Automatically publish domain events from aggregate roots:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var entityTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain.Entity")
            .Where(t => t.TryGetModel<IHasStereotypes>(out var model) && 
                       model.HasStereotype("AggregateRoot"));
    
        foreach (var template in entityTemplates)
        {
            template.CSharpFile.OnBuild(file =>
            {
                var @class = file.Classes.FirstOrDefault();
                if (@class == null) return;
    
                // Add domain events property
                @class.AddProperty("List<DomainEvent>", "_domainEvents", prop =>
                {
                    prop.Private().WithInitialValue("new List<DomainEvent>()");
                });
    
                @class.AddProperty("IReadOnlyCollection<DomainEvent>", "DomainEvents", prop =>
                {
                    prop.Getter.WithExpressionBody("_domainEvents.AsReadOnly()");
                });
    
                // Add methods for domain event handling
                @class.AddMethod("void", "AddDomainEvent", method =>
                {
                    method.Protected()
                          .AddParameter("DomainEvent", "domainEvent");
                    method.AddStatement("_domainEvents.Add(domainEvent);");
                });
    
                @class.AddMethod("void", "ClearDomainEvents", method =>
                {
                    method.Protected();
                    method.AddStatement("_domainEvents.Clear();");
                });
    
                file.AddUsing("System.Collections.Generic");
            });
        }
    }
    

    Integration with File Builder System

    Complex Code Manipulation

    Factory Extensions can perform sophisticated manipulations of the C# File Builder objects:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var entityTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain.Entity");
    
        foreach (var template in entityTemplates)
        {
            AddValidationLogic(template);
            AddEqualsAndHashCode(template);
            AddToStringMethod(template);
        }
    }
    
    private void AddValidationLogic(ICSharpFileBuilderTemplate template)
    {
        template.CSharpFile.OnBuild(file =>
        {
            var @class = file.Classes.FirstOrDefault();
            if (@class == null) return;
    
            // Add validation method
            @class.AddMethod("ValidationResult", "Validate", method =>
            {
                method.AddStatement("var result = new ValidationResult();");
                
                // Add validation for each property
                foreach (var property in @class.Properties.Where(p => p.HasGetter && p.HasSetter))
                {
                    if (property.Type == "string")
                    {
                        method.AddStatement($"if (string.IsNullOrWhiteSpace({property.Name}))");
                        method.AddStatement($"    result.AddError(\"{property.Name} is required\");");
                    }
                }
                
                method.AddStatement("return result;");
            });
        });
    }
    
    private void AddEqualsAndHashCode(ICSharpFileBuilderTemplate template)
    {
        template.CSharpFile.OnBuild(file =>
        {
            var @class = file.Classes.FirstOrDefault();
            if (@class == null) return;
    
            var keyProperties = @class.Properties.Where(p => p.Name.EndsWith("Id")).ToList();
            if (!keyProperties.Any()) return;
    
            // Add Equals method
            @class.AddMethod("bool", "Equals", method =>
            {
                method.Override();
                method.AddParameter("object", "obj");
                method.AddStatement($"return obj is {@class.Name} other && {string.Join(" && ", keyProperties.Select(p => $"{p.Name} == other.{p.Name}"))};");
            });
    
            // Add GetHashCode method
            @class.AddMethod("int", "GetHashCode", method =>
            {
                method.Override();
                var hashCode = string.Join(" ^ ", keyProperties.Select(p => $"{p.Name}.GetHashCode()"));
                method.AddStatement($"return {hashCode};");
            });
        });
    }
    

    External Process Integration

    Running Build Tools After Generation

    Execute external tools as part of the generation process:

    protected override void OnAfterCommitChanges(IApplication application)
    {
        // Run TypeScript compilation
        RunTypeScriptCompilation(application);
        
        // Generate OpenAPI documentation
        GenerateOpenApiDocs(application);
        
        // Run code formatting
        FormatGeneratedCode(application);
    }
    
    private void RunTypeScriptCompilation(IApplication application)
    {
        try
        {
            var clientPath = Path.Combine(application.RootLocation, "ClientApp");
            if (!Directory.Exists(clientPath)) return;
    
            var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "npm",
                    Arguments = "run build",
                    WorkingDirectory = clientPath,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                }
            };
    
            process.Start();
            var output = process.StandardOutput.ReadToEnd();
            var error = process.StandardError.ReadToEnd();
            process.WaitForExit();
    
            if (process.ExitCode != 0)
            {
                Logging.Log.Warning($"TypeScript compilation warnings/errors: {error}");
            }
            else
            {
                Logging.Log.Info("TypeScript compilation completed successfully");
            }
        }
        catch (Exception ex)
        {
            Logging.Log.Warning($"Failed to run TypeScript compilation: {ex.Message}");
        }
    }
    

    Best Practices and Patterns

    1. Order-Dependent Operations

    Control execution sequence using the Order property:

    public class SecurityExtension : FactoryExtensionBase
    {
        public override int Order => -50; // Execute early for security setup
    }
    
    public class ValidationExtension : FactoryExtensionBase
    {
        public override int Order => 0; // Default order
    }
    
    public class DocumentationExtension : FactoryExtensionBase
    {
        public override int Order => 50; // Execute late for final documentation
    }
    

    2. Safe Template Access

    Always verify template availability and types:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var templates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain.Entity");
        
        foreach (var template in templates)
        {
            // Verify the template is what we expect
            if (template?.CSharpFile?.Classes?.Any() != true)
            {
                Logging.Log.Warning($"Skipping invalid template: {template?.Id}");
                continue;
            }
    
            // Safe manipulation with error handling
            try
            {
                template.CSharpFile.OnBuild(file =>
                {
                    var @class = file.Classes.FirstOrDefault();
                    @class?.AddProperty("DateTime", "LastModified");
                });
            }
            catch (Exception ex)
            {
                Logging.Log.Error($"Failed to modify template {template.Id}: {ex.Message}");
            }
        }
    }
    

    3. Configuration-Driven Behavior

    Use application settings to control Factory Extension behavior:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var settings = application.Settings.GetMyModuleSettings();
        
        if (settings.EnableAuditing())
        {
            AddAuditingSupport(application);
        }
        
        if (settings.EnableCaching())
        {
            AddCachingSupport(application);
        }
        
        if (settings.EnableSecurity())
        {
            AddSecuritySupport(application);
        }
    }
    

    4. Conditional Template Manipulation

    Only modify templates that meet specific criteria:

    protected override void OnAfterTemplateRegistrations(IApplication application)
    {
        var entityTemplates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>("Domain.Entity");
        
        foreach (var template in entityTemplates)
        {
            // Only modify templates that have specific stereotypes
            if (!template.TryGetModel<IHasStereotypes>(out var model) || 
                !model.HasStereotype("Auditable"))
            {
                continue;
            }
    
            // Check if template already has auditing properties
            var hasAuditingAlready = false;
            template.CSharpFile.OnBuild(file =>
            {
                var @class = file.Classes.FirstOrDefault();
                hasAuditingAlready = @class?.Properties.Any(p => p.Name == "CreatedAt") == true;
            });
    
            if (!hasAuditingAlready)
            {
                AddAuditingProperties(template);
            }
        }
    }
    

    Error Handling and Debugging

    Common Issues

    • Templates Not Found: Ensure you're using the correct template IDs or roles and that templates are registered before your extension runs.
    • Null Reference Exceptions: Always check for null values when accessing template properties and File Builder objects.
    • Order Dependencies: Use the Order property to ensure your extension runs at the right time.
    • Template Model Access: Use TryGetModel<T>() to safely access template models without causing exceptions.

    Debugging Tips

    • Review generated output: Always check the actual generated C# code to understand what your Factory Extension is producing.
    • Use the debugger: You can debug your Factory Extensions and inspect the real-time state of templates and File Builder objects using the .NET Debugger.
    • Log template discovery: Use Logging.Log.Info() to track which templates are found and processed by your extension.
    • Verify execution order: Log entry and exit points of your extension methods to understand the execution flow.

    Factory Extensions provide a powerful mechanism for implementing cross-cutting concerns and coordinating complex code generation scenarios. Use them strategically to build sophisticated, maintainable module ecosystems that work together seamlessly.

    • Edit this page
    ☀
    ☾
    In this article
    Back to top Copyright © 2017-, Intent Software Pte Ltd.