One of the major features that you will see as of 10.1 is the Typed Application Model.
As you will know XAF allows developers to define business class and then builds the http://documentation.devexpress.com/#Xaf/CustomDocument2580 from these declarations and generates a UI based on them. Thereafter if you want to change the UI you simply customise the Application Model using the http://documentation.devexpress.com/#Xaf/CustomDocument2582. The Application Model is therefore a cornerstone of XAF and we have re-designed it from the bottom up in 10.1.
Firstly, let’s take a look at what the Application Model is like now, prior to 10.1. It is currently defined by it’s schema and looks something like this:
<Element Name="Application">
<Element Name="Views">
<Element Name="ListView">
<Element Name="Variants" >
<Attribute Name="Current" />
<Element Name="Variant" >
<Attribute Name="ID" />
<Attribute Name="Caption" />
<Attribute Name="ViewID" />
</Element>
</Element>
</Element>
</Element>
</Element>
This snipped declares the Variants node, as a child node of the ListView node. As you can see, in this simplified example, the only thing that attributes haveare names. There are no types declared. So, even if the ID attribute is intended to hold an integer and Caption to hold a string, there is really no difference. The untyped Application Model stores its data as a set of string. For a caption this is fine, for an integer though, it’ll first need to be converted to a string representation. When retrieving its value, it’ll need to be converted again from a string to an integer. What’s more, you need to remember that an integer is stored there, because all the Application Model stores are plain strings. What a PITA!
Now let’s take a look at how the Application Model worked under the hood. This’ll be a pretty simplified look but it’ll do for our purposes. When an application is started, the types info subsystem (see http://documentation.devexpress.com/#Xaf/CustomDocument3224) collects metadata on all the business classes declared in the application. After this, the Application Model generation is started. Firstly, the BOModel node’s child nodes are generated and filled with values from the types info subsystem. Then all the other Application Model’s nodes and their attributes are filled with data.
After this initial generation is complete, the Application Model is filled with custom data from all the Modules used in the application.
At this point we have the initial Application Model, that part which can’t be changed by the user. Then the Application Model is filled with each user’s customizations . Now, the Application Model generation is complete, and we can use our application.
Now that we know how our untyped Application Model works, we can see the disadvantages:
- We COMPLETELY generate ALL THE Application Model at application startup. This is slow.
- Memory consumption is far from being optimal. Each attribute contains a string value, not a reference. Usually, there are a lot of duplicate info. For example, a string holding the name of a business class can be found in: types info subsystem, a BOModel node’s attribute, an attribute of each of the Views declared for the class (usually, there are several Views for each business type, at least two or three) and so on.
- The Application Model is untyped, and working with it isn’t straightforward. Though we can use node wrappers, the process is generally bulky.
So, in general, the current Application Model, is suboptimal. :-)
But it is improved in 10.1 and here’s how. What we’ve done is to do away with the schema and we now use interfaces instead. So the example above would now be declared like so:
public interface IModelViewVariants : IModelNode
{
IModelVariants Variants { get; set; }
}
public interface IModelVariants : IModelNode,
IModelList<IModelVariant>
{
IModelVariant Current { get; set; }
}
public interface IModelVariant : IModelNode
{
int Id { get; set; }
string ViewID { get; set; }
string Caption { get; set; }
}
Now as you see, attributes not only have names, but they also have types. Also, there’s no need to use node wrappers or convert attribute values to and from string representations. That is much better, don’t you think?
The basic Application Model structure is described by the base IModelApplication interface. This interface defines the root Application node (its attributes and child nodes). Each Module can also extend the Application Model. This is done by specifying additional interfaces derived from the IModelNode interface.
So, firstly, when an application is started, we collect all the interfaces that define the Application Model. Then we compile an object implementing all these interfaces. This object represents the Application Model. At this point the Application Model doesn’t hold any actual data.
Secondly, we fill the Application Model with layers. Our new typed Application Model consists of unmerged layers. Each layer represents a separate data set. Each Module is represented by its own data layer. So, for each Module we create a separate layer and fill it with data supplied by the Module. We also create the user customizations layer and load the user customizations into it. Note that the base Application Model layer defined by the IModelApplication interface doesn’t contain any data at this point.
At this point, the Application Model is ready to be used. When, for example, the Logon Detail View needs to be invoked, the Application Model is accessed, to retrieve the Detail View’s layout. The following code snippet illustrates this:
IModelView modelView =
Application.ModelApplication.Views[
“Logon_DetailView”];
Since the Views node hasn’t been generated yet, a Views generator is invoked which in turn invokes the BOModel generator. As a result, the Views and BOModel nodes’ child nodes are created. Note however, that the created child nodes are NOT filled with data. Then, since we asked for the “Logon_DetailView” data, this node gets populated with data on the base layer. After this, XAF displays the specified View using the data.
As you can see, the base layer of the Application Model is populated with data on-demand. In other words, if you launch an XAF application, and use only a Contact List View then only the Contact_ListView node will be populated with data. All the other View nodes of the Application Model will remain uninitialized.
A note on ASP.NET Web XAF applications. Now, if users haven’t customized the Application Model, a single Application Model instance is shared between all the users. Moreover, even if users have customized the Application Model, the common part of it is still represented by a single instance which is also shared between the users.
Now that we know how our typed Application Model works, we can outline its advantages:
- We never completely generate all the Application Model. It’s created on demand (kind of a lazy initialization). This is fast (or at least we hope it will be).
- Memory consumption is much more optimal. Each attribute holds a reference, not another value copy. So, for example, ideally, a string holding the name of a business class could only be found in the types info subsystem. In ASP.NET Web XAF applications, most of the Application Model (and sometimes ALL the Application Model) is shared between users.
- The Application Model is typed, and working with it becomes more straightforward. No more need for string conversions and use of additional artificial wrappers.
Okay, so let’s finish up with some before and after examples:
Defining a key:
Before:
<Element Name="Variant" KeyAttribute="ID" />
After:
[KeyProperty("Id")]
public interface IModelEditorStateRule : IEditorStateRule {
string Id { get; set; }
}
Declaring multiple child nodes:
Before:
<Element Name="ListView">
<Element Name="Variants">
<Element Name="Variant" Multiple="True" />
After:
public interface IModelViewVariants : IModelNode {
IModelVariants Variants { get; set; }
}
public interface IModelVariants :
IModelNode, IModelList<IModelVariant> {
}
public interface IModelVariant : IModelNode { .. }
Declaring a localisable attribute:
Before:
<Element Name="Variant">
<Attribute Name="Caption" IsLocalized="True" />
After:
public interface IModelVariant : IModelNode {
[Localizable(true)]
string Caption { get; set; }
}
Specifying an image for an node:
Before:
<Element Name="Variant" ImageName="ModelEditor_ListView">
After:
[ImageName("ModelEditor_ListView")]
public interface IModelVariant : IModelNode {...
Specifying the display property:
Before:
<Element Name="Variant" DisplayAttribute="Caption" />
After:
[DisplayProperty("Caption")]
public interface IModelVariant : IModelNode {...
Specifying a required attribute:
Before:
<Element Name="Variant" >
<Attribute Name="ViewID" Required="True" />
After:
public interface IModelVariant : IModelNode {
[Required()]
string ViewID { get; set; }
Specifying a child node’s Index attribute used to order child nodes:
Before:
<Element Name="Variant" >
<Attribute Name="Index" IsNewNode="True"/>
After:
Nothing to do here as the base IModelNode interface already declares such an attribute.
Using an enumeration to specify the possible values for an attribute displayed in a dropdown:
Before:
<Element Name="NavigationItems" >
<Attribute Name=""
DefaultChildItemsDisplayStyle=""""
Choice=""List,LargeIcons=""""/>
After:
public interface IModelNavigationItems : IModelNode {
ItemsDisplayStyle DefaultChildItemsDisplayStyle { get; set; }
Getting data from the Application Model. Retrieving a node:
Before:
DictionaryNode viewsNode =
Application.Model.RootNode.GetChildNode("Views");
DictionaryNode viewNode =
viewsNode.FindChildNode(
BaseViewInfoNodeWrapper.IdAttribute, viewId);
After:
IModelView modelView =
Application.ModelApplication.Views[viewId];
Getting data from the Application Model. Retrieving an attribute value:
Before:
DictionaryNode viewNode =
viewsNode.FindChildNode(
BaseViewInfoNodeWrapper.IdAttribute, viewId);
string myCustomFilter =
viewNode.GetAttributeValue("MyCustomFilter");
After:
IModelViewMyExtention myModelView =
(IModelViewVariants)
Application.ModelApplication.Views[viewId];
string myCustomAttrValue = myModelView.MyCustomFilter;
Well that’s all for this post, until next time – happy XAF-ing! :-)