Skip to main content
.NET 8.0+

Use Metadata to Customize Business Classes Dynamically

  • 8 minutes to read

This topic describes how to manipulate business object metadata to alter an existing data model without the need to modify its code. This technique allows end users (application administrators) to add custom fields to business objects in the Model Editor so that there is no need to recompile the application. XAF application developers can also use this technique to add custom fields to objects implemented in external libraries whose code is not available for modification.

A custom field can be calculated (values are computed based on a specified expression) or persistent (values are saved in the data store). Use the table below to determine what types of custom fields are compatible with your business objects:

XPO Entity Framework Core Non-persistent objects
Calculated fields icon_checkmark_green icon_checkmark_green
Persistent (editable) fields icon_checkmark_green icon_checkmark_green

Custom fields declared in the Application Model are automatically collected and registered within the Types Info subsystem. Pay attention to the following specifics of runtime custom fields usage:

  • Generally, persistent fields should be added at design time only. It is not good practice to allow end users to alter the database schema - allow this only as a last resort.
  • It is strongly recommended to restart the application after a custom field is deleted at runtime.
  • Custom fields added to the user differences (Model.User.xafml file) are not supported when the ThreadSafeDataLayer Data Access Layer is in use. In XAF, this Data Layer is enabled when you instantiate an XPObjectSpaceProvider using the constructor that takes the threadSafe parameter and set this parameter to true. The SecuredObjectSpaceProvider used in Integrated Mode of the Security System uses the ThreadSafeDataLayer by default, when the threadSafe parameter is not specified. As a workaround, copy a custom field to the administrator’s differences (Model.xafml) by using the Model Merge feature.
  • Calculated fields are always evaluated on the client side. So, sorting, grouping, filtering, and summary calculation do not work for calculated columns in Server, ServerView, InstantFeedback, and InstantFeedbackView modes. If you use XPO and create a new calculated field with one of the techniques described in this topic, the PersistentAliasAttribute is automatically added to this newly created field. Thus, these operations are available for the corresponding column.
  • You cannot add custom persistent fields to the EF Core data model.
  • You can add custom persistent fields to a non-persistent class only if it inherits NonPersistentBaseObject. Note that these fields are not stored in a database. You can change them in the UI and get values in code as described in the Access a Custom Field in Code section.
  • You cannot add custom collection properties to a non-persistent class.

This topic includes the following sections:

Note

To see custom fields in action, refer to the Custom Fields section in the Feature Center demo installed with XAF. The default location of the application is %PUBLIC%\Documents\DevExpress Demos 24.2\Components\XAF\FeatureCenter.NETFramework.XPO.

Add a Custom Calculated Field in the Model Editor

To add a custom calculated field, invoke the Model Editor at design time or in a WinForms application at runtime, or use the standalone Model Editor tool (for example, to add a field to an ASP.NET Web Forms application). Then follow the steps below.

  1. Right-click the BOModel | <Class> | OwnMembers node and choose Add… | Member.

    AddCustomField_ME

  2. Focus the new member node. In the property grid to the right, specify the IModelMember.Name and IModelMember.Type values.
  3. Pass the expression to be used to compute the field value to the IModelMember.Expression property. You can click the ellipsis button located to the right of the property value to invoke the Expression Editor dialog. In this editor, you can select functions, operators, and operands using editor controls.
  4. If you are creating a custom field at runtime, specify the IModelCommonMemberViewItem.PropertyEditorType value. Choose an appropriate Property Editor from the combo box. At design time, the PropertyEditorType value is optional - an appropriate Property Editor is determined automatically. Note that platform-specific Property Editors are not available in the common module.
  5. Save your changes and restart the Model Editor.
  6. Create a Detail View item or List View column mapped to the newly added custom field (see Display a Nested Property Editor in a Detail View and List View Columns Customization).

The image below illustrates the member node settings described above.

AddCustomField_ME2

Add a Custom Persistent Field in the Model Editor in Visual Studio

Creating a custom persistent field is very similar to creating the calculated field described in the previous section. The only difference is that you should set the IModelMember.IsCalculated property to false instead of specifying the Expression value.

Note

Thread-safe data layers have limitations. If you create a SecuredObjectSpaceProvider or an XPObjectSpaceProvider and set threadSafe to true in the constructor, the following features are unavailable:

These capabilities require your application to load information about custom persistent fields and then update the database schema. However, thread-safe data layer does not support data model modifications after a database connection is established.

Add a Custom Persistent Field in the Model Editor at Runtime

The creation of custom persistent fields is allowed at design time only (the IsCalculated value is not editable). To allow end users to add custom persistent fields in the Model Editor at runtime, set the static ModelMemberReadOnlyCalculator.AllowPersistentCustomProperties property to true. The created field should then be merged to the application-level model differences (see Model Merge Tool). To allow updating of the database schema after a field is added at runtime, set the XafApplication.DatabaseUpdateMode property to DatabaseUpdateMode.UpdateDatabaseAlways. Also, consider the note about ThreadSafeDataLayer from the previous section. A column mapped to the current field will automatically be added to the database table.

Important

Add Custom Fields in Code

To extend an existing business class from an external class library, you can add a custom field in code. Edit the Module.cs (Module.vb) file and override the ModuleBase.CustomizeTypesInfo method. The code below demonstrates how to add a persistent field (PersistentField1), a persistent field with the ImmediatePostDataAttribute applied (PersistentField2), and a calculated field (CalculatedField).

using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
// ...
public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
    base.CustomizeTypesInfo(typesInfo);
    TypeInfo personTypeInfo = typesInfo.FindTypeInfo(typeof(Person)) as TypeInfo;
    if (personTypeInfo != null) {
        personTypeInfo.CreateMember("PersistentField1", typeof(int));
        personTypeInfo.CreateMember("PersistentField2", typeof(int)).AddAttribute(new ImmediatePostDataAttribute());
        personTypeInfo.CreateMember("CalculatedField", typeof(int), "PersistentField1 + PersistentField2");
    }
}

Access a Custom Field in Code

The following snippet illustrates how to access a custom field in a Controller context using the IMemberInfo.GetValue method.

Person person = this.View.CurrentObject as Person;
ITypeInfo personInfo = this.Application.TypesInfo.FindTypeInfo(typeof(Person));
int? value = personInfo.FindMember("CalculatedField").GetValue(person) as int?;

The following snippet illustrates how to access a custom field in a Controller context from the PropertyEditor.ControlValue property.

ViewItem viewItem = ((DetailView)View).FindItem("CalculatedField");
decimal? value = ((PropertyEditor)viewItem).ControlValue as decimal?;

Create Associations in Code

The following snippet illustrates how to declare an association between two class properties when you have no access to the code of these classes and cannot apply the AssociationAttribute directly.

public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
    base.CustomizeTypesInfo(typesInfo);
    ITypeInfo typeInfo1 = typesInfo.FindTypeInfo(typeof(DomainObject1));
    ITypeInfo typeInfo2 = typesInfo.FindTypeInfo(typeof(DomainObject2));
    IMemberInfo memberInfo1 = typeInfo1.FindMember("Object2");
    IMemberInfo memberInfo2 = typeInfo2.FindMember("Object1s");
    if(memberInfo1 == null) {
        memberInfo1 = typeInfo1.CreateMember("Object2", typeof(DomainObject2));
        memberInfo1.AddAttribute(new DevExpress.Xpo.AssociationAttribute("A", typeof(DomainObject2)), true);
    }
    if(memberInfo2 == null) {
        memberInfo2 = typeInfo2.CreateMember("Object1s", typeof(XPCollection<DomainObject1>));
        memberInfo2.AddAttribute(new DevExpress.Xpo.AssociationAttribute("A", typeof(DomainObject1)), true);
        memberInfo2.AddAttribute(new DevExpress.Xpo.AggregatedAttribute(), true);
    }
    ((XafMemberInfo)memberInfo1).Refresh();
    ((XafMemberInfo)memberInfo2).Refresh();
}

As you can see in the code above, the IMemberInfo.AddAttribute method’s second parameter is true. In this case, Types Info does not reload information from XPDictionary immediately. After adding all required attributes, call the XafMemberInfo.Refresh method for each updated member. Alternatively, you can call XafTypesInfo.RefreshInfo for each type containing updated members:

public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
    // ...
    typesInfo.RefreshInfo(typeof(DomainObject1));
    typesInfo.RefreshInfo(typeof(DomainObject2));
}

Tip

To access the XPDictionary directly, use the XpoTypesInfoHelper.GetXpoTypeInfoSource method.

See Also