Search This Blog

Monday, August 11, 2014

Personalizing and securing the runtime Model Editor for your end-users


There is the word "tricks" in the name of my blog, and today I really want to tell you about something, which is in my opinion, deserves to be called a real trick. At least I am sure that I am going to highlight a solution, which not everyone is aware of and using in his production app. But, don't worry, because this solution is based on the well-tested core functionality of the framework and approach described in the XAF documentation: How to: Access Business Object Metadata.

Scenario

In the majority of customer projects I have seen, end-users are not using the runtime Model Editor, most likely because this developer tool can be confusing, and it is quite easy to "shoot yourself the foot" with it. This blog post is devoted to showing a way of customizing the Model Editor to limit end-users to see only certain nodes. For instance, one may want regular users to see only the Options and Validation nodes, while allowing full customization to administrators. Also, many may want to see a custom text within the Model Editor form header instead of the default XAF text.





1. Customizing the Model Editor's form appearance

Let's start from the last business requirement, which is also the most simple to implement:

//MainDemo.Win/WinApplication.cs
namespace MainDemo.Win {
public partial class MainDemoWinApplication : WinApplication {
        protected override System.Windows.Forms.Form CreateModelEditorForm() {
            System.Windows.Forms.Form result = base.CreateModelEditorForm();
            DevExpress.ExpressApp.SystemModule.AboutInfo.Instance.Init(this);
            result.Text = DevExpress.ExpressApp.SystemModule.AboutInfo.Instance.Copyright;
            return result;
        }

Seeing the "eXpressApp Framework Model Editor" text does not make a lot of sense if you allow your end-users to use that tool. So, the code above will change the title of the runtime Model Editor's form to force a user to think that you created this powerful beast yourself:-)
Of course, using the same approach, you can customize other form properties.

2. Hiding or filtering out certain model elements from the tree

2.1. Static approach

The idea is to access a required model element type and then find its member to decorate it with the standard System.ComponentModel.BrowsableAttribute, which is understandable by XAF. For instance, I want to hide the Application | BOModel node. Technically, this node is represented by the BOModel property of the IModelApplication interface. That said, the code that hides it will look as follows:

//MainDemo.Module/Module.cs
namespace MainDemo.Module {
    public sealed partial class MainDemoModule : ModuleBase {
        public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
            base.CustomizeTypesInfo(typesInfo);
            //Static approach:
            ITypeInfo tiModelApplication = typesInfo.FindTypeInfo(typeof(IModelApplication));
            tiModelApplication.FindMember("BOModel").AddAttribute(new System.ComponentModel.BrowsableAttribute(false));

While we do things at runtime, I still call this method "static" because we can define the hiding condition only once - at the moment types metadata is customized within the overridden CustomizeTypesInfo method of our module.

2.2. Dynamic approach.

Unlike the previous method, this checks the hiding condition later on demand, only when it is needed, e.g. when the Model Editor builds its nodes tree. This is more flexible, because I have more information about the current context and environment, since the app is fully initialized: the user is logged in, the database is ready to be accessed, etc. For instance, I want to hide the Application | NavigationItems node for all application users except for Mary. Since I am pretty sure:-) that this node is technically just a property of the IModelApplicationNavigationItems interface, which is at the same time an IModelApplication extension, the code will look like this:

//MainDemo.Module/Module.cs
namespace MainDemo.Module {
    public sealed partial class MainDemoModule : ModuleBase {
        public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
            base.CustomizeTypesInfo(typesInfo);
            //Dynamic approach:
            ITypeInfo tiNavigationItems = typesInfo.FindTypeInfo(typeof(IModelApplicationNavigationItems));
            tiNavigationItems.FindMember("NavigationItems").AddAttribute(new DevExpress.ExpressApp.Model.ModelBrowsableAttribute(typeof(MyModelElementVisibilityCalculator)));
        }
        internal class MyModelElementVisibilityCalculator : IModelIsVisible {
            bool IModelIsVisible.IsVisible(IModelNode node, string propertyName) {
                return SecuritySystem.CurrentUserName == "Mary";
            }
        }

Take special note of the ModelBrowsableAttribute, which takes a type that implements the IModelIsVisible interface - the core of the dynamic nature of this method. The IModelIsVisible implementation is straightforward, since it requires providing only a single method-predicate. Here just for demo purposes, I check the SecuritySystem.CurrentUserName property, but nothing prevents you from doing any complex checks here, e.g., checking custom security permissions (learn more...). To me, this concept resembles filtering secured data in XAF ListViews, a sort of universal nodes filter or query interceptor in OData...


3. Security and configuration considerations


3.1. This approach is good when you store model differences in the database and your end-users have no access to the XAFML files to modify them directly in their favorite text editor. I mean that by adding these attributes in metadata, we provide protection at the Model Editor UI level, because it looks at the model elements metadata before displaying the corresponding nodes and their properties.

3.2. Take special note that the use of the SecuritySystem object with the dynamic approach is limited to runtime and fully initialized application only. That means that using this method and the standalone Model Editor tool you will not be able to use stuff that depends on things that occur during the normal XafApplication life cycle and require end-user interaction such as authorization. This is because the standalone tool is a separate app, which does not launch the application normally, but rather just instantiates it to read types metadata and build the Application Model.

3.3. It is usually not a good practice to access the XAF application database using pure XAF means at the moment types info is customized. That is fairly logical, because you customize or change the structure of your data model, which should eventually be mapped to the database tables. So, it cannot logically be done until you are finished with your schema customizations. However, I promise not to tell anyone if you access the database using standard ADO.NET or XPO techniques to read your configuration settings. Or, even store configuration settings in a file system (e.g., a configuration file) or get this info from a service or whatever place you wish.

3.4. You cannot use the static approach #2.1 to hide nodes corresponding to certain object types (e.g., Person) under the BOModel. This is because this approach is applicable only to the underlying node types (e.g., IModelClass). Currently, there is no ready XAF functionality that would allow you to hide individual classes under the BOModel node, I am afraid. At maximum you can only hide certain sub-nodes of IModelClass (e.g., OwnMembers, Links, AppearanceRules, etc.) using the dynamic approach #2.2.

No comments:

Post a Comment