ObjectContainerDataSource: What a useless piece of code!

Topics: Web Client Software Factory, User Forum
Aug 26, 2009 at 12:45 PM

I'm wracking my head for days now and do not arrive at another decision: The ObjectContainerDataSource provides some nice events for the MVP presenter to register at, but where the hell is the added value if you cannot add complex objects to it?!

Well, you can, but the nested / sub objects are totally ignored when it comes to an update! I still do not see the sense why after an update the control creates a new flat (argh!!) object and populates it with only the values of the bound object, but all sub objects are null. This is so scandalously useless that I do not find any words to express my rage.

Or is anybody out there who uses this control in a real-life environment? There must be some kind of intention how this control should be used, still I do not see it! Any ideas anyone? Or is there another control that provides the ability to handle complex objects as well as events that are fired after an update (and therefore fits into an MVP environment)? Or is that still needed to be written?

Aug 27, 2009 at 11:53 AM
Edited Aug 27, 2009 at 11:56 AM

Since all my hair is gone now I found a solution that at least keeps me working with that ObjectContainerDataSource. Just in case someone is interested, I outline my initial problems and their respective solutions. If someone has a better approach I am very keen to learn.

Problems

  1. Complex objects added to the DataSource return with null subobjects on update event.
  2. As a consequence of 1) you cannot edit complex objects in a form view that has an ObjectContainerDataSource as data source.

Solutions

to 1.) I checked the source code of the ObjectContainerDataSource and found out that it creates (as expected) a new Instance of the data object on each Update, leaving sub elements null. You might say I could create these subelements in the constructor of my data object but that does not work in my case since these subelements are injected by the Entity Framework and that will throw an error when another object has been created before.

So I did not find another way than writing my on class ProtectiveContainerDataSource which has the ObjectContainerDataSource as its base class. It creates a (self implemented) ProtectiveContainerDataSourceView which is derived from ObjectContainerDataSourceView and there I override the method ExecuteUpdate. Essentially, in there I do not create a new item but I find the one in the data source, update that with the new values and return it to the handler that passes it finally to the database.

to 2.) You still cannot edit (even though you can show them!) bound sub elements with my allmighty ProtectiveContainerDataSource. The reason is that bound properties are defined by a string and nobody evaluates strings like "DataObject.Subelement.Property". You probably could write a recursive reflector orgy that assigns the values to the right properties, but I decided to use nested controls instead (FormView in this case).

Each FormView has its own ProtectiveContainerDataSource where I bind the subelements. But only the FormView of the topmost data object shows the "Update" control button. I handle the Updating event of the topmost form view where I call the UpdateItem() method of all nested formviews. As a result, all nested objects get the values of the input form. Finally in the Updated handler of the topmost form view I send the complete complex object down to the controller who makes a big linq party in the Entity Framework I guess... well, that's it.

Here is the code of my ProtectiveContainerDataSource:

using System;

using Microsoft.Practices.Web.UI.WebControls;

namespace ProtectiveContainerDataSource
{
    public class ProtectiveContainerDataSource : ObjectContainerDataSource
    {
        private ProtectiveContainerDataSourceView _view;

        protected override ObjectContainerDataSourceView View
        {
            get
            {
                if (_view == null)
                    _view = new ProtectiveContainerDataSourceView(this, "DefaultView");

                return _view;
            }
        }

        protected virtual ProtectiveContainerDataSourceView ProtectiveView
        {
            get { return _view; }
        }
    }
}




using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;

using System.ComponentModel;
using System.Web.Compilation;
using System.Web.UI;

using Microsoft.Practices.Web.UI.WebControls;
using Microsoft.Practices.Web.UI.WebControls.Utility;

namespace ProtectiveContainerDataSource
{
    public class ProtectiveContainerDataSourceView : ObjectContainerDataSourceView
    {
        public ProtectiveContainerDataSourceView(ProtectiveContainerDataSource owner, string name)
            : base(owner, name)
        {
        }
        protected override int ExecuteUpdate(System.Collections.IDictionary keys, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)
        {
            Guard.CollectionNotNullNorEmpty(keys, "Keys are null or empty! Define a key for this type!", "keys");
            Guard.ArgumentNotNull(values, "values");

            ObjectContainerDataSourceUpdatingEventArgs updatingEventArgs =
                new ObjectContainerDataSourceUpdatingEventArgs(DictionaryHelper.GetReadOnlyDictionary(keys), values, oldValues);
            OnUpdating(updatingEventArgs);
            if (updatingEventArgs.Cancel)
                return 0;

            /*
			object newInstance = CreateInstance();  <-- ARGH! That is so...
            */

            int rowsAffected;

            object oldInstance = FindInstance(keys);

            if (oldInstance != null)
            {
                TypeDescriptionHelper.BuildInstance(values, oldInstance); // new: update the OLD object with new values
                //int index = Data.IndexOf(oldInstance);   
                //Data[index] = newInstance;        <-- no need to put it back into the data source, it's already there!
                rowsAffected = 1;
            }
            else
            {
                rowsAffected = 0;
            }

            OnDataSourceViewChanged(EventArgs.Empty);

            // send the old instance back to the handler
            ObjectContainerDataSourceStatusEventArgs updatedEventArgs = new ObjectContainerDataSourceStatusEventArgs(oldInstance, rowsAffected);
            OnUpdated(updatedEventArgs);

            return rowsAffected;
        }

        private object FindInstance(IDictionary keys)
        {
            return base.Data.Find(delegate(object obj)
            {
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(obj);
                foreach (string keyName in keys.Keys)
                {
                    PropertyDescriptor property = TypeDescriptionHelper.GetValidProperty(properties, keyName);
                    if (!property.GetValue(obj).Equals(keys[keyName]))
                        return false;
                }
                return true;
            });
        }

    }
}

Aug 28, 2009 at 8:41 PM
Edited Aug 28, 2009 at 9:18 PM

.