Creating an EPiServer Custom Property with that nice feel to it

April 2, 2009

With the latest versions of EPiServer, creating a custom property is not especially complex anymore and it has been covered in lots of posts. There is one aspect of custom property creation that I think is lacking and that is using a real value type. With that I mean that the Value property of that PropertyCar should return a Car object and not the base type, for instance a String if you inherit from the LongString property for example. I know that I might be a bit picky with this but it gives the property just that extra nice feel. Even EPiServer with it’s latest creation LinkCollection doesn’t do this, instead Value returns the raw data string that is stored in the database.

So how do we get that nice feel? It is a bit harder than you would expect at a first glance. I’m going to walk you through how I created a multiple page type selector who’s Value property returns a PageTypeCollection. We’re going to inherit from PropertyLongString and save the GUID of the selected page types in a comma separated string. I won’t go into any detail around how to do the standard stuff like overriding CreatePropertyControl, others have gone into details around that already.

Starting with the basics we’ll override the PropertyValueType:

public override Type PropertyValueType
{
    get { return typeof(PageTypeCollection); }
}

Since our main goal was to get the Value property to return a PageTypeCollection we need to override that. To make this easy we’ll let it be a wrapper around a separate property of PageTypeCollection type that handles the conversion to and from a string.

One key thing when working with collection is that you either need to track if the items in the collection changes or have a read-only collection with read-only items in it. The former is not always possible if you are working with a collection that you don’t have control over like in our case. The CreateGuidList and ParseGuidList are helper methods that serializes/deserializes a PageTypeCollection to the GUID string.

private PageTypeCollection _pageTypes;

public PageTypeCollection PageTypes
{
    get
    {
        if (_pageTypes == null)
        {
            _pageTypes = ParseGuidList(base.LongString);
        }
        return _pageTypes.AsReadOnly();
    }
    set
    {
        base.ThrowIfReadOnly();
        if (value == null || value.Count() == 0)
        {
            base.Clear();
        }
        else
        {
            base.Modified();
            _pageTypes = new PageTypeCollection();
            _pageTypes.AddRange(value);
            base.LongString = CreateGuidList(_pageTypes);
        }
    }
}

public override object Value
{
    get { return this.PageTypes; }
    set { this.PageTypes = value as PageTypeCollection; }
}

Then we need to ensure that if the LongString value changes or the SetDefaultValue is called, the page type property will be reloaded. If you don’t do this, using this property type for a Dynamic property will easily break inheritance. I also used the same technique in CreateWritableClone to ensure that it won’t use the same PageTypeCollection.

protected override string LongString
{
    get
    {
        return base.LongString;
    }
    set
    {
        _pageTypes = null;
        base.LongString = value;
    }
}

protected override void SetDefaultValue()
{
    base.SetDefaultValue();
    _pageTypes = null;
}

public override PropertyData CreateWritableClone()
{
    PropertyPageTypeCollection property = (PropertyPageTypeCollection)base.CreateWritableClone();
    property._pageTypes = null;
    return property;
}

One important piece in the puzzle is to override the LoadData and SaveData methods. This is because the EPiServer data access layer doesn’t actually look at the value type but at the Type property so it will expect a string. We can make it easy for ourselves and just use the Value property of the base class since this is what the LongString property uses.

public override object SaveData(PropertyDataCollection properties)
{
    return base.Value;
}

public override void LoadData(object value)
{
    base.Value = value;
}

Finally we override ParseToObject and ParseToSelf and create a new static Parse method to make the property API work more as people would expect.

public override PropertyData ParseToObject(string value)
{
    return Parse(value);
}

public override void ParseToSelf(string value)
{
    this.Value = Parse(value).Value;
}

public new static PropertyPageTypeCollection Parse(string value)
{
    PropertyPageTypeCollection types = new PropertyPageTypeCollection();
    types.PageTypes = ParseGuidList(value);
    types.IsModified = false;
    return types;
}

And that’s it! I have implemented IEnumerable<PageType> to make it even easier to use but that is not neccesary at all.

My only regret is that I haven’t found a way to make the PageTypes property writable when the property is writable, but if you have a nice solution to this I’d love to hear about it!

You can find the complete source code here: PropertyPageTypeCollection, PropertyPageTypeCollectionControl

Update: Have posted a quick update with updates to this custom property here.

Advertisements

11 Responses to “Creating an EPiServer Custom Property with that nice feel to it”

  1. Anders Hattestad Says:

    Hi
    nice post. If you want to save more than one value you could take a look at this blog:
    http://labs.episerver.com/en/Blogs/Anders-Hattestad/Dates/2008/10/Properties-in-CMS-5/

    The consept is too save the different values in a xml structure

  2. hn Says:

    Thanks, I’ve seen your property and it’s a cool piece of code. I guess that my point is that just like the PropertyDateTime returns a DateTime object, the PropertyLinkCollection should have returned a LinkCollection, not a string.

    Otherwise thoughts around both of our posts (plus a few more) is that there are some real needs for development in the property area for EPiServer to start looking at. More on that is coming in another blog post I’m writing at the moment…

  3. Anders Hattestad Says:

    Hi

    What do you mean with :
    > My only regret is that I haven’t found a way to
    > make the PageTypes property writable when the
    > property is writable, but if you have a nice
    > solution to this I’d love to hear about it!

  4. hn Says:

    Well, I guess what I really meant was how to keep track of any changes in the PageTypeCollection if you make it writable so that you set the property to modified and keep the collection in sync with the LongString value. Otherwise there are risk for scenarios where they don’t match up. Am I overly cautious?

  5. Anders Hattestad Says:

    If you make PageTypes as your “master” instead of “Value” you will archieve that. Let Value update PageType and retrive the string value of PageType.
    So Value is a string repesentasion of PageTypes

    public override object Value {
    get {
    return CreateGuidList(PageTypes);

    }
    set {
    PageTypes=ParseGuidList(value);

    }
    }

  6. Anders Hattestad Says:

    And remove LongString, that will so the value as text

  7. hn Says:

    Well, I don’t want to return a string from the Value property, since that would remove one of the core reasons for creating it in the first time.

    But I guess I could inherit from PropertyData instead of PropertyLongString, that would probably end up being an even nicer property. Better get on to it, might end up creatíng that PropertyObjectBase after all… :)

  8. hn Says:

    And come to think of it, I would still not get around my initial problem with tracking changes in the collection and changing IsModified accordingly, which is needed to get the correct behaviour in saving and elsewhere.

    So the two options I can come up with it either set the IsModified to always return true or implement a full “Unit-of-work” framework that track any changes to my objects and that is something that seems a bit out of the scope of this post.
    :)

  9. Anders Hattestad Says:

    IsModified can be detected by checking if the orginal string is not equal the new string value generated by CreateGuidList. Not the fastest way but it should work.

  10. Ben Morris Says:

    Nice stuff, mate. Have been doing a lot of work on custom properties myself lately – have taken your approach and moulded it into a generic base class that can implement pretty much anything serializable as an EPiServer property: http://www.bombaycrow.com/web_development_asp_net/episerver_generic_custom_property/


  11. […] This post was mentioned on Twitter by Henrik Nystrom, Henrik Nystrom. Henrik Nystrom said: @joelabrahamsson posts is miles better than mine (http://bit.ly/c2qOgU) was. At least I was a year and a half ahead. :) […]


Comments are closed.

%d bloggers like this: