Monday, August 31, 2009

On the Evils of ObjectContainerDataSource with MVP Pattern

Oh my head is hurting after banging it against the wall for a while now. I've been working with the Web Client Software Factory, and started using the ObjectContainerDataSource class to try to bind some of our model to the view. All was going well, until I found the fine print from the How to page on the ObjectContainerDataSource. That's when I started becoming very angry.

Here's the fine print:

Instance. This is the data object. The ObjectContainerDataSource control uses reflection to create this object (on every update operation, delete operation, and insert operation) and populates it with the input data from the data-bound control.


Huh?

Why on this green ball on which I sit would I want a reconstituted object populated with only the data that was bound to the form?

Don't they understand that there might be more data here I want to maintain? The issue, as far as I can tell, is that the MVP pattern is expecting that the object you bind to the UI will have absolutely nothing but the specific properties that show up on a FormView. This isn't true. I might want to reuse the same object (staying DRY and enabling object reuse. Somehow, I gotta use the same object, so I have a few options:

1. When I hit the point of updating the object, ignore the instance that the ObjectContainerDataSource creates, and simply fetch the old object from the services, checking each property (perhaps via reflection) and setting the properties that have changed onto the object in question. This is obviously a bad idea for several reasons. First of all, that means I'm going to have to make another call to the database, adding more time to the update. I don't want to do that. Also, I'm going to have to write some funky reflection code to compare all the values. This is more work than it's worth.

2. Store the object in state, and retrieve it. That's a nice idea, but what if this whole MVP pattern is being implemented on a user control and I'm planning on having more than one of these controls on a specific page? That won't work either, as the instances will overwrite one another.

3. Completely and totally override the ObjectContainerDataSource and ObjectContainerDataSourceView and make a new one. One desperate programmer has already tried this and I nearly did too, but I felt the solution was a little bit bloated for my needs. Again. I'm lazy.

4. Drop the whole thing, fetch the items from the database, and manually set the value from each and every field in the FormView to the object. Nah. Not gonna do that.

So what was the solution I came up with? I'm sure most of you are hanging on the edge of your seats with bated breath.

I ignored the entire Updated event entirely.

Here's why. The Updated Event comes with the ObjectContainerDataSourceStatusEventArgsobject, while the Updating event comes with the ObjectContainerDataSourceUpdatingEventArgs object, both of which would make Steve McConnell choke because of their names. Nevertheless, they're very different objects.

The ObjectContainerDataSourceStatusEventArgs provides two properties: Instance, and AffectedRows. Instance is the reconstituted (regurgitated?) version of the bound object, while AffectedRows tells you how many fields the user changed before he/she submitted. Yeah, both of those are pretty useless.

Contrast that with the Updating event. The Updating event offers you the Keys property, and the NewValues and OldValues Dictionaries, along with the Cancel property to cancel the update altogether. Ahh, now we can determine exactly what's changed.

Now, buried deep in the framework is a type I found while hunting around called the
TypeDescriptionHelper. This class is the right to ObjectContainerDataSource's wrong.

On TypeDescriptionHelper, there's a poorly named, but very useful method called BuildInstance, which takes an IDictionary and an existing instance of the bound class. It reflects on the bound class and sets the values as indicated in the dictionary you pass in. This method is actually called after the Updating event, but before the Updated event, after ObjectContainerDataSourceView calls Activator.CreateInstance to create an instance of the class you've bound to the form. The only difference is, we're going to call it here.

So what I chose to do is narrow the dictionary down to only those values that have changed, by comparing the OldValues with the NewValues, and then pass the resulting dictionary to the BuildInstance method along with the Person, which, believe it or not, I can get by casting the sender to ObjectContainerDataSourceView, and pulling it out of the Items collection. My final method looks like this:


protected void UpdatingPerson(object sender, ObjectContainerDataSourceUpdatingEventArgs e)
{

var source = sender as ObjectContainerDataSourceView;
var person = source.Items[0] as Person;

Dictionary<string, string> changedValues = new Dictionary<string, string>();

foreach (string key in e.Keys)
{
if (!e.OldValues[key].Equals(e.NewValues[key]))
changedValues.Add(key, e.NewValues[key] as string);
}

TypeDescriptionHelper.BuildInstance(changedValues, person);

_presenter.SavePerson(person);
}


This UpdatingPerson method is the handler for the ObjectContainerDataSource's Updating event, and I don't handle the Updated event at all.

Another advantage to this method is the fact that I'm not setting anything that hasn't changed. I leave all the other properties alone. As they should be.