Updated: Feb 4 (see end of post)
Although I promised to talk about Stephen Manderson’s Dashboard module I will skip and I will provide a discussion on dynamic member aliases. The reason is that cross data source filtering provided by the Dashboard Suite does not permit aliases for our aggregated members. I already touched dynamic members subject at calculated members creation—Pros and Cons. However today we will go in details through all steps involved in creating member aliases through XAF’s Application Model and as usual at the end of the post a sample will be available.
In the following image you can see existing implementations from our community project eXpand as discussed in calculated members creation—Pros and Cons.
In this post we are only interested in the RuntimeCalculatedMember entry.,
Extending the model
Following our documentation to the letter (How to: Extend the Application Model) first we need to define and interface that will persist all parameters to the model.
publicinterfaceIModelRuntimeCalculatedMember : IModelMember {
[Required]
stringAliasExpression { get; set; }
}
The context popup menu
You probably noticed in IModelRuntimeCalculatedMember instead of deriving from IModelNode interface as suggested in our docs we used the IModelMember. This is an already registered interface that describes the model members. So do not need to re extend our Application Model with the IModelRuntimeCalculatedMember. XAF knows how to create the popup menu with the correct entry:
Changing Model Editor’s behavior
AliasExpression
In IModelRuntimeCalculatedMemberwe marked the AliasExpression property with the RequiredAttribute because an empty alias is not valid. XAF Model Editor will notify that this is a mandatory property with an asterisk icon as shown:
Editable Type at Runtime
Since our IModelRuntimeCalculatedMember derives from IModelMember and not from IModelNode it inherits all its properties. This however, raises a conflict with XAF’s native support for design time custom members. The conflict refers to IModelMember Type property which is by design editable only in design time. As we see in the next image is marked with a ModelReadOnlyAttribute which tells Model Editor what to do.
In XAF everything is overridable! So to change Type property we need to create a new Type property in our IModelRuntimeCalculatedMember and mark it with the new keyword. In addition we need to create and use an AlwaysEditableCalculator instead of the DesignerOnlyCalculator:
Remember the IsCustom functionality
As I already mentioned XAF has native support for runtime members only if Model Editor is at design time. This is done adding a new IModelMember and setting IsCustom to true as shown:
IModelRuntimeCalculatedMember inherits from IModelMember. This means when we create a new IModelRuntimeCalculatedMember XAF will set IsCustom to true as it does for simple IModelMember. ModuleUpdaters that can change Application Model’s values are designed to work only in the “zero” layer and here we need to change the differences made from the Model Editor designer. However XAF is designed to be extensible and our docs to solve the problems Convert Application Model Differences. The solution to this problem is to implement Implement a Node Updater following our docs to the letter:
NodeUpdaters are one more powerful tool provided to us by XAF an is designed to take us out of trouble. As you can see in above image it does it fairly simple.
- We make our module or any class a NodeUpdater by implementing the IModelNodeUpdater<T> interface.
- We register the new NodeUpdater by overriding the AddModelNodeUpdaters or our module.
- Implement our logic inside the the UpdateNode method
The Dessert
The AliasExpression property will hold basic criteria + complex criteria as well as aggregated function. Right now there is no editor associated with the property. However we can easily associate a CriteriaModelEditorControl editor as shown:
As we see the CriteriaModelEditorControl offers great features:
The Coding part
Up to now we modeled a new Application Model member type the IModelRuntimeCalculatedMember. What remains is to write the algorithm to create that member in our TestBO object. Unfortunately we cannot use the standard place for extending our business objects as suggested by our knowledgebase. This is because the Application Model is not fully constructed at that point. However we can use any other place, as far as our algorithm is smart enough to execute just one time without consuming many resources.
privatestaticIEnumerable<IModelRuntimeCalculatedMember> GetCustomFields(IModelApplication model) {
return model.BOModel.SelectMany(modelClass => modelClass.AllMembers).OfType<IModelRuntimeCalculatedMember>();
}
staticvoidAddRuntimeMembers(IModelApplication model) {
foreach (IModelRuntimeCalculatedMembermodelRuntimeMemberinGetCustomFields(model))
try {
Type classType = modelRuntimeMember.ModelClass.TypeInfo.Type;
XPClassInfo typeInfo = _dictionary.GetClassInfo(classType);
lock (typeInfo) {
if (typeInfo.FindMember(modelRuntimeMember.Name) == null) {
newXpandCalcMemberInfo(typeInfo, modelRuntimeMember.Name, modelRuntimeMember.Type, modelRuntimeMember.AliasExpression);
XafTypesInfo.Instance.RefreshInfo(classType);
}
}
In fact forget about the many resources when using our frameworks, Please search our Support Center, there are answers to almost all common problems you will face! If not shoot the guys they are happy to die for you !
Now let’s touch the unknown XpandCalcMemberInfo class:
- XPO allows non persistent calculated properties with the use of PersistentAliasAttribute.
- To create a dynamic member we simply need to instantiate an XPCustomMemberInfo derivative like the XpandCalcMemberInfo.
publicclassXpandCalcMemberInfo : XPCustomMemberInfo {
publicXpandCalcMemberInfo(XPClassInfo owner, string propertyName, Type propertyType, string aliasExpression)
: base(owner, propertyName, propertyType, null, true, false) {
AddAttribute(newPersistentAliasAttribute(aliasBLOCKED EXPRESSION;
}
publicoverrideobjectGetValue(object theObject) {
var xpBaseObject = ((XPBaseObject)theObject);
return !xpBaseObject.Session.IsObjectsLoading&& !xpBaseObject.Session.IsObjectsSaving
? xpBaseObject.EvaluateAlias(Name)
: base.GetValue(theObject);
}
protectedoverrideboolCanPersist {
get { returnfalse; }
}
}
Therefore in the constructor we added a PersistentAliasAttribute using the AddAttribute method of the XPCustomMemberInfo. In addition we had to modify the returned value of the member by overriding the GetValue method and using an approach similar with the EvaluateAlias docs,
Best place to create the dynamic members
In my opinion there is no such place and everything depends on our requirements. However I can suggest a solution we used in eXpand for many years without problems. You can do it just after login where the the user model is fully merged.
publicsealedpartialclassDynamicMemberAliasesModule : ModuleBase {
publicoverridevoidSetup(XafApplication application) {
base.Setup(application);
application.LoggedOn+= (sender, args) => RuntimeMemberBuilder.AddFields(application.Model);
}
The check please!
We discussed in detail all the steps needed for dynamic member aliases. In DynamicMemberAliases.zip is a XAF solution to see everything in action.
To implement the rest of the dynamic members (RuntmeNonPersistent, RuntimeOrphanedCollection, RuntimeMember) you need to follow one of the steps bellow:
- Use Core or ModelDifference module of eXpandFramework (see: How to use an eXpand module with an existing XAF application)
- Copy RuntimeMemberBuilder, all interfaces from IModelRuntimeMember.cs and extend the Application Model with the included IModelMemberEx interface.
That’s all you need, to have a few productive and happy hours with XAF . I really enjoy working with such a flexible framework and I am sure the XAF team will continue to surprise us in the future!
Remember next post will talk the integration of XAF + Dashboard suite so stay tuned.
We are happy to read your feedback about anything you heard today!. Remember that your questions are the best candidates for future posts .
Until next time, happy XAF’ing!
Update Feb 4
The sample DynamicMemberAliases.zip updated to:
- Support the Delete action in IModelRuntimeCalculatedMember (similar with The context popup menu paragraph)
- Allow true runtime aliases without the need of application restart!