eXpress App Framework Team

January 2013 - Posts

  • Oliver Sturm's Expert eXpress Persistent Objects (XPO) Class - Bad Ems, Germany + SMALL BONUS

    Join Oliver Sturm in Bad Ems, Germany for 2 days (February 28 - March 01 2013) of expert DevExpress eXpress Persistent Objects (XPO) training:

    “This class is a deep dive into DevExpress eXpress Persistent Objects, short XPO. XPO is an object/relational mapping (ORM) tool with a brilliant feature set, lending itself equally well to small and large development projects. With its broad database and platform compatibility, XPO is a valuable proposition for many scenarios that competing tools don't cover. As the basis of the eXpressApp Framework (XAF) product, a business application framework also made by DevExpress, XPO has additional credibility. The class starts with the basics, but moves on quickly to advanced scenarios. As much as the two-day timeframe allows, no question about XPO shall remain unanswered!”.

    To learn more and register, visit Oliver's website at http://www.devexpress.com/Home/Training/


    SMALL BONUS

    There will be soon more great news from Oliver, but I would like to keep it secret for now, please stay tuned!:)

    BTW, while we are talking about our frameworks, I would like point out the improvements we have made to these products in version 12.2.6 -  just released.

    Here you can see the list of fixed issues (it is quite large so I will not post it here).

    And below are the implemented suggestions:

    eXpress Persistent Objects

    • S170602 - AdvantageConnectionProvider - Support Advantage Database 11.1 and .NET Framework 4.5
    • Q457118 - ORM Designer - Provide the capability to reorder fields
    • S170604 - ORM Designer - Provide the capability to set the UseAssociationNameAsIntermediateTableName property in the designer
    • S170549 - Provide a web service project template for creating an XPO OData Service that can also include a visual data model
    • S170635 - Support System.Data.SQLite v1.0.84.0

    eXpressApp Framework

    • S170305 - BO Designer - Add a note to the ~.bo.designer.cs file that its modifications are prohibited
    • S170362 - Chart - Add Entity Framework support
    • Q458006 - Core - Add TargetObjectsCriteriaMode property to DevExpress.Persistent.Base.ActionAttribute
    • Q401329 - Deployment - Certain assemblies required for the deployment are missing in default project templates
    • S170364 - Pivot - Add Entity Framework support
    • S170227 - TreeList - Add Entity Framework support
    • Q442195 - Workflow - Ensure compatibility with WF 4.5 features

    As you can see, we are continuing to polish the core, supporting new database engines and frameworks, improving designers and of course, empowering the Entity Framework support in XAF.
    I hope you like these features. I look forward to hearing from you!

  • Dynamic member aliases from Application Model

    Updated: Feb 4 (see end of post)

    Although I promised to talk about Stephen Manderson’s Dashboard module I feel that I must discuss dynamic member aliases instead. The reason is that cross data source filtering provided by the Dashboard Suite does not permit aliases for our aggregated members. I already went over the dynamic members subject at the calculated members creation—Pros and Cons blog. However today will detail the steps involved in creating member aliases through XAF’s Application Model. You will find a sample at the bottom of this post.

    In the following image, you can see existing implementations from our community project eXpand as discussed in the calculated members creation—Pros and Cons blog.

    image_thumb[14]

    This particular post will cover only the RuntimeCalculatedMember entry.,

    Extending the model

    Following our documentation to the letter (How to: Extend the Application Model) first we need to define an interface that will extend our Application Model.

    public interface IModelRuntimeCalculatedMember : IModelMember {

        [Required]

        string AliasExpression { get; set; }

    }

     

    The context popup menu

    You probably noticed that instead instead of deriving from IModelNode interface as suggested in our docs, we used the IModelMember in the IModelRuntimeCalculatedMember. This is an already registered interface that describes the model members. So we don’t need to re-extend our Application Model with the IModelRuntimeCalculatedMember. XAF knows how to create a popup menu with the correct entry:

     

    image

     

    Changing Model Editor’s behavior

     

    AliasExpression

     

    In the IModelRuntimeCalculatedMember, we marked the AliasExpression property with the RequiredAttribute because an empty alias is not valid. The XAF Model Editor will notify you that this is a mandatory property with an asterisk icon as shown.

     

    image

    Editable Type at Runtime

    Since our IModelRuntimeCalculatedMember derives from the IModelMember and not from the IModelNode it inherits all its properties. This, however raises a conflict with XAF’s native support for design time custom members. The conflict refers to the IModelMember Type property which is by design is editable only in design time. As the next image illustrates the property is marked with a ModelReadOnlyAttribute which tells the Model Editor what to do.

    image

    In XAF, everything is overridable! So to change the Type property, we need to create a new Type property in our IModelRuntimeCalculatedMember and mark it with the new keyword. In addition we need to create and use an AlwaysEditableCalculator  instead of the DesignerOnlyCalculator:


    image

    Remember the IsCustom functionality

    As I already mentioned XAF has native support for runtime members only if the Model Editor is at design time. For this we need to add a new IModelMember and set IsCustom to true as shown:

    image

    image

    The IModelRuntimeCalculatedMember inherits from the IModelMember. This means when we create a new IModelRuntimeCalculatedMember, XAF will set IsCustom to true as it would for a simple IModelMember. To override this, we can follow the Convert Application Model Differences help document and implement the following Node Updater.

    image

    NodeUpdaters are another powerful tool provided to us by XAF. NodeUpdaters are designed to get us out of trouble. As the image above illustrates this a fairly easy task.

    1. We make our module or any class a NodeUpdater by implementing the IModelNodeUpdater<T> interface.
    2. We register the new NodeUpdater by overriding the AddModelNodeUpdaters or our module.
    3. Implement our logic inside the UpdateNode method

    The Dessert

    The AliasExpression property will hold basic criteria + complex criteria, as well as aggregated function. Right now there is no editor associated with the property. However, we can easily associate a CriteriaModelEditorControl editor as shown:

    image

    The CriteriaModelEditorControl offers great features:

    image

    The Coding part 

    Up to now we modeled a new Application Model member type the IModelRuntimeCalculatedMember. What remains is to write the algorithm to create that member in our TestBO object. Unfortunately we cannot use the standard place for extending our business objects as suggested by our knowledgebase. This is because the Application Model is not fully constructed at this point. However, it is possible to write this algorithm in any place we like (e.g: Application SetupCompleted event).  

    private static IEnumerable<IModelRuntimeCalculatedMember> GetCustomFields(IModelApplication model) {

        return model.BOModel.SelectMany(modelClass => modelClass.AllMembers).OfType<IModelRuntimeCalculatedMember>();

    }

     

    static void AddRuntimeMembers(IModelApplication model) {

        foreach (IModelRuntimeCalculatedMember modelRuntimeMember in GetCustomFields(model))

            try {

                Type classType = modelRuntimeMember.ModelClass.TypeInfo.Type;

                XPClassInfo typeInfo = _dictionary.GetClassInfo(classType);

                lock (typeInfo) {

                    if (typeInfo.FindMember(modelRuntimeMember.Name) == null) {

                        new XpandCalcMemberInfo(typeInfo, modelRuntimeMember.Name, modelRuntimeMember.Type, modelRuntimeMember.AliasExpression);

                        XafTypesInfo.Instance.RefreshInfo(classType);

                    }

                }

    Now let’s touch the unknown XpandCalcMemberInfo class:

    1. XPO allows non persistent calculated properties with the use of the PersistentAliasAttribute.
    2. To create a dynamic member we simply need to instantiate an XPCustomMemberInfo derivative like the  XpandCalcMemberInfo.

    public class XpandCalcMemberInfo : XPCustomMemberInfo {

        public XpandCalcMemberInfo(XPClassInfo owner, string propertyName, Type propertyType, string aliasExpression)

            : base(owner, propertyName, propertyType, null, true, false) {

            AddAttribute(new PersistentAliasAttribute(aliasBLOCKED EXPRESSION;

        }

     

        public override object GetValue(object theObject) {

            var xpBaseObject = ((XPBaseObject)theObject);

            return !xpBaseObject.Session.IsObjectsLoading && !xpBaseObject.Session.IsObjectsSaving

                       ? xpBaseObject.EvaluateAlias(Name)

                       : base.GetValue(theObject);

        }

     

        protected override bool CanPersist {

            get { return false; }

        }

    }

     

    Therefore, we added a PersistentAliasAttribute in the constructor using the AddAttribute method of the XPCustomMemberInfo. In addition, we had to modify the returned value of the member by overriding the GetValue method and using an approach similar to the EvaluateAlias docs,

    Best place to create the dynamic members

    Choosing the best place may varry because it depends on our requirements. However I can suggest a solution we used in eXpand for many years without problems. Because we need to take into account the user Application Model a good place to create the dynamic members is just after user login.

    public sealed partial class DynamicMemberAliasesModule : ModuleBase {

        public override void Setup(XafApplication application) {

            base.Setup(application);

            application.LoggedOn += (sender, args) => RuntimeMemberBuilder.AddFields(application.Model);

        }

    The check please!

    So we covered in detail all the steps needed for dynamic member aliases. A sample is available in DynamicMemberAliases.zip.

    To implement the rest of the dynamic members (RuntmeNonPersistent, RuntimeOrphanedCollection, RuntimeMember) follow one of the steps bellow:

    1. Use the Core or ModelDifference module of the eXpandFramework (see: How to use an eXpand module with an existing XAF application)
    2. Copy the RuntimeMemberBuilder, all interfaces from the IModelRuntimeMember.cs and extend the Application Model with the included IModelMemberEx interface.

    That’s all you need, to have a few productive and happy hours with XAF Smile. I really enjoy working with such a flexible framework and I am sure the XAF team will continue to surprise us in the future!

    In the next post, I will talk about the integration of XAF + Dashboard suite so stay tuned.

    We are happy to read your feedback about anything you read today!. Remember that your questions are the best future posts .

    Until next time, happy XAF’ing!

    Update Feb 4

    The sample DynamicMemberAliases.zip was updated to:

    1. Support the Delete action in the IModelRuntimeCalculatedMember (similar with The context popup menu paragraph)
    2. Allow true runtime aliases without the need of application restart!
    For a complete sample with DC support, see Domain Components+ Calculated properties + Application Model

    Subscribe to XAF feed
    Subscribe to community feed

  • User friendly way to add permissions

    The XAFsecurity system, just like XAF itself is flexible and easily addresses the most complex of scenarios. Depending on the nature of the application at hand, we may wish to modify the default design to make it more user friendly! And that’s exactly that this clog will cover. Check the end of this post for a sample application. 

    Let’s assume that we have a simple business object the DashboardDefinition. Our goal is to create a user Role that can Read and Navigate to predefined DashboardDefinition instances. XAF provides a special operator for this the SecurityOperation.ReadOnly.

    If we had to use code, we’d use a ModuleUpdater, along with the following snippet.

    public class Updater : ModuleUpdater {

        public Updater(IObjectSpace objectSpace, Version currentDBVersion) : base(objectSpace, currentDBVersion) { }

        public override void UpdateDatabaseAfterUpdateSchema() {

            base.UpdateDatabaseAfterUpdateSchema();

     

            var role= ObjectSpace.FindObject<SecuritySystemRole>("Name='User'");

            var criteria = CriteriaOperator.Parse("Oid=?", "TOO hard to know the key value").ToString();

            const string operations = SecurityOperations.ReadOnlyAccess;

            role.AddObjectAccessPermission<DashboardDefinition>(criteria,operations);

     

        }

    }

     

    The useful AddObjectAccessPermission Role extension method hides the internals, which:

    1. Search for a SecuritySystemTypePermissionObject for the DashboardDefinition type and create it if not found. The SecuritySystemTypePermissionObject is a persistent object with a Type property, used to relate permissions for business objects. Moreover the SecuritySystemTypePermissionObject has a set of properties (AllowRead, AllowNavigate etc.) that are used by the Security System to determine if permissions are granted for the business object.
    2. Create a new SecuritySystemObjectPermissionsObject that holds the Criteria and Operation action and relates it with the SecuritySystemTypePermissionObject from step 1.

    Although the AddObjectAccessPermission allows us to write user friendly code there are a few problems:

    1. This post covers a user-friendly way of adding permissions (without utilizing code) and a way of modifying the default XAF UI.
    2. It is difficult to construct the Criteria parameter of the AddObjectAccessPermission method (the developer should be aware of the name and value of the key property).

    Let’s now turn to the code snippet displayedto see how it is translated to a XAF UI and what steps are required from the end user to that end.

    image

    The most difficult step is the Criteria construction (just like it is if you utilize the code approach). Since this step will be used by a business user and not a developer, things will be even harder. This will make even ordinary actions, like identifying a key property, more complex. In addition the end user, will require a lot of time to create permissions for many of the objects.

    The solution is to modify the default XAF UI and allow the business user to associate a Role with a DashboardDefinition object instance as displayed bellow:

    image

    The above UI represents a many-to-many collection between Roles and DashboardDefintion. Since only the link action is available (see left arrow), we can tell that is an M-M relationship., The New DashboardDefinition action is hidden and the creation of the intermediate objects is done “magically” by XAF!

    To create the DashboardDefinition collection illustrated in the UI above, we can use the ProvidedAssociationAttribute as discussed in Modifying Business Objects using Attributes post.

    image

    In this step, by using a simple attribute, we guided XAF to create a completely different UI for associating a Role with DashboardDefintion. What remaining step is to write code that will automatically create the required permissions by extending the SecuritySystemRole class.

    Creating a custom Security Module

    Extending the SecuritySystemRole class means that we need to create a custom Role class deriving from the SecuritySystemRole. The process is well documented in How to: Implement Custom Security Objects (Users, Roles, Operation Permissions). Since, however a reusable functionality is what we desire, we recommend creating a module to host the custom Role class.

    public class XRole:SecuritySystemRole {

        public XRole(Session session) : base(session) {

        }

     

    }

     

    The next step is to create an attribute with two parameters:

    1. OperationProviderProperty: the name of the property that will provide the SecurityOperation which will be applied to the collection of DashboardDefinition of our XRole.
    2. CollectionName: the name of the dynamically created DashboardDefinition collection member in our XRole.

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]

    public class SecurityOperationsAttribute : Attribute {

        readonly string _collectionName;

        readonly string _operationProviderProperty;

     

        public SecurityOperationsAttribute(string collectionName, string operationProviderProperty) {

            _collectionName = collectionName;

            _operationProviderProperty = operationProviderProperty;

        }

     

        public string CollectionName {

            get { return _collectionName; }

        }

     

        public string OperationProviderProperty {

            get { return _operationProviderProperty; }

        }

    }

     

    Now, it’s time to use this SecurityOperationsAttribute in our DashboardDefintion class which does not live in our custom Security module.

     

    image

     

    The collectionName parameter (DashboardDefinitions) is the name of the collection created from the ProvidedAssociationAttribute discussed in the start of this post. The operationProviderProerty (DashboardOperation) does not yet exist in our XRole class and we need to create it in an abstract way since our Security modules have no knowledge of the DashboardDefinition’s existence. Writing abstract code with XAF is really a piece of cake! Our goal is to enumerate through all PersistentTypes (this includes DashboardDefintion) marked with the SecurityOperationAttribute. Then, for each Persistent type, we need to create a dynamic member in our XRole class to hold the SecurityOperation. Again, note that our module is not even aware of what the Role type is.

     

    public sealed partial class MySecurityModule : ModuleBase {

        public override void CustomizeTypesInfo(ITypesInfo typesInfo) {

            base.CustomizeTypesInfo(typesInfo);

            var roleTypeProvider = Application.Security as IRoleTypeProvider;

            if (roleTypeProvider != null) {

                foreach (var attribute in SecurityOperationsAttributes(typesInfo)) {

                    CreateMember(typesInfo, roleTypeProvider, attribute);

                }

            }

        }

     

        void CreateMember(ITypesInfo typesInfo, IRoleTypeProvider roleTypeProvider, SecurityOperationsAttribute attribute) {

            var roleTypeInfo = typesInfo.FindTypeInfo(roleTypeProvider.RoleType);

            if (roleTypeInfo.FindMember(attribute.OperationProviderProperty) == null) {

                var memberInfo = roleTypeInfo.CreateMember(attribute.OperationProviderProperty, typeof (SecurityOperationsEnum));

                memberInfo.AddAttribute(new RuleRequiredFieldAttribute());

            }

        }

     

        IEnumerable<SecurityOperationsAttribute> SecurityOperationsAttributes(ITypesInfo typesInfo) {

            var typeInfos = typesInfo.PersistentTypes.Where(info => info.FindAttribute<SecurityOperationsAttribute>() != null);

            return typeInfos.SelectMany(info => info.FindAttributes<SecurityOperationsAttribute>());

        }

     

    Using the code above will add a new property to the previous XRole UI.

     

    image

     

     

    Now, we have already an XRole instance and we created a dynamic collection of DashboardDefinition objects. So we need a method to retrieve the SecurityOperations by checking if our DashboardDefinition object is decorated with SecurityOperationsAttribute.

     

    static string GetSecurityOperation(ISecurityRole securityRole, XPMemberInfo memberInfo) {

        var typeInfo = XafTypesInfo.Instance.FindTypeInfo(memberInfo.CollectionElementType.ClassType);

        var roleTypeInfo = XafTypesInfo.Instance.FindTypeInfo(securityRole.GetType());

        var operationsAttribute = typeInfo.FindAttributes<SecurityOperationsAttribute>().FirstOrDefault(attribute => attribute.CollectionName == memberInfo.Name);

        return operationsAttribute != null ? Convert(securityRole, roleTypeInfo, operationsAttribute) : null;

    }

     

    static string Convert(ISecurityRole securityRole, ITypeInfo roleTypeInfo, SecurityOperationsAttribute operationsAttribute) {

        var value = roleTypeInfo.FindMember(operationsAttribute.OperationProviderProperty).GetValue(securityRole);

        if (value == null || ReferenceEquals(value, ""))

            return null;

        var securityOperations = (SecurityOperationsEnum)value;

        var fieldInfo = typeof(SecurityOperations).GetField(securityOperations.ToString(), BindingFlags.Public | BindingFlags.Static);

        if (fieldInfo != null)

            return fieldInfo.GetValue(null).ToString();

        throw new NotImplementedException(value.ToString());

    }

     

    Since we have a list of SecurityOperations from the GetSecurityOperation method we can use XAF’s metadata API to create the ObjectOperationPermissions.

     

    public static IEnumerable<ObjectOperationPermission> ObjectOperationPermissions(this ISecurityRole securityRole, XPMemberInfo member) {

        var collection = ((XPBaseCollection)member.GetValue(securityRole)).OfType<object>();

        var securityOperation = GetSecurityOperation(securityRole, member);

        if (!string.IsNullOrEmpty(securityOperation)) {

            foreach (var operation in securityOperation.Split(ServerPermissionRequestProcessor.Delimiters, StringSplitOptions.RemoveEmptyEntries)) {

                foreach (var obj in collection) {

                    yield return ObjectOperationPermissions(member, obj, operation);

                }

            }

        }

    }

     

    static ObjectOperationPermission ObjectOperationPermissions(XPMemberInfo member, object obj, string securityOperation) {

        return new ObjectOperationPermission(member.CollectionElementType.ClassType, Criteria(obj, member.CollectionElementType), securityOperation);

    }

     

    static string Criteria(object obj, XPClassInfo classInfo) {

        var keyProperty = classInfo.KeyProperty;

        var keyValue = keyProperty.GetValue(obj);

        return CriteriaOperator.Parse(keyProperty.Name + "=?", keyValue).ToString();

    }

     

    Finally we put all these methods to a class SecuritySystemRoleExtensionsand override our custom XRole GetPermissionsCore method as discussed in  How to: Implement Custom Security Objects (Users, Roles, Operation Permissions).

     

    public class XRole : SecuritySystemRole {

        public XRole(Session session)

            : base(session) {

        }

     

        protected override IEnumerable<IOperationPermission> GetPermissionsCore() {

            var operationPermissions = base.GetPermissionsCore();

            return OperationPermissionCollectionMembers().Aggregate(operationPermissions, (current, xpMemberInfo) => current.Union(this.ObjectOperationPermissions(xpMemberInfo).Cast<IOperationPermission>()));

        }

     

        IEnumerable<XPMemberInfo> OperationPermissionCollectionMembers() {

            return ClassInfo.OwnMembers.Where(info => info.IsAssociationList && info.CollectionElementType.HasAttribute(typeof(SecurityOperationsAttribute)));

        }

    Today, we discussed how to mix BO’s metadata with instance data using a simple attribute in order to avoid tedious and repetitive work. To summarize when we want to create user friendly ObjectAccessPermissions  we can mark our BO as shown.

     

    image

     

    Note that even if the DashboardDefintion class may live in a module, we do not have source code, It is really easy to dynamically replace attributes adjusting to your own preferences (see also How to customize a Business Model at runtime (Example)).

     

    public override void CustomizeTypesInfo(ITypesInfo typesInfo) {

        base.CustomizeTypesInfo(typesInfo);

        var typeInfo = (TypeInfo) typesInfo.FindTypeInfo(typeof (DashboardDefinition));

        var memberInfo = typeInfo.FindMember("Roles");

     

        //replace ProvidedAssociationAttribute in Roles collection

        memberInfo.RemoveAttributes<ProvidedAssociationAttribute>();

        memberInfo.AddAttribute(new ProvidedAssociationAttribute("DashboardDefinition-Roles","MyDashboardDefintions",RelationType.ManyToMany, null));

     

        //replace SecurityOperationsAttribute

        typeInfo.RemoveAttributes<SecurityOperationsAttribute>();

        typeInfo.AddAttribute(new SecurityOperationsAttribute("MyDashboardDefintions", "MyDashboardOperation"));

    }

     

    My next post will cover Stephen’s Dashboard module, In the meantime let us know your thoughts about today’s blog.

     

    The sample covering today’s discussion can be downloaded from here and is built against XAF v12.2.5.

     

    Until next time, Happy XAFing!

    Subscribe to XAF feed
    Subscribe to community feed

  • Modifying Business Objects metadata using Attributes

    The need to add new members to existing Business Objects that live in assemblies that we don’t own is common.

    Take the following example in the form of a persistent object-DashboardDefinition. Let's assume we want to create a many to many relation between DashboardDefinition and SecuritySystemRole objects. We already wrote the one part of the association in the DashboardDefinition –>(See below the Roles property.

    public class DashboardDefinition : BaseObject {

        public DashboardDefinition(Session session)

            : base(session) {

        }

        [Association("Role-Dashboards")]

        public XPCollection<DevExpress.ExpressApp.Security.Strategy.SecuritySystemRole> Roles {

            get {

                return GetCollection<DevExpress.ExpressApp.Security.Strategy.SecuritySystemRole>("Roles");

            }

        }

    }

     

    However, to create a M-M relationship, we need also a DashboardDefinition collection in the SecuritySystemRole, so there are 3 alternatives:

    1. We can modify the source code of the SecuritySystemRole to create a DashboardDefinition collection. However we assumed that we do not have access to SecuritySystemRole source code so this cannot be done.
    2. Create a custom class that derives from the SecuritySystemRole, add the new property there and use the new class instead of the SecuritySystemRole —> This design suffers from flexibility and at some point we will end up with many SecuritySystemRoles descendants.
    3. Create a ProvidedAssociationAttribute, which upon decorating the DashboardDefintion Roles collection a member will result in runtime creation of SecuritySystemRolw DashboardDefinition collection.

    Following are the steps needed to implement the third alternative.

    1. First we need to design a ProvidedAssociationAttribute that will decorate the DashboardDefinition Roles property and will hold the association name with the SecuritySystemRole DashboardDefinition collection.

      [AttributeUsage(AttributeTargets.Property)]

      public class ProvidedAssociationAttribute : Attribute {

          readonly string _associationName;

       

          public ProvidedAssociationAttribute(string associationName) {

              _associationName = associationName;

          }

       

          public string AssociationName {

              get { return _associationName; }

          }

      }

       
    2. Next, its time to actual create dynamically the SecuritySystemRole DashboardDefinition collection. For this, we need to to override the CustomizeTypesInfo method in a module (see also How to: Access Business Class Metadata).


      public
      sealed partial class ProvidedAssociationModule : ModuleBase {

          public override void CustomizeTypesInfo(ITypesInfo typesInfo) {

              base.CustomizeTypesInfo(typesInfo);

          }

       
    3. However we want an abstract technique that is not aware of our specific Business Objects (DashboardDefinition, SecuritySystemRole. Instead we can enumerate all persistent objects with members decorated with the ProvidedAssociationAttribute discussed in step one with the next code snippet.

      public override void CustomizeTypesInfo(ITypesInfo typesInfo) {

          base.CustomizeTypesInfo(typesInfo);

          var memberInfos = MemberInfos(typesInfo);

          foreach (var memberInfo in memberInfos) {

       

          }

      }

       

      IEnumerable<IMemberInfo> MemberInfos(ITypesInfo typesInfo) {

          Func<IMemberInfo, bool> hasAttribute = info => info.FindAttribute<ProvidedAssociationAttribute>() != null;

          return typesInfo.PersistentTypes.SelectMany(info => info.Members).Where(hasAttribute);

      }

       
    4. Have all decorated members from step three (e.g. the Roles property of the DashboardDefintion), we can create the dynamic collections of the other part (e.g. the SecuritySystemRole’s DashboardDefinition collection) as in the next code snippet.

          foreach (var memberInfo in memberInfos) {

              CreateMember(memberInfo);

          }

      }

       

      void CreateMember(IMemberInfo memberInfo) {

          var dictionary = XpoTypesInfoHelper.GetXpoTypeInfoSource().XPDictionary;

          var providedAssociationAttribute = memberInfo.FindAttribute<ProvidedAssociationAttribute>();

          var customMemberInfo = dictionary.GetClassInfo(memberInfo.ListElementType).CreateMember(memberInfo.Owner.Name + "s", typeof (XPCollection), true);

          var associationAttribute = new AssociationAttribute(providedAssociationAttribute.AssociationName,memberInfo.Owner.Type);

          customMemberInfo.AddAttribute(associationAttribute);

          memberInfo.AddAttribute(new AssociationAttribute(providedAssociationAttribute.AssociationName));

      }


      Finally let’s see how steps one to four can be used with our DashbboardDefintion. We can decorate the Roles collection with the
      ProvidedAssociationAttribute
      and this will dynamically create a DashboardDefinitions collection in the SecuritySystemRole object!


      image

    5.  

     

    The ProvidedAssociationAttribute idea has been in existence in our community project (www.expandframework.com) a few years now and happens to be a favorite among eXpand users. Through the years, of course, we created a real world implementation of it. I decoupled all the related files in this XAF Solution for everyone to give a quick try or to copy/paste into your solutions!

    P.S.: Although the current implementation is XAF dependent, it is easy to replace all XAF related code with XPO and use it in non XAF projects as well.

    We are happy to read your input about this!.

  • How to rule the Pivot

    Interface can be used as parameters to configure code. And since XAF is based on interfaces, it features technologies like the Application Model and Domain Components, which in turn offer what no other frameworks do - multiple inheritance, flexible design, no match in reusability, etc. The Application Model, for example, describes all parts of a business application and can be further extended using interfaces. So, our code should simply query the values of these Application Model interfaces and operate accordingly. Let’s name these interfaces Rules. The next step is to check out existing implementations and discuss a Pivot control ruling concept. Make a note here that instead of the Pivot, it be any control, our goal is to describe behaviors in XAF’s Application Model using such Rules/interfaces, then implement these behaviors in a Controller and simply copy paste this controller to any project!

    All the code needed for today’s discussion can be found in the XVideoRental demo in the Common.Win project under the NameSpaces: Common.Win.ModelAdapter, Common.Win.ColumnView.RepositoryItems, Common.Win.PivotGridControl.

    Take a look at this ruling concept/idea in action in the XAF Validation module. In the image bellow we see a set of interfaces that describe validation rules. The Validation Engine simply queries them and validates the objects accordingly.

    image

    Similar ruling based stuff can be found in our community project eXpandFrameWork, under a whole Logic Architecture concept. A few modules that use this architecture are AdditionalViewControls, ModelArtifact, MasterDetail, ConditionalDetailViews  The logic architecture is based in the composition over inheritance principle, meaning that it can make our rules amazingly powerful!. This is because we do not extend the IModelListView, but rather add the IModelListViews and other model artifacts to root model nodes like the ConditionalControllerState and LogicConditionalActionState we see in the image bellow.

     image

    In this post, however, we will concentrate on our XVideoRental demo. Take a look at this traditional Windows form taken from the legacy VideoRent application of our Windows Components Suite.

    image

    A traditional developer created a multidimensional UI given a collection of ChartPrice objects. For example, one dimension is the type of the price (DVD,Blue-Ray,Video CD), another one is the day number 1-7 and so on. As you can see, the developer to implement this user interface used 3 tabs and 9x3 controls. Three of them must accept integers only and all other double numbers. This is an acceptable design, it is however, not flexible. Here are a few reasons:

    1. The number of days is fixed to add more days (e.g. up to 10), more controls are needed.
    2. User interaction is needed from the end user if he/she wants to see prices for Blue-Ray, he must click on the assigned tab.
    3. The code that implements this works only for specific Windows form.

    THE XAF WAY

    To create the above multidimensional UI, we can use the PivotGridListEditor and create rules/interfaces that we can use to describe what the traditional form does. Since we will work with Rules/interfaces which will extend the Application Model, this methodology is not bound to other model artifacts like Business Objects or Views.

    However, at this point, let’s assign a PivotGridListEditor to the collection of ChartPrice objects to see the starting point and to have a point of reference.

    image

    The PivotGridListEditor is perfect for multidimensional analysis, making our UI more flexible than the legacy one. However, PivotGridListEditor is missing some things (not editable,row header formatting, cells formatting, object saving). Next, we will discuss how to build all that stuff with XAF and make them reusable by all of our projects!

    Extending the Application Model to host different rule types

    We must first create a collection of abstract rules and extend our Application Model’s IModelListView. To extend the Application Model, follow this help document making use of the interfaces listed below.

    public interface IModelPivotRules : IModelNode, IModelList<IModelPivotRule> {

    }

     

    [ModelAbstractClass]

    public interface IModelPivotRule : IModelNodeEnabled {

     

    }

    So after extending the model the Model Editor for IModelListView will look like the following image.

    imagetEd

    You may wonder why we chose to extend in a deeper level than IModelListView. This was done, because we wanted to group the new Rules with the PivotGridListEditor option interfaces which are discussed in Dressing up our classes – Date with a model tonight.

    Control the range of the cells

    The next step is to design an interface that can help us to apply rules to certain Pivot control cells. Thus, we require an interface with two properties - Start, End

    [ModelAbstractClass]

    public interface IModelPivotSelectionRule : IModelPivotRule {

        Point Start { get; set; }

        Point End { get; set; }

    }

    No other action is required here. XAF’s TypesInfo system is already aware of the IModelPivotSelectionRule since it derives from the already registered IModelPivotRule.

    Formating the Pivot Cells

    To format Pivot cells we need an Interface that:

    a) defines the range of the cells (eg. IModelPivotSelectionRule) .
    b) defines the PivotArea where the cells are located (Row, Column, Data).
    c) allows format configuration.

    The IModelFormatRule interface below fulfills of all the above-stated requirements.

    public interface IModelPivotArea : IModelNode {

        PivotArea PivotArea { get; set; }

    }

    public interface IModelFormatRule : IModelPivotArea, IModelFormatInfo, IModelPivotSelectionRule {

        [RuleValueComparison("IModelFormatRule_PivotArea_not_filter", DefaultContexts.Save, ValueComparisonType.NotEquals, PivotArea.FilterArea)]

        [DefaultValue(PivotArea.ColumnArea)]

        new PivotArea PivotArea { get; set; }

    }

    [ModelAbstractClass]

    public interface IModelFormatInfo : IModelNode {

        string FormatString { get; set; }

        FormatType FormatType { get; set; }

    }

    Again no other action is required here. XAF’s TypesInfo system is already aware of the IModelPivotSelectionRule since it derives from the already registered IModelPivotRule. In addition, the IModelFormatRule is not marked with  the ModelAbstractAttribute therefore the Model Editor will automatically add a menu entry as shown,

     image

    Now, we are ready to create a FormatRule for all row headers to display the info that the Legacy Windows form displays.

    image

    In the image we see that we created a FormatRule for RowArea cells that Start at X=0, Y=1 and End at the last cell X=-1, Y=-1. This rule will instruct a Controller to modify the initial ListView as shown below.

    image

    Next we will add one more FormatRule for the first row header.

    image

    What is different with this FormatRule  is the End X=0, Y=0 which points to the first row header and of course the FormatString. So, XAF will first evaluate the “All row headers format” rule and then the “First Row Header” resulting in the following UI.

    imageina

    Finally, we create a similar FormatRule for the second row header.

    image

    …which will result in the following UI.

    image

    Using a declarative runtime approach we managed to format all row headers-amazing!

    Make all Pivot cells editable and with double Datatype

    We are now ready to start modeling the legacy windows form behavior. So, we need to make the Pivot cells editable therefore we first query our Support Center to see how this can be done –> E1232. From this example we understand that we can subscribe to certain events and replace the RepositoryItem of a Pivot cell. We already have a range of Pivot cells defined from the IModelPivotSelectionRule above, and are interested only in double and integer based Repositoryitems - therefore an interface like the one below is sufficient.

    [ModelDisplayName("SpinEdit")]

    public interface IModelPivotSpinEditRule : IModelPivotSelectionRule {

        IModelRepositoryItemSpinEdit SpinEdit { get; }

    }

     

    This interface derives from the IModelPivotSelectionRule so it will provide a range of Pivot cells whose RepositoryItems will be replaced with a RepositoryItemSpinEdit (int, double). The IModelRepositoryItemSpinEdit is an interface that describes all properties of a RepositoryItemSpinEdit and can be created either by hand or automatically as discussed in Dressing up our classes – Date with a model tonight!

     

    The next step is to create the first rule that will make all Pivot cells of the data area editable. Of course we need to write code to query this rule and also save this object (like in E1232). However for the sake of simplicity and as I already stated in the beginning of this post, all code can be found in the Common.Win project of our XVideoRental demo. Look for the Common.Win.PivotGridControl.PivotGridController.

     

    imageinstrcuts

     

    In the image above: End X=-1, Y=-1 instructs our code to include all cells of the data area. Moreover since this is a RepositoryItem replacement rule, it assumes that row and column headers are excluded, so X=0, Y=0 point to the top left data cell.

     

    With this rule, the UI will be editable and will accept double numbers as shown below.

     

    image

     

    The last rule will help us format the edit value of the first row (Default # of Rental Days) to accept integers only as shown below by simply setting the IsFloatValue to false.

     

     

    image

     

    More Rule Types

    In XVideoRental we have more Rule types than the ones discussed in this post . It is rather easy to implement more and we would appreciate your ideas and contributions. So please feel free to use our Support Center to share them with the rest of our community. A list of the already implemented Pivot Rules is shown bellow. 

    image

    Controlling end user selection

    One of our objectives as developers is to provide our customers with tools that make their lives easier. For example, the PivotGridListEditor contains two components (PivotGridControl and a ChartControl hosted inside a LayoutControl). The ChartControl has as datasource the PivotGridControl. As a result when the end user selects a range of cells in the Pivot the ChartControl automatically renders that selection as shown.

    image

    When we close the view or the application, however, the selection is lost and the end user has to do again.  I really do not like to spend my time doing repetitive tasks. It is much easier an faster to model it in the Application Model once at for all! To implement this remember selected cells functionality we need an interface to extend the Application Model.

    public interface IModelPivotGridSelection : IModelNode {

        Rectangle Rectangle { get; set; }

        [DefaultValue(true)]

        bool Synchronize { get; set; }

    }

     

    When the Synchronise property is set to true, our code will persist the end user cell selection in the Rectangle property, when Synchronize is set to false, we can only use this functionality for preconfiguring a cell selection when the view opens.

     

    image

     

     

    The XVideoRental demo contains a large number of Application Model Rules, and I will probably need a large number of posts to talk about all of them. So I will concentrating in the more important ones. Feel free to explore the demo and contact us with your questions and ideas!

     

    As always happy XAFing!

  • Tooltips to the max - expressive UI

    As you know, creating a great UI requires consideration of numerous usability  factors, among them effective use of screen real-estate. Because the visible/active region of an application is limited, much of the information within a data driven application is hidden behind container controls  (tabs, popup forms etc). To access this hidden information requires end-users to navigate to the appropriate UI element.

    Using Tooltips to Expose Hidden Information

    Tooltips are a powerful UI metaphor and can help you better inform end-users without requiring navigation to different areas of an application. Because our WinForms product line supports tooltips with HTML formatting, you can quickly enable tooltip support within your XAF application as described in this help document.

    To fully leverage the flexibility inherent in XAF, I’ll discuss ways in which you can extend the use of tooltips and create highly functional solutions with ease.

    Let’s get started by taking a look at our  XVideoRental RWA.

    DataOnToolTip

    The goal of this implementation is to push any data property of a business object to a tooltip. Our first step is to create an interface and extend our Application Model columns as described in our documentation,

    [ModelAbstractClass]

    public interface IModelColumnTooltipData : IModelColumn {

        IModelTooltipData TooltipData { get; }

    }

    public interface IModelTooltipData : IModelNode {

        [Category("DataOnToolTip")]

        bool DataOnToolTip { get; set; }

        [Category("DataOnToolTip")]

        int MaxHeight { get; set; }

        [Category("DataOnToolTip")]

        int MaxWidth { get; set; }

    }

     

    public class GridViewImageTextToolTipController : ViewController<ListView>, IModelExtender {

        public void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {

            extenders.Add<IModelColumn, IModelColumnTooltipData>();

        }

    This code will extend the Application Model as shown below:

    image_thumb9

    Next, we need to implement a method that will display data within a tooltip when the DataOnTooltip attribute is set:

    image_thumb5

    It’s important to note that because of the XAF’s MVC like architecture, it is extremely easy to reuse this functionality in any project without writing a single line of code.

    ToolTipText

    The goal of this example is to provide direction to end-users within the Advanced Banded ListView so they know to double click a row to view more information associated with a specific record. The first step is to extend the Application Model with an attribute:

        public interface IModelTooltipData : IModelNode {

            [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]

            string ToolTipText { get; set; }

    //        ...

    We can now use the Model Editor to assign the text we want in the ToolTipText attribute and XAF will do the rest for us across any project.

    image_thumb8[1]

    ToolTipController

    Extending the Application Model in order to associate a class (controller) with any of its nodes can be done easily. First we add one more property to our IModelToolTipData interface:

    public interface IModelTooltipData : IModelNode {

        [DataSourceProperty("ToolTipControllers")]

        [TypeConverter(typeof(StringToTypeConverterBase))]

        Type ToolTipController { get; set; }

     

        [Browsable(false)]

        IEnumerable<Type> ToolTipControllers { get; }

    The TypeConverter attribute will convert the type to a string so it will be possible for the Model Editor to show it and the DataSourceProperty attribute will populate the Types (Controllers) we require. What's remains is to decide which classes will be in that list. This can be done by writing Domain Logic for the non browsable ToolTipControllers enumeration:

    [DomainLogic(typeof(IModelTooltipData))]

    public class IModelToolTipControllerDomainLogic  {

        public static IEnumerable<Type> Get_ToolTipControllers(IModelToolTipController modelToolTipController) {

            return FindTypeDescenants(typeof(ObjectToolTipController));

        }

        protected static IEnumerable<Type> FindTypeDescenants(Type type) {

            var typeInfo = XafTypesInfo.Instance.FindTypeInfo(type);

            return ReflectionHelper.FindTypeDescendants(typeInfo).Where(info => !info.IsAbstract).Select(info => info.Type);

        }

     

    }

     

    Now that we’ve finished the Application Model extension, let’s see how straightforward it is to write and apply such a controller for our Movie business object. For a very simple ToolTipController like:

    public class MovieToolTipController : ObjectToolTipController {

        const int MaxPhotoWidth = 120, MaxPhotoHeight = 120;

        public MovieToolTipController(Control parent) : base(parent) { }

     

        protected override void InitToolTipItem(ToolTipItem item) {

            var movie = ObjectSpace.FindObject<Movie>(CriteriaOperator.Parse("MovieTitle=?", EditObject));

            var photo = movie.Photo;

            if (photo != null)

                item.Image = photo.CreateImage(MaxPhotoWidth, MaxPhotoHeight);

            item.Text = GetMovieInfoHtml(movie);

        }

        public string GetMovieInfoHtml(Movie movie) {

            return string.Format("<b>{0}</b>\r\n<i>{2:D}</i>\r\r\n{1}", movie.Title, movie.Plot, movie.ReleaseDate);

        }

     

    }

    When we assign it to a listview column:

    image_thumb8

    XAF will automatically display an HTML formatted tooltip:

    image_thumb5[1]

    We can use a similar approach for PivotGridListEditors:

    image_thumb12

    Everything discussed in this post is included in the Common.Win project under the Common.Win.General.ToolTip namespace of our XVideoRental demo.

    Let us know if you have questions or subjects you’d like us to cover.

    Until next time, Happy XAFing to everybody!

LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2017 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners