Views: 3757
Number of votes: 2
Average rating:

Synchronize Relate entity’s attributes in a strongly-typed way

 

When working with Relate (EPiServer Community) sites you sometimes come across entity attributes idea – which is quite powerful. If you are using some of the built-in entities like IUser, Entry (blog post) or for instance VideoGallery you are using object that are derived from FrameworkEntityBase which implements IAttributeExtendableEntity interface. This is the interface which gives you possibility to add some user-defined attributes to the entity. Attribute could be of some predefined scalar value like bool, int, string, etc. or some complex type for instance Club or EntryComment.

This approach gives you pretty high level of flexibility to extend built-in entities with your own required attributes or link together your own entities.

Working with attributes is pretty simple:

 

var val = user.GetAttributeValue<string>("TheStringAttribute");

 

And setting the value:

 

user.SetAttributeValue("TheStringAttribute", "this is a value");

 

How ever this approach involves some drawbacks:

  1. You have to define attributes manually in administration user interface which is not so handy if you have lot of them (and that could be the case when you are building quite large community site).
  2. Access to property is stringly-typed interface which is not subject for code refactoring tools and you have to take care yourself to rename or find usages.
  3. There is no control either entity will have such a attribute defined. If requesting attribute that is not defined for the entity – exception will be thrown by EPiServer.

 

 

So I wanted to introduce attribute synchronizer that will sync defined attributes for entities and somehow provide strongly-typed access to the attributes.

Hereby I introduced EntityAttributeBuilder that takes care of some of the tasks which you have to face when working with attribute extendable entities.

So here comes some insight description of the library:

  1. Attributes are synced via EPiServe InitializableModule after context initialization process completes.
  2. Module travels application domain loaded assemblies and searches for CommunityEntity attributed classes that are describing target entity for which attributes will be synced. If one is found it’s added to the queue for synchronization.
  3. Entity’s attribute is described using CommunityEntityMetadata attribute. Each class decorated with CommunityEntity is inspected for properties decorated with CommunityEntityMetadata . If those are found, they are added to the queue for sync.

 

So entity attribute definition may look something like this:

 

[CommunityEntity(TargetType = typeof(IUser))]
public class UserMetadata
{
    [CommunityEntityMetadata]
    public virtual string OrgCode { get; set; }

    [CommunityEntityMetadata]
    public virtual Club PrimaryClub { get; set; }
}

 

Effect of this definition will be that IUser entity will receive 2 additional attributes (OrgCode” with type string and “PrimaryClub” with type Club) after initialization module will execute its sync process.

 

Then comes next part. Next step is to provide strongly-typed access for those attributes. Strongly-typed access to attributes wouldn’t be possible without lambda expressions. So we will borrow some approaches from this post to extract member access expressions.

 

To support fully strongly-typed approach I made few extension methods for IAttributeExtendableEntity interface.

 

public static R GetAttributeValue<T, R>(this IAttributeExtendableEntity entity, Expression<Func<T, R>> expression)

 

and

 

public static void SetAttributeValue<TMetadata, R>(this IAttributeExtendableEntity entity, Expression<Func<TMetadata, R>> expression, R value)

 

Using these extension methods it’s now possible to set attribute value using following code snippet:

 

user.SetAttributeValue<UserMetadata, string>(m => m.OrgCode, "12345");

 

and you can get attribute value using following snippet:

 

user.GetAttributeValue<UserMetadata, string>(m => m.OrgCode);

 

Anyway this is not perfect solution when reviewing multiple assignments in the row:

 

user.SetAttributeValue<UserMetadata, string>(m => m.OrgCode, "12345");
user.SetAttributeValue<UserMetadata, string>(m => m.AnotherAttribute, "another value");
user.SetAttributeValue<UserMetadata, string>(m => m.HiddenAttribute, "secret value");
user.SetAttributeValue<UserMetadata, Club>(m => m.PrimaryClub, null);

 

Lots of code repetitions and code tries to be non-readable.

So therefore I introduced one more useful extension method that will convert entity into attribute metadata definition class. Code snippet for this:

 

var metadata = user.AsAttributeExtendable<UserMetadata>();

 

This gives you opportunity to talk to your entity in interest via metadata definition class.

 

metadata.OrgCode = "54321";
metadata.PrimaryClub = ClubHandler.Instance.GetClubs("test club").First();

 

Behind the scene AsAttributeExtendable() method actually creates proxy and intercepts all the incoming requests for property setters or getters and calls original IAttributeExtendableEntity methods. Therefore it’s possible to talk to entity via metadata definition class and set or get attribute values.

 

Entity meta data definition looks like this:

 

public class CommunityEntityMetadata : Attribute
{
    public object[] Choices { get; set; }
    public string Name { get; set; }
    public Type Type { get; set; }
    public bool IsHidden { get; set; }
}

 

Attribute definition supports:

  1. Choices
  2. Hidden attributes

 

All of the definition values are optional. This means that library will extract all required information from the property information itself which is decorated by the CommunityEntityMetadata attribute. Library is also capable to handle partial entity attribute definitions (for example part of the attributes are defined in one assembly rest are defined in another one). But be careful with duplications – as this will cause exception of course.

 

What is does not support?

  1. Currently support for IList<T> values is not there.
  2. Library does not handle rename or removal of the attribute. This has to be carried out manually via admin UI.

 

Library could be found at EPiServer NuGet feed called “Geta.Community.EntityAttributeBuilder”.

While the package is uploading you can find the source on bitbucket.org, short description of usage found be found there.

 

Library required following assemblies from the EPiServer platform that are not delivered via some of the NuGet packages:

  1. EPiServer.Common.Cache
  2. EPiServer.Common.Framework
  3. EPiServer.Common.Framework.Impl

 

So assumption is that there will be those missing libs.

 

 

Hope this helps!

Feb 07, 2012

sveinung
(By sveinung, 2/7/2012 1:03:14 PM)

Great work Valdis!!

valdis
(By valdis, 2/10/2012 12:06:03 PM)

If you are using this lib with PageType builder which has reference to Castle.Core 2.5 NuGet sometimes does not update assembly binding redirections.
Then you should add to your "assemblyBinding" element:




Please login to comment.