Thursday, October 8, 2009

Adventures while building a Silverlight Enterprise application part #25

In the previous episode I discussed how an enum can be used to encapsulate char values in a database, in this case using a Gender enum as an example. As cool as that was it does pose a problem with databinding in Silverlight that I'd like to share with you. Part of that explanation leads us into the realm of reflection (that would make for a cool blog title :) ) and then into some cool trick in binding, all to end with a bit of a dissapointment. So if you already feel depressed, stop reading now :-P.

The story
First there was a BussinessObjectBase. This class handles a lot of stuff around transporting data between the service and the client and back. It also helps in keeping track of property changes and notifies about them. It's a very generic class and it serves as a base class for all our 'business' objects.
In this case let's say there is a PersonBase class, that is derived from BusinessObjectBase. PersonBase is generated based on some metadata, so we don't want to touch this source. The PersonBase class has a property called Gender that is of type string.
Then there is the Person class, which was generated but is meant to be used for custom code, so this is where we will write some code to make our Gender available as a Gender instead of a string.
We could simply write something like this in the Person class:
private char GetGender()
{
string gender = base.Gender;
if (gender.Length == 0)
{
return DefaultGender;
}
return gender[0];
}

public Gender GenderValue
{
get
{
return (Gender)GetGender();
}
set
{
base.Gender = ((char)value).ToString();
}
}

Our intention here is to hide the base implementation of the Gender property and have our own implementation that returns an actual Gender enum. Nothing special so far.
Next step is to use this new property in a databinding scenario. Here is what the binding statement in XAML would look like.
{Binding Path=Gender, Mode=TwoWay}

Now if you'd try to use a binding statement like this and run the code, what would happen is, that you would get an AmbiguousMatchException, the reason being that the binding engine can't distinguish between PersonBase.Gender and Person.Gender.
What? But we wanted to hide PersonBase.Gender, right? Absolutely, but both properties are public, so both are actually available.

Reflecting on the 'new' property
To get a better understanding of why this was happening, I decided to write some reflection code:
object person = newPerson();
object personBase = newPersonBase();

Type personType = person.GetType();
PropertyInfo personGenderProperty = personType.GetProperty("Gender");
MessageBox.Show(personGenderProperty.GetValue(person, null).ToString());

Actually trying to get the value of the Gender property through reflection threw the AmbiguousMatchException, just like it did when databinding to it. Actually calling personType.GetProperties from the Immediate Window in Visual Studio returned both properties. Then the exception all of a sudden makes sense.

What might seem a bit akward is the fact that both properties are there. If I'd try to write string gender = somePerson.Gender where somePerson is of type Person then it would not compile because I can't implicitly cast this, proving that the base property is actually hidden. Still, having the property available is needed, because the derived class needs to be able to call the property in the base class.

Trying to bind to it anyway
Still, I needed to find a way to bind to this property. I came up with some simple solutions:
  1. Make the base property protected
  2. Rename one of the properties to remove the problem all together
  3. Find a way to distinguish between the two properties in databinding
The first solution doesn't work for me, because I can't touch this code (it was generated, remember?).
The second solution would mean that I'd have to rename the property in the derived class, which doesn't seem very nice.
The third solution was a long shot, but I had to give it a try. I did actually find this syntax to use for a case like this, however it wasn't very well documented, so I did some experimentation to get a better understanding of it. I started writing this in XAML:
{Binding Path=(local:Person.Gender)}

This failed with an exception telling me that this was an invalid value for the attribute. I was surprised an puzzled, because I read about other people using it and it is actually in the documentation like this. Some further investigation learned me that this only works on dependency properties. I've build a small example of this and it actually works very well, however...

...having a dependency property with all the plumbing involved is best done by deriving your class from DependencyObject (which contains stuff like GetValue and SetValue). I obviously can't derive Person or PersonBase from DependencyObject, because I have already derived them from another class. This means I now have to rename the property in the derived class :(.

You can find the example of binding to a new property here. Hopefully it is helpful to you. It was a great learning experience overall. Just to bad it didn't lead to a better solution for me.

No comments:

Post a Comment