-
Notifications
You must be signed in to change notification settings - Fork 0
Custom Producer
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.
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.
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.
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);
}
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:
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:
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):
- Introduction
- Architect Guide
- Concepts
- Using Visual Studio
- Overview
- Creating a CodeModeler Project
- Visual Environment
- Project Hierarchy
- Design Surface
- Customizing Design Surfaces
- Ribbon Bar
- Property Grid
- Member Format Expressions
- Model Grid
- Method Editor
- View Editor
- Instance Editor and Grid
- Resources Editor
- Inferred Model Viewer
- Building
- Project Physical Layout
- Source Control Support
- Generating
- Aspect Oriented Design (AOD)
- Developer Guide
- The Business Object Model (BOM)
- CodeModeler Query Language (CMQL)
- Starting Guide - Tutorial
- Upgrade From CFE