Skip to content

Custom Producer

Simon Mourier edited this page Feb 19, 2020 · 1 revision

This section details how to create custom producers and sub-producers to extend SoftFluent CodeModeler scope yourself. Whether you’re creating a custom producer or a custom sub-producer, the logic is the same: all you need to do is implement the corresponding interface. You can also add Visual Studio integration if your code must expose specific attributes.

Note: To develop a custom producer or sub producer, you must write it using .NET Framework, not .NET Core which is not supported.

Integration with Visual Studio

To integrate a producer or sub producer in the dialog boxes shown using “Add New Producer” and/or “Add New SubProducer” context menus in the CodeModeler project system, you must declare it in the CodeModeler’s Custom.config file.

To determine where is located this Custom.config file, go to Visual Studio’s Tools / Options / SoftFluent CodeModeler, select the Advanced Properties tab and check the “Custom Configuration File Path” property. By default, this file doesn’t exist, so, get to the pointed directory and create a Custom.config file with the following content (set the ‘typeName’ to your actual producer):

<codeModeler.Modeler> 
    <producerDescriptors> 
      <producerDescriptor name="MyProducer" displayName="MyProducer" category="MyStuff" typeName="MyAssembly.MyProducer, MyProducer" /> 
    </producerDescriptors> 
</codeModeler.Modeler>

Then, once the producer or a sub producer has been compiled as an assembly (.dll), this assembly must be copied to Visual Studio, for example in this directory (adapt to your Visual Studio version):

C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\IDE\PublicAssemblies

Note: The assembly is locked by the Visual Studio process when used by a CodeModeler project. You must restart Visual Studio if you want to modify it.

Developing a Custom Producer

If ever you need to target a technology or platform that isn't supported by SoftFluent CodeModeler natively, you can develop your own producer which will translate the inferred in-memory representation of your model into your desired output. Do develop a custom producer, all you must do is implement the IProducer interface:

public interface IProducer
{
    event Producer.OnProductionEventHandler Production;
    void Initialize(Project project, Producer producer);
    void Produce();
    void Terminate();
}

So, to develop a custom producer, you must create a .NET Class Library, reference at least CodeModeler.Runtime.dll, CodeModeler.Model.dll, and CodeModeler.Producers.dll assemblies, and add a class that implements IProducer to the class library.

Developing a Custom SubProducer

The Object Model Cache Producer is an example of a sub-producer of the Business Object Model Producer: it adds custom code to the Business Object Model (BOM) layer being generated.

To develop a custom sub producer, you must create a .NET Class Library, reference at least CodeModeler.Runtime.dll, CodeModeler.Model.dll, and CodeModeler.Producers.dll assemblies, and add a class that implements ICodeDomSubProducer to the class library:

public interface ICodeDomSubProducer
{
    void Initialize(CodeDomBaseProducer baseProducer, SubProducer subProducer, IDictionary context);
    void Produce(IDictionary context, CodeCompileUnit unit);
    void Terminate(IDictionary context);
}

Example

This example shows the code of a CodeDomLightProducer class that implements ICodeDomSubProducer, the most important line is in the constructor where we add a CodeDomProductionEventHandler event handler. This sub-producer removes public static methods like Insert, Delete and Save from the class generated for entities that have attributes local to http://www.softfluent.com/codemodeler/sample.producers.lightProducer/2020/1 XML namespace (a namespace invented for sample purposes):

using System;
using System.CodeDom;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml;
using CodeModeler.Model;
using CodeModeler.Model.Design;
using CodeModeler.Producers;
using CodeModeler.Runtime;
using CodeModeler.Runtime.Utilities;
 
namespace CodeDomLightProducer
{
    // IDescribable exposes descriptors for concept’s property grid support
    // IConfigurable exposes xml element to be able to persist configuration in parts
    // IProjectNode is for integration in CodeModeler project’s meta model
    public class LightProducer : ICodeDomSubProducer, IDescribable, IProjectNode, IConfigurable
    {
        // this is our namespace that uniquely identifies us
        public const string NamespaceUri = "http://www.softfluent.com/codemodeler/producers.lightProducer/2020/1";
 
        // this is out preferred prefix
        public const string NamespacePrefix = "lp";
 
        public IEnumerable<Descriptor> Descriptors { get; private set; }
        public Project Project => SubProducer.ModelSubProducer.Project;
        public CodeModeler.Producers.SubProducer SubProducer { get; private set; }
        public XmlElement Element { get => SubProducer.Element; set { } }
 
        // a property that will be used in property grid, stored in the model's xml part
        [Description("Determines if this producer uses case-sensitive comparisons.")]
        [Category("Configuration")]
        [DefaultValue(false)]
        [DisplayName("Case Sensitive")]
        [ModelLevel(ModelLevel.Normal)] // this is where you determine the property grid's tab
        public bool CaseSensitive { get => XmlUtilities.GetAttribute(Element, "caseSensitive", false); set => XmlUtilities.SetAttribute(Element, "caseSensitive", value.ToString().ToLowerInvariant()); }
 
        public void Initialize(CodeDomBaseProducer baseProducer, CodeModeler.Producers.SubProducer subProducer, IDictionary context)
        {
            SubProducer = subProducer;
            baseProducer.CodeDomProduction += OnCodeDomProduction;
 
            // build descriptors used in a concept property grid ('Aspects and Producers Properties' tab)
            // add a descriptor that explains the 'exclude' attributes
            var list = new List<Descriptor>();
            list.Add(BaseProducer.BuildDescriptor(this, "Light Producer", NamespaceUri, NamespacePrefix,
                "exclude", "string", null, "Excluded Methods", "A case-sensitive, comma-separated, list of method names.",
                NodeType.Entity)); // we target only entity concepts
            Descriptors = list;
        }
 
        public void Produce(IDictionary context, CodeCompileUnit unit)
        {
            // do nothing
        }
 
        public void Terminate(IDictionary context)
        {
            // do nothing
        }
 
        private void OnCodeDomProduction(object sender, CodeDomProductionEventArgs e)
        {
            // only handle this event and check arguments
            if (e.EventType != CodeDomProductionEventType.UnitsProducing || !(e.Argument is CodeCompileUnit[] units))
                return;
 
            foreach (var unit in units)
            {
                foreach (var ns in unit.Namespaces.OfType<CodeNamespace>())
                {
                    foreach (var type in ns.Types.OfType<CodeTypeDeclaration>())
                    {
                        // this is how CodeModeler stores the CodeModeler's BaseType <-> CodeTypeDeclaration relation
                        var modelType = UserData.GetBaseType(type);
                        if (modelType == null)
                            continue;
 
                        // is it an Entity type or a EntityCollectionType?
                        var element = modelType is Set ? ((Set)modelType).ItemEntity.Element : modelType.Element;
 
                        // gather the list of methods to remove
                        var methodsToHide = new HashSet<string>(); // note it's case-sensitive
                        foreach (var attribute in element.Attributes
                            .OfType<XmlAttribute>()
                            .Where(a => a.NamespaceURI == NamespaceUri && a.LocalName == "exclude"))
                        {
                            foreach (var method in attribute.Value.Split(','))
                            {
                                methodsToHide.Add(method);
                            }
                        }
 
                        foreach (var method in type.Members
                            .OfType<CodeMemberMethod>()
                            .Where(m => methodsToHide.Contains(m.Name))
                            .ToArray()) // realize the collection since we want to remove from it
                        {
                            type.Members.Remove(method);
                        }
                    }
                }
            }
 
        }
    }
}

Here is how to modify CodeModeler’s Custom.config file:

<codeModeler.Modeler> 
    ...     
    <producerDescriptors> 
      ...     
      <producerDescriptor name="Light" displayName="Light Producer" category="My Custom Producers" typeName="CodeDomLightProducer.LightProducer, CodeDomLightProducer" /> 
      ...     
    </producerDescriptors> 
    ...     
</codeModeler.Modeler>

The “typeName” attribute is the assembly-qualified type name of the LightProducer class.

Once you have copied the resulting CodeDomLightProducer.dll into Visual Studio’s public assemblies, you will see this when you try to add a new sub producer:

Example - Picture 345

The “Case Sensitive” property in the property grid corresponds to the public CaseSensitive .NET property of the LightProducer class. It has been decorated with attribute (category, display name, default value, etc.) to fully integrate it in the property grid.

Once the producer has been added, it’s displayed in the project hierarchy:

Example - Picture 346

And we can set the “Exclude” attribute in the “Light Producer” category on any entity concept (thanks to the descriptor that has been added to the code):

Example - Picture 347

Clone this wiki locally