Skip to main content
A newer version of this page is available. .

Extend and Customize the Application Model in Code

  • 15 minutes to read

You can customize the information loaded to the Application Model via the Model Editor. It allows you to edit the information at design time, forming Application Model layers in each project. The layers are superimposed onto one another in a certain order. Values from every successive layer override the corresponding values from the previous layer (see the Application Model Basics topic). There is one more approach you can use to customize the information stored in the Application Model. In most cases, this approach is less suitable because the changes to be made in the Application Model are performed in code. But this approach is useful when the feature that you implement in a module demands the modification of the information generated in the Application Model by default. When implementing a new feature, you may need to add custom nodes to the Application Model. You may also need to customize an existing node by adding custom properties to it. In this way, you can allow other developers or end-users to change the behavior of your code by modifying your custom nodes and properties in the Model Editor. This topic explains how to extend and customize the Application Model in Code. To learn how the Application Model’s structure is formed refer to the Application Model Structure topic.

Several typical scenarios are provided in this topic. You can select one of them or combine them to handle more complex customizations.

Tip

If it is required to access properties exposed by an existing node, use the approach described in the Access the Application Model in Code topic.

Important

Always use a Node Generator or Generator Updater to customize the current node and its child nodes. To update nodes outside the current node hierarchy, create separate Node Generators or Generator Updaters.

Add a Custom Property to the Existing Node

To add new properties to an existing node, you should first define an interface and expose the required properties:

using DevExpress.ExpressApp.Model;
// ...
public interface IModelMyModelExtension : IModelNode {
    string MyCustomProperty { get; set; }
}

Then, extend the required node in your Module or Controller. Override the ModuleBase.ExtendModelInterfaces method or implement the IModelExtender interface, respectively.

using DevExpress.ExpressApp.Model;
// ...
public sealed partial class CustomizeModelExampleModule : ModuleBase {
    // ...
    public override void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
        base.ExtendModelInterfaces(extenders);
        extenders.Add<IModelApplication, IModelMyModelExtension>();
    }
    // ...
}

Rebuild your solution and open the Model Editor to see how the code above affects the Application Model.

CustomizeApplicationModelInCode_1

The IModelApplication node now exposes the MyCustomProperty property. To add the property to another node, substitute the interface corresponding to the required node, instead of the IModelApplication (see Application Model Structure).

You can decorate node properties with the following attributes.

Attribute Description
Browsable Indicates that a property is visible in the Model Editor. All properties added via the approach described above are visible, by default. To hide a property, decorate it with the Browsable attribute and pass false as the attribute’s parameter.
Category Specifies the target property category. The properties of each node are grouped by their categories in the Model Editor’s property grid. The default category is “Misc”. To assign a category to a property, decorate the property with the Category attribute and pass a category name as the attribute’s parameter. If the specified category does not exist, it will be added.
DataSourceProperty Specifies a name of the property that exposes a list of the target property’s possible values. You will be able to choose a value via the drop-down list in the Model Editor’s property grid. When the current property’s node exposes a list of child nodes (implements the IModelList<ChildNodeType> interface, as illustrated in the Extend The Application Model with New Nodes section), you can pass “this” as the DataSourceProperty attribute parameter. As a result, the child nodes list will be the current property possible values list.
DefaultValue Specifies the target property’s default value.
Editor Binds a custom editor to the property (see EditorAttribute).
Description Specifies the target property’s description displayed in the Model Editor.
Localizable Specifies that the target property can be localized. To define a localizable property, decorate it with the Localizable attribute and pass true as the attribute’s parameter.
ReadOnly To prohibit property value modification via the Model Editor, decorate the property with the ReadOnly attribute and pass true as the attribute’s parameter. Alternatively, you can omit the property’s setter. But, the ReadOnly attribute affects only the Model Editor, and does not restrict the property modification in code.
Required Indicates that a target property should have a value. The Model Editor will not allow you to save changes until you supply this property value.

Examples of some of these attributes in use will be provided later in this topic.

Note

Do not create the property named “Application”. Otherwise, this property will hide the node’s inherited IModelNode.Application property. Hiding the Application property causes the “Cannot compile the generated code” exception.

Extend the Application Model with New Nodes

  • The extension of the Application Model with new nodes is performed similarly to adding new properties. First, you should define interfaces representing your custom nodes and their properties. Note that you should derive from the IModelNode interface when you define an interface that specifies a separate node.

    using System.ComponentModel;
    // ...
    [KeyProperty("Name")]
    public interface IModelMyChildNode : IModelNode {
        string Name { get; set; }
        [Localizable(true)]
        string MyStringProperty { get; set; }
        int MyIntegerProperty { get; set; }
    }
    public interface IModelMyNodeWithChildNode : IModelNode {
        IModelMyChildNode MyChildNode { get; }
    }
    public interface IModelMyNodeWithChildNodes : IModelNode, IModelList<IModelMyChildNode> {
    }
    

    As you can see, each custom node is derived from the IModelNode interface. The IModelMyNodeWithChildNode node exposes a single IModelMyChildNode child node.

    Note

    When determining whether a property will be a child node or value, the existence of the setter in its declaration is taken into account. If the property has a setter, then it will not automatically be filled with values, and vice versa. So, the MyChildNode property has no setter (marked as ReadOnly in VB).

    The IModelMyNodeWithChildNodes node exposes the list of IModelMyChildNode nodes, as it supports the IModelList<IModelMyChildNode> interface.

    You can decorate interfaces, representing nodes, with the following attributes.

    Attribute Description
    Description Specifies the target node’s description displayed in the Model Editor.
    DisplayName Specifies the node’s display name, which is used as the node’s caption in the Model Editor.
    DisplayProperty Specifies the node’s property name, whose value is used as the node’s caption in the Model Editor.
    ImageName Specifies the node’s image, which is used in the Model Editor. See the ImageNameAttribute topic for additional information.
    KeyProperty Specifies the node’s key property. The key property is used to identify nodes. Only a string property can be a key property. If the KeyProperty attribute is omitted, the Id key property is generated automatically. The use of this attribute is illustrated in the code above.

    To add these nodes to a certain parent node in the Application Model, extend the required node’s interface, as described in the Add a Custom Property to the Existing Node section of this topic.

    public interface IModelMyModelExtension : IModelNode {
        // ...
        IModelMyNodeWithChildNode MyNodeWithOneChildNode { get; }
        IModelMyNodeWithChildNodes MyNodeWithSeveralChildNodes { get; }
    }
    // ...
    public override void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
        base.ExtendModelInterfaces(extenders);
        extenders.Add<IModelApplication, IModelMyModelExtension>();
    }
    

    Rebuild your solution and open the Model Editor to see how the code above affects the Application Model.

    CustomizeApplicationModelInCode_2

    You can add child nodes to the MyNodeWithSeveralChildNodes node via the context menu.

    CustomizeApplicationModelInCode_3

Customize the Application Model via the Nodes Generator Class

In some scenarios, implementing logic to be executed when the custom node is added to the Application Model is a requirement. The so-called Generator classes serve this purpose. The Generator is the ModelNodesGeneratorBase descendant. The ModelNodesGeneratorBase class exposes the GenerateNodesCore virtual method. To implement required logic, override this method. To access the node for which the generator is invoked, use the method’s node parameter. The following snippet illustrates Generator implementation.

using DevExpress.ExpressApp.Model.Core;
// ...
public class MyChildNodesGenerator : ModelNodesGeneratorBase {
    protected override void GenerateNodesCore(ModelNode node) {
        for (int i = 0; i < 10; i++) {
            string childNodeName = "MyChildNode " + i.ToString();
            node.AddNode<IModelMyChildNode>(childNodeName);
            node.GetNode(childNodeName).Index = i;
        }
    }
}

Note

The node parameter exposes the Application property, providing you with access to the whole Application Model.

This Generator creates ten IModelMyChildNode nodes and assigns indexes to them. To apply the Generator to the required node, use the ModelNodesGeneratorAttribute attribute.

[ModelNodesGenerator(typeof(MyChildNodesGenerator))]
public interface IModelMyNodeWithChildNodes : IModelNode, IModelList<IModelMyChildNode> {
}

Each Node can have only one generator. However, additional customizations can be performed via the Generator Updaters

Rebuild your solution and open the Model Editor to see how the code above affects the Application Model.

CustomizeApplicationModelInCode_4

Note

The Generator affects the Application Model zero layer. So, the GenerateNodesCore method will be automatically executed on demand when the data stored in the MyNodeWithSeveralChildNodes is required for the first time. For details on the Model layers, refer to the Application Model Basics topic. To ensure that the generator is executed only once, you can do the following.

  • Set the breakpoint inside the GenerateNodesCore method.
  • Run the Windows Forms application.
  • Invoke the run-time Model Editor. The application execution will be interrupted.
  • Continue the application execution and close the Model Editor.
  • Invoke the run-time Model Editor once again. The application execution will not be interrupted. The Application Model’s zero layer already contains all the required information at this moment, so there is no need to invoke the Generator again.

Implement a Property Having a List of Predefined Values

Let’s assume that the IModelMyNodeWithChildNodes node should expose the SelectedChildNode property, representing the “selected” child node. The user-friendly approach is to provide a drop-down, allowing you to choose one of the valid values in the Model Editor. So, the property should have a list of predefined values. This property can be implemented in the following manner.

using DevExpress.Persistent.Base;
// ...
public interface IModelMyNodeWithChildNodes : IModelNode, IModelList<IModelMyChildNode> {
    [DataSourceProperty("this")]
    IModelMyChildNode SelectedChildNode { get; set; }
}

Generally the DataSourceProperty attribute can be used to specify a name of the current node’s property which exposes a list of the target property’s possible values. Additionally, you can pass “this” as the DataSourceProperty attribute parameter (as illustrated in the code above). In this case, a list of the target property’s possible values will be filled with the current node’s child nodes.

Rebuild your solution, and open the Model Editor to see how the code above affects the Application Model.

CustomizeApplicationModelInCode_5

You can add and remove child nodes, and the drop-down list will reflect changes instantly.

Note

To define a more complex logic for generating a list or predefined values, implement the supplementary calculated property holding the required list as described in the text section of this topic. Then, make this property hidden, by applying the Browsable(false) attribute to it. Finally, decorate your target property with the DataSourceProperty attribute and pass the supplementary hidden property name as the parameter. Note that the list items must be of the same type as the target property.

Use the Domain Logic to Implement the Calculated Property

To customize the node’s properties behavior, the Domain Logic (which is a part of the Domain Components technology) can be used. The common use case of Domain Logic is implementing calculated properties. The following code illustrates how to implement the NumberOfChildNodes property, which gets the number of current node’s child nodes.

using DevExpress.ExpressApp.DC;
// ...
public interface IModelMyNodeWithChildNodes : IModelNode, IModelList<IModelMyChildNode> {
    // ...
    int NumberOfChildNodes { get; }
}
// ...
[DomainLogic(typeof(IModelMyNodeWithChildNodes))]
public class MyNodeWithChildNodesLogic {
    public static int Get_NumberOfChildNodes(
        IModelMyNodeWithChildNodes modelMyNodeWithChildNodes) {
        return modelMyNodeWithChildNodes.NodeCount;
    }
}

The DomainLogicAttribute in the code above indicates that the MyNodeWithMultipleChildNodesLogic class represents the Domain Logic for the IModelMyNodeWithChildNodes node. This class exposes the Get_NumberOfChildNodes method, executed when getting the NumberOfChildNodes property value. This method returns the number of IModelMyChildNode child nodes exposed by the IModelMyNodeWithChildNodes node. For details on Domain Logic, refer to the Domain Components Basics topic.

Rebuild your solution, and open the Model Editor to see how the code above affects the Application Model.

CustomizeApplicationModelInCode_9

You can add and remove child nodes, and the NumberOfChildNodes property value will reflect changes instantly.

Add the Generator Updaters

Each node can have a single Generator assigned. However, you can “attach” one or more Updaters to the Generator. The Generator Updater is the ModelNodesGeneratorUpdater<T> class’ descendant. This class exposes the IModelNodesGeneratorUpdater.UpdateNode virtual method. To implement the required logic, override this method. To access the node for which the Updater is invoked, use the method’s node parameter. The following snippet illustrates the implementation of two Generator Updaters.

public class MyChildNodesUpdater1 : ModelNodesGeneratorUpdater<MyChildNodesGenerator> {
    public override void UpdateNode(ModelNode node) {
        foreach (IModelMyChildNode childNode in ((IModelMyNodeWithChildNodes)node)) {
            if (childNode.Index.HasValue) {
                    childNode.MyIntegerProperty = (int)childNode.Index + 1;
            }
        }
    }
}
public class MyChildNodesUpdater2 : ModelNodesGeneratorUpdater<MyChildNodesGenerator> {
    public override void UpdateNode(ModelNode node) {
        ((IModelMyNodeWithChildNodes)node).SelectedChildNode =
            ((IModelMyNodeWithChildNodes)node)["MyChildNode 9"];
    }
}

Note

The node parameter exposes the Application property, providing you with access to the whole Application Model.

The first Updater sets the IModelMyChildNode.MyIntegerProperty values. The second sets the IModelMyNodeWithChildNodes.SelectedChildNode property value. All the Updaters should be registered in the overridden ModuleBase.AddGeneratorUpdaters method.

using DevExpress.ExpressApp.Model.Core;
// ...
public sealed partial class CustomizeModelExampleModule : ModuleBase {
    // ...       
    public override void AddGeneratorUpdaters(ModelNodesGeneratorUpdaters updaters) {
        base.AddGeneratorUpdaters(updaters);
        updaters.Add(new MyChildNodesUpdater1());
        updaters.Add(new MyChildNodesUpdater2());
    }
    // ...
}

Rebuild your solution and open the Model Editor to see how the code above affects the Application Model.

CustomizeApplicationModelInCode_6

If you previously modified these property values in the Model Editor, you should reset differences for the MyNodeWithSeveralChildNodes node to see the changes. To do this, right-click this node and select Reset Differences. The reason for this is that the Updaters operate at the Application Model zero layer, and changes can be overridden in higher layers.

Important

Node Generators and Node Updaters work at the zero layer and thus have no access to higher Application Model layers where cloned/copied nodes or other model differences reside. Also, all customizations made at this time will not be saved in user model differences. To customize nodes from model differences, use the approaches from the How to change the default Application Model value globally or for multiple nodes article.

The example above illustrates how to customize a custom Nodes Generator’s behavior. However, you can use the same approach to “attach” Generator Updaters to built-in Nodes Generators. The following topics provide complete examples of implementing Generator Updaters for built-in Generators.

See Also