Sunday, February 13, 2011

Writing custom properties: #2 Default value

In previous post I presented how to create simple property returning Guid values. In this post I will briefly describe how to deal with default values for custom properties.
  1. Writing custom properties: #1 Custom value type
  2. Writing custom properties: #2 Default value 
  3. Writing custom properties: #3 Edit control

Admin UI based approach
The easiest way is to use admin UI and set default values for properties using three available modes:


Unfortunately this approach has several drawbacks:
  • Error-prone: It’s possible that for some reason administrator will not set this value. Application logic may not be prepared for this case.
  • Default value is applied only when creating page:  If you add new property to page type, value will not be updated for empty properties.
  • Editor can remove default value so property will fall back to empty/null value
  • String representation: It may be not easy/possible to specify string representation

In my opinion this value should be used as suggestion for editor, not as way to enforce data correctness.

Ensuring default value in custom property
Second approach for defining default value is setting it inside custom property.

There is method PropertyData.SetDefaultValue(), which is called when property is created (even if is null) or when cleared.

Together with SetDefaultValue, you will usually need implement IsNull property returning value indicating if current property value is equal to it's default.

Sample implementation for GuidPropety may be following:
[PageDefinitionTypePlugIn]
public class GuidProperty : PropertyString
{ 
    // for rest of code check previous post
    //.....

    protected override void SetDefaultValue()
    {
        base.SetDefaultValue();

        this.value = Guid.Empty;
        //or set custom default value – default product code in store etc.
        //this.value = new Guid(“12345678-1234-1234-1234-123456789012”);
    }

    public override bool IsNull
    {
        get
        {
           return this.value == Guid.Empty;
           //return this.value == new Guid(“12345678-1234-1234-1234-123456789012”);
        }
    }
}


Some readers may ask why setting guid field to Guid.Empty is necessary, when Sytem.Guid is value type and it’s default value is Guid.Empty. Answer is that SetDefaultValue is called when property is cleared with PropertyData.Clear() so reseting to default value is necessary.

Usage
For newly created page or property added to exiting page

var value = CurrentPage["Guid"]; // “00000000-0000-0000-0000-000000000000”  
                               // or e.g “12345678-1234-1234-1234-123456789012”

var isNull = CurrentPage.Property["Guid"].IsNull; // returns true


Collection based custom properties
For collection types Microsoft design guidelines state:
Array (and collection) properties should never return a null reference. Null can be difficult to understand in this context.
http://msdn.microsoft.com/en-us/library/k2604h5s.aspx

To follow this guideline using SetDefaultValue is invaluable. Having custom property GuidCollectionProperty it can be implemented like below:

[PageDefinitionTypePlugIn] 
public class GuidCollectionProperty : PropertyString
{
    // for rest of code check previous post
    //.....
    protected override void SetDefaultValue()
    {
        base.SetDefaultValue();
        this.value = new List<Guid>();
    }

    public override bool IsNull
    {
        get
        {
            return this.value == null || this.value.Count == 0;
        }
    }
}

Note: Admin default value override
It’s still possible to set default value using admin UI to override default value set by property creator. Good thing is that default value format will be verified, so it is not allowed to set “abc” as default value for GuidProperty.

Note: Virtual call in constructor
Current implementation of PropertyData base class calls virtual method SetDefaultValue() in constructor. This is discouraged because calling virtual method in base class can cause accessing uninitialized values in derived class, since derived constructor would not be invoked by that time.
Do not call virtual members from constructors. Calling virtual methods during construction will cause the most derived override to be called, even though the most derived constructor has not been fully run yet
http://msdn.microsoft.com/en-us/library/ms182331(v=vs.80).aspx
http://blogs.msdn.com/b/brada/archive/2004/08/12/213951.aspx

What does it mean for us? In order to preserve backward compatibility this behavior, I guess, won’t be fixed soon. Therefore we should be aware what we use in SetDafaultValue() and check if it has been initialized in constructor before accessing value.

No comments: