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!.

3 comment(s)
James Zhong

Thanks Apostolis for your example article on dynamic relations between business objects! Could you please show how to add new properties (string, integer, datetime, enum, lookup, etc.) to existing business objects dynamically at runtime (by end users)?

24 January, 2013
Apostolis Bekiaris (DevExpress)
29 January, 2013
flxa flxa

mark

29 July, 2013

Please login or register to post comments.