Skip to content
apobekiaris edited this page Oct 17, 2020 · 110 revisions

GitHub issues GitHub close issues

About

The ModelMapper allows to control all XAF components from the application model.

Details

This is a platform agnostic module that transforms any type to XAF model format and will extend the model with a simple call like:

public override void Setup(ApplicationModulesManager moduleManager) {
	base.Setup(moduleManager);
	moduleManager.Extend(new ModelMapperConfiguration(typeof(MyClass),typeof(IModelListView),typeof(AnyModelInterfaceType)) );
}

For each mapped type a container interface will be generated and used to extend the model. There are several extension methods you can use to identify the major mapped types for further usage such as extension or population.

typeToMap.ModelMapperContainerTypes();
typeToMap.ModelTypeName();
typeToMap.ModelMapContainerName();
typeToMap.ModelListType();
typeToMap.ModelListItemType();
//GetRepositoryItemNode, AddRepositoryItemNode, GetControlsItemNode, AddControlsItemNode
var listView = (IModelListView)application.Model.Views["Customer_ListView"];
var listViewColumn = listView.Columns["Name"];
listViewColumn.GetRepositoryItemNode(PredefinedMap.RepositoryItemButtonEdit);
listViewColumn.AddRepositoryItemNode(PredefinedMap.RepositoryItem);

var detailView = (IModelDetailView)application.Model.Views["Customer_DetailView"];
var modelPropertyEditor = detailView.Items["Name"];
modelPropertyEditor.GetRepositoryItemNode(PredefinedMap.RepositoryItemButtonEdit);
modelPropertyEditor.AddRepositoryItemNode(PredefinedMap.RepositoryItem);

To extend an existing map you can use:

public override void Setup(ApplicationModulesManager moduleManager) {
	base.Setup(moduleManager);
	applicationModulesManager.ExtendMap(typeof(MyClass)).Subscribe(_ => _.extenders.Add(_.targetInterface,typeof(IModelSomthing)))
}

Model Editor control on all major components used from XAF Traditionally: You need to create model interfaces and extend the model for all component structures. You need to link them with the actual runtime objects and editors and update their values. As this is not a non-trivial case a large number of Unit and EasyTest must run on evert build. Xpand.XAF.Modules Solution: The cross platform Xpand.XAF.Modules.ModelMapper ships with predefined maps for all the common XAF components such as Grids, Charts, Tree, Pivot etc. In the next screencast we see how to extend the XAF model with the GridView and the GridColumn components. Then we used the model editor to modify the model and run the application to test our changes at runtime.

aYbdUf4HwV

image

Map Generation

Generating maps for many types is costly however it happens in parallel and only once. Afterwards the model interfaces are loaded from the ModelMapperAssembly.dll found in path. All mapped types are included in this one assembly even if the map was executed from different modules.

The module will generate automatically the ModelMapperAssembly.dll under the following conditions.

  1. If the assembly does not exist in path.
  2. If the ModelMapper module ModuleVersionId changed.
  3. If any of the mapped types assembly ModuleVersionId changed.
  4. If Mapping customization changed. (Read more on How to customize a Map)

It is possible for the map to be outdated if an indirect reference of the participating types has changed. The solution is to simply delete the ModelMapperAssembly.dll from path and let it regenerate.

What is mapped
  1. All Public or Public Nested Types.
  2. All public read/write value types and string properties will be transformed to NullAble types.
  3. All public readOnly or read/write Reference properties.
  4. All public collections that their type can be detected. (They will not get populated though, it is up to the map author to customize the map as in how to customize the map section).
  5. All public type attributes if all their arguments Type are public.
  6. All Description attributes public or not, resulting in populating the ModelEditor help panel as in the next shot. image
What is not mapped
  1. Private or internal Types.
  2. Attributes where any of their argument type is not public.
  3. Properties marked as Obsolete or Browsable false or DesignerSerializationVisibility hidden.
  4. Properties that lead to recursion when a new XAF model node is generated (XAF will follow all possible paths and this can recourse).
  5. DevExpress DesignTime classes.
  6. ReservedPropertyNames, ReservedPropertyTypes, ReservedPropertyInstances (see how to customize a map).
  7. Already mapped types.
  8. The DefaultValue attribute.
  9. Attributes where any of their arguments is a Flag combination (XAF fails to generate such configurations).
Predefined maps

The module ships with a large list of maps well tested, ready to use and already integrated with eXpandFramework main modules.

You are free to install any combination of them like the next snippet which installs the GridView and LayoutView maps:

public override void Setup(ApplicationModulesManager moduleManager){
	base.Setup(moduleManager);
	moduleManager.Extend(PredefinedMap.AdvBandedGridView,PredefinedMap.BandedGridColumn,PredefinedMap.GridView,PredefinedMap.GridColumn);
}

The predefined maps are categorized as:

  1. Several ListEditor maps extend the IModelListView, IModelColumn and their visibility is bound to the respective ListEditor.

    Editor ListView Column
    GridListEditor image image
    AdvBandedGridListEditor image image
    LayoutViewListEditor image image
    TreeListEditor image imageNavigationimage
    ChartListEditor image image
    PivotGridListEditor image image
    SchedulerListEditors(Win/Web) image image
  2. The RepositoryItems map extend only in windows both the IModelColumn and the IModelPropertyEditor interfaces. However since the actual repository is known only in runtime, the extension is of a model list type where multiple map nodes can be added and they will be matched automatically at runtime favoring the node index.In this category the following maps exist: RepositoryItem, RepositoryItemTextEdit, RepositoryItemButtonEdit, RepositoryItemComboBox, RepositoryItemDateEdit, RepositoryFieldPicker, RepositoryItemPopupExpressionEdit, RepositoryItemPopupCriteriaEdit, RepositoryItemImageComboBox, RepositoryItemBaseSpinEdit, RepositoryItemSpinEdit, RepositoryItemObjectEdit, RepositoryItemMemoEdit, RepositoryItemLookupEdit, RepositoryItemProtectedContentTextEdit, RepositoryItemBlobBaseEdit, RepositoryItemRtfEditEx, RepositoryItemHyperLinkEdit, RepositoryItemPictureEdit, RepositoryItemCalcEdit, RepositoryItemCheckedComboBoxEdit, RepositoryItemColorEdit, RepositoryItemFontEdit, RepositoryItemLookUpEditBase, RepositoryItemMemoExEdit, RepositoryItemMRUEdit, RepositoryItemBaseProgressBar, RepositoryItemMarqueeProgressBar, RepositoryItemProgressBar, RepositoryItemRadioGroup, RepositoryItemTrackBar, RepositoryItemRangeTrackBar, RepositoryItemTimeEdit, RepositoryItemZoomTrackBar, RepositoryItemImageEdit, RepositoryItemPopupContainerEdit, RepositoryItemPopupBase, RepositoryItemPopupBaseAutoSearchEdit

    --- ---
    image image
  3. The Controls maps extends in both platforms the IModelPropertyEditor interface in a design similar to the RepositoryItem maps.

    In this category we find the following maps: RichEditControl, DashboardViewer, ASPxDashboard, ASPxHtmlEditor, ASPxUploadControl, ASPxDateEdit, ASPxHyperLink, ASPxLookupDropDownEdit, ASPxLookupFindEdit, ASPxSpinEdit, ASPxTokenBox, ASPxComboBox

    Windows Web
    image image
  4. Module specific maps. In this category we have the DashboardDesigner map which extends the IModelDashboardModule interface as shown:
    image

  5. In the IModelView related maps category we find the XafLayoutControl, the SplitContainerControl and the PopupControl maps.

XafLayoutControl SplitContainerControl
image image

ASPxPopupControl

DashboardView ListView DetailView
image image image
How to customize a map

You can customize the map the following ways:

  1. On declaration using the ModelMapperConfiguration properties.
        public interface IModelMapperConfiguration{
            string VisibilityCriteria{ get; }
            string ContainerName{ get; }
            string MapName{ get; }
            string DisplayName{ get; }
            string ImageName{ get; }
            List<Type> TargetInterfaceTypes { get; }
            Type TypeToMap{ get; set; }
            bool OmitContainer{ get; }
        }
  2. Using static collections of the TypeMappingService class such as ReservedPropertyNames, ReservedPropertyTypes, ReservedPropertyInstances, AdditionalTypesList, AdditionalReferences
  3. Adding or inserting Type or Property related rules. For example to disable the map of all attributes you can write:
    TypeMappingService.PropertyMappingRules.Add(("Disable", tuple => {
        foreach (var modelMapperPropertyInfo in tuple.propertyInfos){
            foreach (var modelMapperCustomAttributeData in modelMapperPropertyInfo.GetCustomAttributesData().ToArray()){
                modelMapperPropertyInfo.RemoveAttributeData(modelMapperCustomAttributeData);
            }
        }
    }));

Several real world examples are available at Predefined namespace.

How to bind a map

All predefined maps are bound to the runtime instance automatically. The module when any view controls are created will search its model for IModelModelMap properties and use them to configure the related instances. It is possible to customize the process as in next snippet:

ModelBindingService.ControlBind.Subscribe(parameter => parameter.Handled = true);

If you create a custom map you can manually map as:

modelNode.BindTo(objectInstance)

The BindTo method will follow the hierarchy tree respecting the disabled nodes and will update all properties that are not null.


Possible future improvements:
  1. Chart Calculated fields #717.
  2. Any other need you may have.

Installation

  1. First you need the nuget package so issue this command to the VS Nuget package console

    Install-Package Xpand.XAF.Modules.ModelMapper.

    The above only references the dependencies and nexts steps are mandatory.

  2. Ways to Register a Module or simply add the next call to your module constructor

    RequiredModuleTypes.Add(typeof(Xpand.XAF.Modules.ModelMapperModule));

Versioning

The module is not bound to DevExpress versioning, which means you can use the latest version with your old DevExpress projects Read more.

The module follows the Nuget Version Basics.

Dependencies

.NetFramework: net461

DevExpress.ExpressApp Any
Enums.NET 3.0.3
Fasterflect.Xpand 2.0.7
JetBrains.Annotations 2020.1.0
Mono.Cecil 0.11.2
System.CodeDom 4.7.0
System.Interactive 4.1.1
System.Reactive 4.4.1
System.ValueTuple 4.5.0
Xpand.Collections 1.0.1
Xpand.Extensions 2.202.57
Xpand.Extensions.Reactive 2.202.58
Xpand.Extensions.XAF 2.202.58
Xpand.XAF.Modules.Reactive 2.202.58
Xpand.VersionConverter 2.202.10

Issues-Debugging-Troubleshooting

To Step in the source code you need to enable Source Server support in your Visual Studio/Tools/Options/Debugging/Enable Source Server Support. See also How to boost your DevExpress Debugging Experience.

If the package is installed in a way that you do not have access to uninstall it, then you can unload it with the next call at the constructor of your module.

Xpand.XAF.Modules.Reactive.ReactiveModuleBase.Unload(typeof(Xpand.XAF.Modules.ModelMapper.ModelMapperModule))

Tests

The module is tested on Azure for each build with these tests