
Smart forms of the eXpressApp Framework (XAF). Part 2 - Metamodel UI Applications
- Tutorial

In the first part, I talked about how to “animate” forms of the eXpressApp Framework by adding simple business rules (data control, control of highlighting, accessibility and visibility of fields) using attributes in the code of business entities. In this part I will talk about how to solve this problem by setting up the XAF metamodel of the aka Application Model application, and also, in fact, I will talk about why this metamodel is needed and how it is built from the inside.
I will not forget about the wide possibilities of expanding the metamodel by developers, its editing by end users through the visual editor Model Editor and much more. I also think that after reading this article, you will probably find some similarities between the XAF metamodel, as well as the XAFML description language, with all the well-known CSS and XAML (as well as QML, LSML, etc.), as well as the in-depth ideas embedded in these technologies . Anyone who is not afraid to get to know more about such an unusual product of domestic bicycle construction (by the way, written before the appearance of analogues at Microsoft), please welcome under cat.
Attributes in Business Entity Code vs. Settings in the metamodel
So, if we critically look at what happened to the original business entities from the previous article, then someone can definitely be scared from the abundance of various attributes:
- [DomainComponent]
- public interface IAccount {
- [RuleRequiredField, RuleUniqueValue]
- [RuleRegularExpression (@ "^ [_ a-z0-9 -] + (. [_ A-z0-9 -] +) * @ [a-z0-9 -] + (. [A-z0-9 -] +) * (. [az] {2,4}) $ ")]
- string Email {get; set; }
- [FieldSize (25)]
- [ImmediatePostData]
- [Appearance ("MarkUnsafePasswordInRed", "Len (Password) <6", FontColor = "Red")]
- string Password {get; set; }
- }
- [XafDefaultProperty ("FullName")]
- [DomainComponent, ImageName ("BO_Person")]
- public interface IPerson {
- string LastName {get; set; }
- string FirstName {get; set; }
- [Calculated ("Concat (IsNull (FirstName, ''), '', IsNull (LastName, ''))”]]
- string FullName {get; }
- DateTime Birthday {get; set; }
- }
- [DomainComponent]
- public interface IOrganization {
- [RuleRequiredField]
- string Name {get; set; }
- [Aggregated]
- IList
Staff {get; } - [RuleRequiredField (TargetCriteria = "Staff.Count> 0")]
- [DataSourceProperty ("Staff") / *, DataSourceCriteria ("StartsWith (FirstName, '123')") * /]
- [Appearance ("ChangeManagerAvailabilityAgainstStaff", "Staff.Count = 0", Visibility = ViewItemVisibility.Hide, Context = "DetailView")]
- IPerson Manager {get; set; }
- }
The abundant use of attributes that specify the appearance and behavior of the user interface directly in the code of a business entity, although it helps to significantly increase the speed of development in “pass and forget” projects, may not be so convenient in large projects. Also, do not forget about scenarios where any references to eXpressApp Framework assemblies may be undesirable (for example, a non-XAF project that simply uses the same library of business entities, but without any "left" attributes there). And finally, I’m not even talking about possible poor readability
It would be great if there was a separate centralized place to configure the appearance and behavior of the user interface of the application, as well as store other settings that are not directly related to business logic, which ideally should be separated from the interface. Partial or complete transfer of settings to a separate level can not only cleanse your soul before the followers of the separation of
However, I can not say unequivocally that one of these approaches is ideal, and the other is forbidden to use. On the contrary, I am convinced that each of these approaches deserves its place in the developer's arsenal and should be applied depending on the conditions of the project (such as size, urgency, etc.). For example, our main web site (front-end) is a regular (non-XAF) ASP.NET application and uses our visual ASP.NET components for the interface and our ORM library eXpress Persistent Objects (XPO)to access data. At the same time, the library with business entities (Customer, Issue, Bug Report, Question, etc.) does not have references to the eXpressApp Framework and does not use XAF-specific attributes and entities, although it is used in several back-end applications written in XAF . Moreover, all the settings for the representations of these business entities are made through the metadata of individual XAF modules.
Application metamodel or, moreover, onion ...
Such a repository of settings in XAF is the Application Model, which is a layer of metadata describing the appearance and behavior of the UI. Technically, this is not just one layer, but a bunch of layers, the changes in which are layered on top of each other (as in the bow head) according to the principle of superposition. The very first layer, the so-called “zero” ring of
For a better understanding of the place of the metamodel in the general architecture of the XAF application, I will give a small diagram:

Changes or differences between layers in XAF terms are commonly called model differences. Hence the name “Model.DesignedDiffs.XAFML” for XML module files containing these differences. I note that you can change the contents of each layer in several ways:
1. a special visual editor - Model Editor ;
2. directly editing XAFML files;
3. through the program code.
The superposition of all layers will form the final application model:

Here, by the “final” application model, I mean what the developer will get in his code when he turns to some element or its property. Also, the metamodel itself is “lazy”, i.e. superposition is calculated only when necessary, for example, upon request from the internal API, when it is required to build some kind of user interface screen. It is important to note here that these calculations are not done every time when accessed, but are correctly cached during execution.
I’m talking a lot about layers here, but I didn’t explain in detail why we came up with them, because outside the framework of our framework there are so many applications and technologies where there are no layers at all, take XAML for example. Therefore, I will list below the main reasons / requirements for creating a multilayer metamodel of the XAF application:
1. The ability to flexibly configure applications for multiple platforms;
2. Acceleration of development due to application configuration at runtime;
3. Easy development of custom modules that change the standard behavior;
4. The ability to centrally configure an already deployed application by the administrator;
5. The ability to individually change the appearance and behavior of the application for end users (including dynamically, for example, based on access rights).
Practical examples of the metamodel
I think enough theory, and let's work with our metamodel in practice. To do this, rebuild our application and double-click on the Model.DesignedDiffs.xafml file of a platform-independent module (or call the appropriate command from the context menu in Solution Explorer) of our previous application:

This will open the model editor (the Model Editor itself), as shown in the picture above. In a nutshell, this editor is a fairly “Spartan” interface with a tree with all the model elements on the left and the property inspector on the right (there are also built-in tools for quickly searching, localizing, merging model layers, but we will not dwell on them here - more can be found in the documentation in English).
Just from this picture you can quickly evaluate the aspects of the application controlled by the metamodel. By the way, what we see here is a visual representation of the zero layer of the model generated by XAF according to the types found in the current module and its dependencies.
Imagine that we suddenly needed to slightly change the appearance of one of our forms (ICustomer_ListView). If we delete one column, set sorting for another, change the column order and finally localize our application into Russian, then this XML will be saved in the corresponding layer (Habr.Module / ModelDesignedDiffs.XAFML), which is, in fact, difference from the previous layer:
* To save space, I specifically do not provide here the localization for which a separate XAFML file will be created.
If we run our application, we will see that our settings are applied as expected (note the sorting and the reordered columns):

Technically, this means that our code (from the standard XAF modules) turned to the metamodel and configured the controls accordingly .
I especially want to highlight the script with the application setup at runtime, and not in Visual Studio. In this case, all the settings made while the application is running fall into the very last layer - the user settings layer. By default, this last layer is represented by the Model.User.XAFML file, but from experience most people prefer the file system storage in the database (most likely in the future we will make this the default behavior). Thanks to the powerful DevExpress controls, the application setup does not require any special skills and tools and can be performed in most cases by the end users themselves. For example, the user can change the sorting, grouping of filters, the location of controls on the form, their sizes and much more, using the tools of the visual controls themselves. If you want more fine-tuning and you are an advanced user or developer, then at your service there is a Model Editor, also available at runtime.
Let's now see how you can use the visual editor to quickly customize detailed forms. I think,this can hardly be demonstrated better than in the video .
The very idea of "rubber" auto-generated forms was intended, basically, to make life easier for developers, and it solves two main tasks:
1. More convenient and faster configuration of forms at the development stage
2. More convenient deployment and support of the application
The first is achieved due to the fact that with the proposed approach you don’t need to spend hours on aligning hundreds of fields in some complex form and redo everything if new fields appear in the business model. Since metadata is dynamically generated on the basis of business entities, during development you can change your business model as much as you like and be sure that your changes will automatically affect the application model (of course, there is an opportunity to “fix” form settings so that changes in business entities did not affect them).
The second is possible due to the ability to give the forms customization to your end users or application administrators, as this task can be completely done at runtime. Due to the multi-layer application model, you can centrally change the settings of end-user applications by making the necessary changes once at the administrator level (technically in some lower layer of the model).
To make custom settings part of the main application or module, you can use the special extension of the model’s built-in editor - Model Merge Tool :

This tool allows you to select the source and target layers of the metamodel and actually merge them into one.
A little more about the internal structure and expansion of the metamodel
Above, I showed a piece of the XAFML file, where you probably noticed Application, Views, ListView and other elements in it, which are separate elements of the metamodel. Inside, these elements are implemented through Domain Components (DC) technology, which you already knew from the first article (to be absolutely precise, through its light version, which refers to the main one, much like Silverlight to full .NET):
- public interface IModelListView: IModelObjectView, IModelView, IModelNode
- {
- IModelColumns Columns {get; }
- [DataSourceProperty ("Application.Views"), DataSourceCriteria ("(AsObjectView Is Not Null) And (AsObjectView.ModelClass Is Not Null) And ('@ This.ModelClass' Is Not Null) And (AsObjectView.ModelClass.Name =' @ This.ModelClass.Name ') ")]
- IModelDetailView DetailView {get; set; }
- [DataSourceProperty ("ModelClass.ListEditorsType")]
- Type EditorType {get; set; }
- IModelSorting Sorting {get; }
- bool UseServerMode {get; set; }
- ... and other properties that characterize forms with lists of objects (List View)
- }
As you probably already understood from the main feature of DC, real classes with all the logic and fields representing model elements are assembled dynamically from interfaces and associated special logic classes. Above, I already mentioned the caching of calculated values in the model, there is also a kind of cache to speed up loading the application, since the generation of dynamic types is not a very fast operation (especially if there are hundreds and thousands of these types). In other words, the assembly of dynamic model types occurs only once when the application is first launched. Then, they are saved in a special ModelAssembly.dll assembly (by the way, the same thing is done for DC, only they get into DcAssembly.dll) next to the executable file. Further loading of types occurs directly from these assemblies as usual. By the way, do not be lazy and open these two assemblies with Reflector to see what all these interfaces have turned into :-)
A standard set of model elements is obtained from system modules that are connected to the new XAF project by default. It is worth noting that layers can have a different “schema” (as for XML), i.e. An accessible set or scheme of elements of the application model, which directly depends on the modules used to build the layer. So, for example, we have IModelListView - an application model element that will determine the List View structure in platform-independent modules. There are also platform-specific IModelListViewWeb and IModelListViewWin elements, with their own unique set of properties that characterize forms with lists of objects. In practice, this means that opening the Model Editor for our module for the Web (Habr.Module.Web / ModelDesignedDiffs.XAFML), we will see new elements and options, for example SaveListViewStateInCookies.
It is important that this scheme can be expanded by the developer at his discretion when implementing additional functionality. For example, in order to add your own option to configure a standard List View element (or any other element whose code you do not own), just put the following code in your module:
- public interface IModelListViewEx {
- string MyCoolOption {get; set; }
- }
- public override void ExtendModelInterfaces (ModelInterfaceExtenders extenders) {
- base.ExtendModelInterfaces (extenders);
- extenders.Add
(); - }
If you own the element code, then simply add this option to the interface or “mix” your interface extension explicitly:
- public interface IModelLayoutViewItem: IModelLayoutItem, IModelViewLayoutElement, IModelLayoutElementWithCaptionOptions, IModelNode, ISupportControlAlignment, IModelToolTip, IModelToolTipOptions
Of course, the developer can flexibly control other aspects of building the model, such as generating a zero layer (in fact, you can make your own generators of standard and custom model elements), customizing the behavior and display in the Model Editor, etc.
Conclusion
In conclusion, I want to say that, largely due to the presence of such a powerful multilayer and dynamic metadata layer, our framework is able to automatically build the application interface (I can’t but insert the buzzword “UI Scaffolding”) for several platforms using the same code base (in in general, these are business entities, controllers, teams, user editors, etc.). So in our case, having only the business entity ICustomer, in a few minutes I created and configured applications for Windows and Web. Also, in general, largely thanks to the capabilities of the metamodel, we are improving the lives of some Windows Forms and Web Forms developers by leveling or even hiding the well-known disadvantages of these platforms for them. Thus, using XAF, developers can get analogues of "goodies" from the XAML paradise,
Of course, there were some minuses, but they concern only us, as developers of all this good, and not our users. The fact is that few in our team will undertake to fix bugs in the core of the metamodel engine without washing their hands and not praying, even despite thousands of block and functional tests - there is too much risk of making an error or at least accidentally worsening performance. The only thing that can not but rejoice the soul is the fact that over the 6-year history of the framework, all childhood diseases have already been cured and there are almost no bugs in the kernel area :-)
Finally, learn more about the capabilities of the metamodel applications, its extension and configuration methods, as well as the XAF framework itself can be found in numerous articles from the documentation(in English) or ask here in the comments. Maybe someone else will be interested in this video from my speech at the last DevCon, but I want to warn that, due to time constraints, I had to give a very general overview of the product and its capabilities.