Search Results for

    Show / Hide Table of Contents

    Creating Templates with External Module Dependencies

    When creating your own modules, your module may be dependent on another module or have interplay with another module. This article covers various topics around this theme.

    Automating the Installation of Dependent Modules

    When talking about module dependencies, they come in one of the following forms:

    • Hard: This is a required dependency; your module will not work without the dependent module being installed.
    • Soft: This is an optional dependency; your module will run without the dependency but will have additional or different functionality if the dependent module is installed.

    Automatically Installing Hard Dependency Modules

    Assume you have a RepositoryModule module and you want to write a new module RepositoryExtensionsModule, which extends the functionality of the RepositoryModule. In this case, the RepositoryExtensionsModule has a hard dependency on the RepositoryModule, as its functionality relies on the presence of the RepositoryModule.

    In this scenario, you can simply add a dependency configuration in your RepositoryExtensionsModule's Module Installation file.

    <?xml version="1.0" encoding="utf-8"?>
    <package>
      <id>RepositoryExtensionsModule</id>
      <version>1.0.0</version>
      ...
      <dependencies>
        <dependency id="RepositoryModule" version="2.0.0" />
        ...
      </dependencies>
      ...
    </package>
    

    This will ensure that when your module RepositoryExtensionsModule is installed, the RepositoryModule is installed and is at least version 2.0.0. (Module Installation)

    Ensuring Minimum Module Dependency Versions for Soft Dependencies

    Assume you have a RepositoryModule module and you have a soft dependency on our CosmosDB module, namely Intent.CosmosDB. If the Intent.CosmosDB module is installed, your RepositoryModule will have additional functionality. In this case, your module is built against a specific version of the Intent.CosmosDB module, and you would want to ensure that the module is installed with at least that minimum version.

    In this scenario, you can modify your RepositoryModule's Module Installation file as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <package>
      <id>RepositoryModule</id>
      <version>2.0.0</version>
      ...
      <interoperability>
        <detect id="Intent.CosmosDB">
          <install>
            <package id="Intent.CosmosDB" version="1.2.1" />
          </install>
        </detect>
        ...
      </interoperability>
      ...
    </package>
    

    The above configuration reads as follows: if the Intent.CosmosDB module is installed, ensure it is at least version 1.2.1.

    Configuring Conditionally Dependent Modules

    Assume you have a RepositoryModule, and you may also have an AspNetCore.RepositoryModule module that you would like to conditionally install if the application has the Intent.AspNetCore module installed.

    In this scenario, you can modify your RepositoryModule's Module Installation file as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <package>
      <id>RepositoryModule</id>
      <version>2.0.0</version>
      ...
      <interoperability>
        <detect id="Intent.AspNetCore">
          <install>
            <package id="AspNetCore.RepositoryModule" version="1.0.0" />
          </install>
        </detect>
        ...
      </interoperability>
      ...
    </package>
    

    The above configuration reads as follows: if the Intent.AspNetCore module is installed, ensure the AspNetCore.RepositoryModule module is installed, and at least version 1.0.0.

    Conditionally Running a Template Based on the Presence of a Module

    If your module has a soft dependency on another module, you may have templates that you want to conditionally run if the dependent module is installed.

    For example:

    Let's say we have a template in our RepositoryModule which has a CosmosInterfaceTemplate. Now we only want to generate the CosmosInterface if the Intent.CosmosDB module is installed.

    We can achieve this scenario by overriding the template's CanRunTemplate method.

    ...
    public partial class CosmosInterfaceTemplate : CSharpTemplateBase<object>, ICSharpFileBuilderTemplate
    {
        ...
        public override bool CanRunTemplate()
        {
            return base.CanRunTemplate() && ExecutionContext.InstalledModules.Any(p => p.ModuleId == "Intent.CosmosDB");
        }
        ...
    }
    

    In this code, you can see that the CanRunTemplate method will return false if the Intent.CosmosDB module is not installed, causing the template not to execute, i.e., not generating any output.

    Another common scenario may be to test for the presence of a file being generated by another module, commonly referred to as a template instance.

    ...
    public partial class MyInterfaceImplementationTemplate : CSharpTemplateBase<object>, ICSharpFileBuilderTemplate
    {
        ...
        public override bool CanRunTemplate()
        {
            return ExecutionContext.FindTemplateInstances("MyInterfaceModule.MyInterface").Any();
        }
        ...
    }
    

    In this code, you can see that the CanRunTemplate method will return false if there are no template instances of the template with the TemplateId MyInterfaceModule.MyInterface. Put more simply, Intent will not generate a MyInterfaceImplementation file if it is not generating a MyInterface file.

    Here are several other common examples of CanRunTemplate implementations:

    // If we are generating any files for `MyInterface.TemplateId`
    return ExecutionContext.FindTemplateInstances(MyInterface.TemplateId).Any();
    
    // If we are generating any files for `MyInterface.TemplateId`
    return TryGetTypeName(MyInterface.TemplateId, out var interfaceTypeName);
    
    // If we are generating any files for `MyInterface.TemplateId`
    return ExecutionContext.FindTemplateInstance<IClassProvider>("MyInterfaceModule.MyInterface") != null;
    
    // Based on a Module setting
    return ExecutionContext.Settings.GetDatabaseSettings().DatabaseProvider().AsEnum() == DatabaseSettingsExtensions.DatabaseProviderOptionsEnum.Cosmos;
    
    // Based on the existence of Designer elements
    return ExecutionContext.MetadataManager.Services(ExecutionContext.GetApplicationConfig().Id).GetElementsOfType("Command");
    
    // If we are generating a file for `Domain.Entity` for a specific designer model, passing in the `Model` parameter.
    // e.g. If we have modeled a `Customer` class in the domain designer, this is checking are we generating a `Customer.cs` file specifically
    return ExecutionContext.FindTemplateInstance<IClassProvider>("Domain.Entity", Model) != null;
    
    // If we are generating a file for `Domain.Entity` for a specific designer Model
    return TryGetTemplate<ICSharpFileBuilderTemplate>("Domain.Entity", Model, out var entityTemplate);
    

    Referencing / Working with Template Instances from Other Modules

    When building your own modules which have dependencies on other modules, it is often useful to be able to ask questions about that module and the code it is generating. Here are some common scenarios module builders run into.

    Note

    Most of these scenarios center around the ability to look up/discover other template instances during template execution. When searching for a template instance, it can be done by the TemplateId or the Template's Role. TemplateIds should generally be unique and hence very specific. It is also possible to use Template Roles, which means different templates, from potentially different modules, could produce the generated file. This is a way to decouple implementation dependencies or have different modules which could fulfill the Template Role. For example, you could have a generic Repository template role which could be fulfilled by CosmosDB.Repository.TemplateId or EntityFrameworkCode.Repository.TemplateId.

    Is the Following File Being Generated?

    Sometimes the code you wish to generate may be conditional on what other modules are installed and/or what they are doing. A fairly typical way to do this is to query and see if there is a template instance of a template generating a specific file.

    // Get the `Domain.Entity` template instance for a specific ClassModel (this would be a `File Per Model` output template)
    if (TryGetTemplate<ICSharpFileBuilderTemplate>("Domain.Entity", Model, out var entityTemplate))
    {
    }
    
    // Get the `MyInterfaceModule.MyInterface` template instance (this would be a `Single File` output template)
    if (TryGetTemplate<ICSharpFileBuilderTemplate>("MyInterfaceModule.MyInterface", out var myInterfaceTemplate))
    {
    }
    
    // Get all the `Domain.Entity` template instances for all the ClassModels
    var templateInstances = ExecutionContext.FindTemplateInstances("Domain.Entity");
    

    The above are examples for discovering other template instances from within your own template.

    Note

    It is often fine to simply check for the presence of a template instance to know if a file is being generated. However, if the template you are looking up implements the CanRunTemplate method, you should also check that this method returns true.

    What is the Type Information of a Class Generated by a Template?

    If you have a template generating a file, you may need to know the type information of that type during the execution of your template. For example: A template with Id Domain.Entity, may have a template instance for the Customer ClassModel which outputs the following type MyApplication.Domain.Entity.Customer;

    var typeName = GetTypeName("Domain.Entity", Model);
    
    if (TryGetTypeName("Domain.Entity", Model, out var typeName))
    {
    }
    

    In the above example, assuming the Model passed in was the Customer one, typeName would be set to MyApplication.Domain.Entity.Customer.

    Interrogate the Code That Is Actually Being Generated

    It can be useful to have access to what code is being produced by another template to make decisions about what your template needs to do.

    var template = GetTemplate<ICSharpFileBuilderTemplate>("Domain.Entity", Model);
    // Access the to-be-generated code constructs through the builder pattern
    var @class = entityTemplate.CSharpFile.Classes.First();
    foreach (var property in @class.Properties)
    {
    }
    

    In the above example, you are able to access and traverse the source being generated by the template through the builder. In this case, we are simply finding the first class in the file and iterating over its properties.

    Note

    This example will only work for templates that implement a Builder pattern, which allows for the interrogation of the generated code.

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