In my opinion, XAF's Application Model (defined as a set of application settings or UI skeleton) is the second most important XAF feature (first being automatic database and CRUD forms generation based on your ORM data model).
Because of its importance, we consistently strive to improve the Application Model and in our upcoming release, we focused our energies on improving startup application performance and general usability for XAF developers.
Application Model Caching
With v16.1, you can speed up WinForms application startup using the EnableModelCache property. When set to true, Application Model content is cached to a file when the application is first launched and recovered on subsequent runs. This property works in a production environment when the debugger is not attached (see Debugger.IsAttached). By default, EnableModelCache is set to false. To change this property value, add the following code to the Program.cs (Program.vb) file, before the Setup method is called: winApplication.EnableModelCache = true;
As a result of these settings, Nodes Generators and Generator Updaters will be executed and the Model.Cache.xafml cache file will be created only when the application is started for the first time. Note that initial startup can take more time than normal, however, the time taken by subsequent startups will be reduced, because the Application Model content will be recovered from the cache. The cache is recreated when application module version is incremented.
By default, the Model.Cache.xafml file is located in the application folder. You can change its path using one of the following methods:
- override the GetModelCacheFileLocationPath method in XafApplication descendant;
- use the ModelCacheLocation key in the configuration file (App.config).
The Model.Cache.xafml file name is specified by the ModelStoreBase.ModelCacheDefaultName constant. If your application is localized, separate cache files are created for each language (e.g. Model.Cache_de.xafml).
Frequently Asked Questions
Q: How is this different from the existing ModelAssembly.dll-based cache?
A: Unlike the existing ModelAssembly file-based cache (which is technically an assembly providing Application Model object implementation based on the IModelXXX interfaces), the new model cache file contains the entire model structure in XAFML format.
Q: Is this cached file an equivalent of merging of the platform agnostic model and WinForms/ASP.NET models? Does this include user modifications?
A: No, this Model.Cache.xafml only contains an unchangeable Application Model layer. Administrative (Model.XAFML) and user-specific (e.g. Model.User.XAFML) model differences are not cached (they are meant to be modified once the app is deployed).
Q: Is generation of this cache file somehow connected with database schema changes?
A: No.
Q: Can this cached file be supplied with the installer?
A: Absolutely. You can generate this file by running your app without the debugger attached and then copy it into your installation program. This manual process may be unnecessary in many cases because the cache file will still be automatically generated after the released application is executed for the first time.
Q: What about this feature for ASP.NET?
A: By default, the EnableModelCache property has no effect on an ASP.NET application since a shared application model is usually generated once for all web clients. If you wish, you can manually activate the creation of this cache file by overriding the GetModelCacheFileLocationPath method of your WebApplication descendant.
Application Model Database Storage Enhancements
As you know, XAF apps can store user-defined application customizations (layouts, selected skin, etc.) in an application database with the use of the ModelDifferenceDbStore class introduced several releases ago. In this release, we've enhanced functionality in the following ways:
- Design-time customizations are now always loaded from the Model.xafml file stored in the file system to simplify debugging.
In other words, for administrative model differences we have returned to the schema we used prior to introduction of ModelDifferenceDbStore. Only user-specific model differences are loaded from the database by default. When required, you can uncomment the XafApplication.CreateCustomModelDifferenceStore event subscription manually. Note that Model.xafml file content will be loaded to the database once the application starts. Further changes to this file will be ignored if the database record already exists for shared model differences. To reload settings from Model.xafml, enable the administrative UI and use the Import Shared Model Difference Action (or delete the Shared Model Difference record and restart).
- ModelDifferenceDbStore can now be used when the Security System is disabled.
Currently, if the Security System is enabled, the SecuritySystem.CurrentUserId value is used as the identifier. The System.Security.Principal.WindowsIdentity.GetCurrent().Name value is used as a user identifier (passed to the IModelDifference.UserId property) when the Security System is disabled. So, you can enable ModelDifferenceDbStore for WinForms applications with the disabled Security System using the approach described here. However, we do not recommended that you enable ModelDifferenceDbStore for unsecured ASP.NET applications because the UserID will be the same for all users. Shared model differences are supported for both WinForms and ASP.NET when the Security System is disabled.
- XML settings are now validated before being persisted.
This is a small usability improvement to simplify error debugging when using ModelDifferenceDbStore and when the administrative UI for editing model differences is enabled in the application as described here. Application administrators will not be able to save invalid XML, e.g. with unclosed tags.
Non-Persistent Objects Enhancements
We continue incorporating non-persistent objects support implemented in our most recent releases with the help of the NonPersistentObjectSpace and NonPersistentObjectSpaceProvider classes.
With this release, you can display persistent objects in a non-persistent object view with much less code. This is possible with the help of the AdditionalObjectSpaces property in the NonPersistentObjectSpace class. It is best to illustrate this improvement with a short code snippet:
...
[DomainComponent, DefaultClassOptions]
public class NonPersistentObject {
// ...
public string Name { get; set; }
public Person Owner { get; set; }
...
public class AdditionalPersonObjectSpaceController : WindowController {
private IObjectSpace additionalObjectSpace;
protected override void OnActivated() {
base.OnActivated();
Application.ObjectSpaceCreated += Application_ObjectSpaceCreated;
additionalObjectSpace = Application.CreateObjectSpace(typeof(Person));
}
private void Application_ObjectSpaceCreated(Object sender, ObjectSpaceCreatedEventArgs e) {
if (e.ObjectSpace is NonPersistentObjectSpace) {
((NonPersistentObjectSpace)e.ObjectSpace).AdditionalObjectSpaces.Add(additionalObjectSpace);//!!!
}
}
...
Once XAF v16.1 is released, refer to the "How to: Show Persistent Objects in a Non-Persistent Object's View" article in the online documentation for a full tutorial and additional explanations. Also notice the new ORM-agnostic DevExpress.ExpressApp.Data.Key attribute we introduced to mark unique key properties of an object type.
Another improvement involves showing a non-persistent Detail View: Separately (e.g., from a navigation control or in a popup window) or Embedded (inside a DashboardView item). With XAF v16.1, you can handle the new ObjectByKeyGetting event of the NonPersistentObjectSpace class to provide a non-persistent object instance manually. As an example, consider a scenario when a navigation item pointing to a non-persistent class DetailView was added under the NavigationItems node and its ObjectKey parameter was specified in the Model Editor as shown below:
You can now add the following Controller to handle this scenario:
...
public class NonPersistentObjectsController : WindowController {
protected override void OnActivated() {
base.OnActivated();
Application.ObjectSpaceCreated += Application_ObjectSpaceCreated;
}
private void Application_ObjectSpaceCreated(object sender, ObjectSpaceCreatedEventArgs e) {
NonPersistentObjectSpace nonPersistentObjectSpace = e.ObjectSpace as NonPersistentObjectSpace;
if(nonPersistentObjectSpace != null) {
nonPersistentObjectSpace.ObjectByKeyGetting += nonPersistentObjectSpace_ObjectByKeyGetting;
}
}
private void nonPersistentObjectSpace_ObjectByKeyGetting(object sender, ObjectByKeyGettingEventArgs e) {
if(e.ObjectType.IsAssignableFrom(typeof(NonPersistentObject))) {
if(((int)e.Key) == 138) {
NonPersistentObject obj138 = new NonPersistentObject();
obj138.Name = "Sample Object";
e.Object = obj138;
}
}
}
...
If you want to create a non-persistent object automatically for each Detail View, you do not need to specify an ObjectKey in the Model Editor. Leave the ObjectKey value empty and create the following View Controller, which will create a non-persistent object
...
public class NonPersistentObjectActivatorController : ViewController<DetailView> {
protected override void OnActivated() {
base.OnActivated();
if(ObjectSpace is NonPersistentObjectSpace) {
View.CurrentObject = View.ObjectTypeInfo.CreateInstance();
}
}
}
...
On a final note, let me add that custom calculated fields are now supported as well. In other words, you can define custom fields for non-persistent types in the Model Editor with the Expression attribute like NonPersistentProperty1 + NonPersistentProperty2.
Several types have changed their host assemblies without changes in namespaces
This is just a formal notification as this change should not be "breaking" or affect you at all in the majority of cases. Refer to the Several types have been moved to the DevExpress.Persistent.Base and DevExpress.Persistent.BaseImpl assemblies from the Charts, ConditionalAppearance, KPI and StateMachine modules in XAF v16.1 KB Article for more details.