Monday, January 21, 2013

Modifying Business Objects using Attributes

Very often there is a need to add new members to existing Business objects that live in assemblies that we do not own.

Take the following example: We have a persistent object DashboardDefinition:

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");

        }

    }

}

 

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 –>Roles property. However we need to add a new collection of DashboardDefinition to the SecuritySystemRole, so we have 3 alternatives:

  1. Change in the source project, add the new property. then recompile then use the new assembly in your project —> Very tedious and hard to support approach.
  2. Create a custom class that derives from SecuritySystemRole, add the new property there and use the new class instead of SecuritySystemRole —> This design suffers from flexibility, at some point we will end with a lot of SecuritySystemRoles descendants.
  3. Create an ProvidedAssociationAttribute that when decorates a member will result in runtime creation of the other part.

In simple English the problem goes as following:

  1. Design a ProvidedAssociationAttribute that can hold the association name:

    [AttributeUsage(AttributeTargets.Property)]

    public class ProvidedAssociationAttribute : Attribute {

        readonly string _associationName;

     

        public ProvidedAssociationAttribute(string associationName) {

            _associationName = associationName;

        }

     

        public string AssociationName {

            get { return _associationName; }

        }

    }

     
  2. We want to write code to access and customize Types information (add new member). So, we need to override the CustomizeTypesInfo method in a module or implement IModelExtender interface in a controller (see also How to: Access Business Class Metadata).


    public
    sealed partial class ProvidedAssociationModule : ModuleBase {

        public override void CustomizeTypesInfo(ITypesInfo typesInfo) {

            base.CustomizeTypesInfo(typesInfo);

        }

     
  3. For all persistent objects with members decorated with the ProvidedAssociationAttribute:

    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. Create the dynamic collection using metadata taken from the decorated member

        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));

    }

  5. Decorate Roles of the DashboardDefinition object to dynamically create a DashboardDefinitions collection in the SecuritySystemRole object

    image

     

 

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

PS: Although the current implementation is XAF depended is rather easy to replace all XAF related code with XPO and use it to non XAF projects as well.

We are happy to read your feedback about this!. Remember that your questions are the best candidates for future posts

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

0 comments:

Post a Comment