📜 ⬆️ ⬇️

Smart Forms eXpressApp Framework (XAF). Part 2 - Metamodel UI Applications


In the first part, I talked about how to “liven up” the eXpressApp Framework by adding simple business rules (data control, management of highlighting, availability and field visibility), using attributes in the business entity code. 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, tell you why this metamodel is still 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 its XAFML description language, with all known CSS and XAML (as well as QML, LSML, etc.), as well as the core ideas embedded in these technologies. . Anyone who is not afraid to get acquainted with such an unusual product of domestic bicycle construction (by the way, written before the appearance of analogues from Microsoft), please welcome under cat.



Attributes in the business entity vs. code Metamodel Settings


So, if we critically look at what happened to the original business entities from the previous article, then someone can definitely become frightened by the abundance of various attributes:
')
  1. [ DomainComponent ]
  2. public interface IAccount {
  3. [ RuleRequiredField, RuleUniqueValue ]
  4. [ RuleRegularExpression ( @ "^ [_ a-z0-9 -] + (. [_ A-z0-9 -] +) * @ [a-z0-9 -] + (. [A-z0-9 -] +) * (. [az] {2,4}) $ " ) ]
  5. string Email { get ; set ; }
  6. [ FieldSize ( 25 ) ]
  7. [ ImmediatePostData ]
  8. [ Appearance ( "MarkUnsafePasswordInRed" , "Len (Password) <6" , FontColor = "Red" ) ]
  9. string Password { get ; set ; }
  10. }
  11. [ XafDefaultProperty ( "FullName" ) ]
  12. [ DomainComponent, ImageName ( "BO_Person" ) ]
  13. public interface IPerson {
  14. string LastName { get ; set ; }
  15. string FirstName { get ; set ; }
  16. [ Calculated ( "Concat (IsNull (FirstName, ''), '', IsNull (LastName, ''))" ) ]
  17. string FullName { get ; }
  18. DateTime Birthday { get ; set ; }
  19. }
  20. [ DomainComponent ]
  21. public interface IOrganization {
  22. [ RuleRequiredField ]
  23. string Name { get ; set ; }
  24. [ Aggregated ]
  25. IList < IPerson > Staff { get ; }
  26. [ RuleRequiredField ( TargetCriteria = "Staff.Count> 0" ) ]
  27. [ DataSourceProperty ( "Staff" ) / *, DataSourceCriteria ("StartsWith (FirstName, '123')") * / ]
  28. [ Appearance ( "ChangeManagerAvailabilityAgainstStaff" , "Staff.Count = 0" , Visibility = ViewItemVisibility. Hide , Context = "DetailView" ) ]
  29. IPerson Manager { get ; set ; }
  30. }


The abundant use of attributes that define the appearance and behavior of the user interface is directly in the code of the business entity, although it helps to significantly increase the speed of development in projects such as “passed and forgotten”, in large projects it may not be so convenient. Also, do not forget about scenarios where any links 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). And finally, I’m not even talking about the possible poor readability and “bad-smell” of the code above.

It would be great if there was a separate centralized place for customizing the appearance and behavior of the user interface of the application, as well as storing 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 clear your soul before adherents of separating flies from cutlets from the business logic of the user interface, but also make your code cleaner, simpler, and it is also possible to speed up and make the development process more pleasant by using advanced visual editors .

However, I can not unequivocally say 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 project conditions (such as size, urgency, etc.). So, 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) for data access. At the same time, the library with business entities (Customer, Issue, Bug Report, Question, etc.) does not have links to the eXpressApp Framework and does not use XAF-specific attributes and entities, although it is used in several internal applications (back-end) written in XAF . In this case, all the settings of the representations of these business entities are made through the metadata of individual XAF modules.

Metamodel application or, and here the bow ...


Such a configuration store in XAF is the Application Model, which is a layer of metadata describing the type and behavior of the UI. Technically, this is not a single layer at all, but a bunch of layers, changes in which are layered one upon another (as in an onion head) according to the principle of superposition. The very first layer, the so-called “zero” protection ring is formed by the framework itself as follows: the framework analyzes our project and collects information about all connected business models, controllers and commands, editors and other entities that are contained in the current module, as well as in all addictions. Then, from the collected information about the types of applications, special metadata is actually generated that governs all aspects of the user interface, including views of the objects, navigation and command systems, localization, and much more.
For a better understanding of the place metamodel in the general architecture of the XAF application, I will give a small diagram:



Changes or differences between layers in terms of XAF are called “model differences”. Hence the name “Model.DesignedDiffs.XAFML” for the XML module files containing these differences. I note that you can change the contents of each layer in several ways:

1. special visual editor - Model Editor ;
2. directly editing XAFML files;
3. through the program code.

The superposition of all layers and will form the final model of the application:



Here, by the “final” model of the application, I mean what the developer will receive in his code when he turns to some element or his property. Also, the meta model itself is “lazy”, i.e. Superposition is calculated only when necessary, for example, by a request from the internal API, when you need to build some kind of user interface screen. It is important to note here that these calculations are not made every time they are accessed, but are correctly cached during execution.

I’m talking a lot about layers here, but I didn’t explain in detail why we invented them, because there are so many applications and technologies outside of our framework, where there are no layers, for example, XAML. Therefore, below we list the main reasons / requirements for creating a layered metamodel of an XAF application:

1. Ability to customize applications for multiple platforms;
2. Acceleration of development due to the configuration of the application 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 type and behavior of the application for end users (including dynamically, for example, based on access rights).

Examples of the practical use of the metamodel


I think enough theory, and let's work with our metamodel in practice. To do this, we will rebuild our application and double-click on the Model.DesignedDiffs.xafml file of the platform-independent module (or call the appropriate command from the context menu in Solution Explorer) of our past application:



This will open the model editor (the Model Editor itself), as shown in the figure above. In a nutshell, this editor is a fairly “Spartan” interface with a tree with all the elements of the model on the left and the property inspector on the right (there are also built-in tools for quick search, localization, 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 assess aspects of the application controlled by a metamodel. By the way, what we see here is a visual representation of the zero layer of the model generated by XAF by the types found in the current module and its dependencies.

Imagine that we suddenly needed to slightly change the look of one of our forms (ICustomer_ListView). If we remove one column, set up sorting for another, change the order of the columns and finally localize our application into Russian, then the corresponding XML (which is, in fact, the following) will be saved in the appropriate layer (Habr.Module / ModelDesignedDiffs.XAFML) Difference from previous layer:

  1. <? xml version = "1.0" encoding = "utf-8" ?>
  2. <Application Title = "HabrDemo" Logo = "ExpressAppLogo" >
  3. <Views >
  4. <ListView Id = "ICustomer_ListView" >
  5. <Columns >
  6. <ColumnInfo Id = "Name" SortOrder = "Descending" />
  7. <ColumnInfo Id = "Email" Index = "1" />
  8. <ColumnInfo Id = "Manager" Index = "2" />
  9. <ColumnInfo Id = "Password" Removed = "True" />
  10. </ Columns >
  11. </ Listview >
  12. </ Views >
  13. </ Application >

* To save space, I specifically do not provide localization here, for which a separate XAFML file will be created.

If we launch our application, we will see that our settings have been applied as expected (note the sorting and reordered columns):



Technically, this means that our code (from standard XAF modules) turned to the metamodel and set up the controls accordingly.

Especially I want to highlight the script with the application settings 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 by experience most people prefer storage in the database to the file system (most likely in the future we will make this the default behavior). Thanks to the powerful controls of DevExpress, setting up the application 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 filters, the location of controls on the form, their sizes and much more, using the means of the visual controls themselves. If you want more fine-tuning and you are an advanced user or developer, then there is a Model Editor at your service, also available during execution.
Let's now see how you can use the visual editor to quickly customize detailed forms. I think it can hardly be demonstrated better than in the video .

The idea of ​​"rubber" auto-generated forms was designed, in the main, to make life easier for developers, and it solves two main tasks:

1. More convenient and faster customization of forms at the development stage
2. More convenient deployment and application support

The first is achieved due to the fact that with the proposed approach you don’t have to spend hours on leveling hundreds of fields in any 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 want and be sure that your changes will automatically affect the application model (of course, it is possible to “fix” form settings so that changes to business entities did not affect them).

The second is possible due to the ability to outsource the setting of forms to your end users or application administrators, since this task can be completely done at runtime. Due to the multi-layered model of the application, 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 a special extension of the built-in model editor - the Model Merge Tool :



This tool allows you to select the source and target layers of the metamodel and merge them into one.

A little more about the internal structure and expansion of the metamodel


Above, I showed a piece of 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 the Domain Components (DC) technology already known to you in the first article (to be very precise, through its lightweight version, which refers to the main one, much like Silverlight to the full .NET):
  1. public interface IModelListView : IModelObjectView, IModelView, IModelNode
  2. {
  3. IModelColumns Columns { get ; }
  4. [ 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 ') " ) ]
  5. IModelDetailView DetailView { get ; set ; }
  6. [ DataSourceProperty ( "ModelClass.ListEditorsType" ) ]
  7. Type EditorType { get ; set ; }
  8. IModelSorting Sorting { get ; }
  9. bool UseServerMode { get ; set ; }
  10. ... and other properties that characterize forms with a list of objects ( List View )
  11. }


As you probably already understood from the main feature of DC, real classes with all the logic and fields representing model elements are collected dynamically from interfaces and the special logic classes associated with them. I already mentioned above about caching the calculated values ​​in the model, there is also a kind of cache here to speed up the loading of the application, since the generation of dynamic types is not a very fast operation (especially if there are hundreds or thousands of them). In other words, the assembly of dynamic types of the model occurs only once when the application is first launched. Then, they are saved to a special Assembly ModelAssembly.dll (by the way, the same is done for DC, they only get into DcAssembly.dll ) next to the executable file. Further loading of types happens straight from these assemblies as usual. By the way, do not be lazy and open these two Reflector assemblies to see what all these interfaces have become :-)

The 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 the layers may have a different "scheme" (as for XML), i.e. available set or diagram of the elements of the application model, which depends on the modules used to build the layer. So, for example, we have IModelListView - an element of the application model that will define the structure of the List View in platform-independent modules. There are also platform-specific IModelListViewWeb and IModelListViewWin elements, with their unique set of properties that characterize forms with lists of objects. Practically, this means that by opening the Model Editor for our web module (Habr.Module.Web / ModelDesignedDiffs.XAFML), we will see new elements and options, for example SaveListViewStateInCookies .

It is important that this scheme can be extended by the developer at his discretion when implementing additional functionality. For example, in order to add your own option to configure the standard List View element (or any other element whose code you do not own), you just need to put this code in your module:

  1. public interface IModelListViewEx {
  2. string MyCoolOption { get ; set ; }
  3. }
  4. public override void ExtendModelInterfaces ( ModelInterfaceExtenders extenders ) {
  5. base . ExtendModelInterfaces ( extenders ) ;
  6. extenders. Add < IModelListView, IModelListViewEx > ( ) ;
  7. }


If you own the element code, then it is enough just to add this option to the interface or “mix” your interface extension explicitly:

  1. public interface IModelLayoutViewItem : IModelLayoutItem, IModelViewLayoutElement, IModelLayoutElementWithCaptionOptions, IModelNode, ISupportControlAlignment, IModelToolTip, IModelToolTipOptions


Of course, the developer can flexibly control other aspects of model building, such as generation of the zero layer (in fact, you can make your own standard and custom model element generators), setting up 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 multi-layered and dynamic layer of metadata, our framework can automatically build the application interface (I can’t help but insert the catchword "UI Scaffolding") for several platforms using the same code base ( Generally, these are business entities, controllers, commands, user editors, etc.). So in our case, having only one business entity ICustomer, in a few minutes I created and configured applications for Windows and the Web. Also, in general, largely due to the capabilities of the metamodel, we are improving the lives of some Windows Forms and Web Forms developers, leveling or generally hiding for them the well-known drawbacks of these platforms. Thus, using XAF, developers can get analogs of "buns" from XAML-paradise, but still stay on the good old platform without investing in learning anything new and not completely changing their approach to development, at least, only so I can explain the still steady interest in XAF after so many years.

Of course, there were some drawbacks, but they concern only us, as developers of all this good, and not our users. The fact is that very few people in our team will take to fix the bugs in the core of the metamodel engine, without having washed their hands and prayed, even despite thousands of block and functional tests - there is too much risk of introducing an error or at least inadvertently degrading performance. The only thing that cannot but please the soul is the fact that for the more than 6-year history of the framework, all children's diseases have already been cured and there are almost no bugs left in the core area :-)

And finally, you can learn more about the capabilities of the metamodel of the application, methods of its expansion and configuration, as well as the XAF framework itself 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 you that, due to time constraints, I had to give a very general overview of the product and its capabilities.

Source: https://habr.com/ru/post/153755/


All Articles