Wednesday, April 1, 2009

How to use ObjectContainerDataSource with interface based entities.

If you know the Web Client Software Factory framework from the Patterns and Practices team at Microsoft, you will now that this is heavily based around Model-View-Presenter pattern.

After a while working with MVP I found myself really confident on this pattern, and specially with WCSF's implementation of this pattern. At my current employer this framework has allowed me to help converting a few "pure ASP.NET" programmers which where just relying on events, codebehind, etc. into a new kind of "dogmatic programmers" aware of the benefits of concern separation where each component (views, presenters, services, etc.) can concentrate on it's purpose, making cleaner code, much easier to maintain than in the old days.

Also, as you might know, ASP.NET 2.0 relies extensively on the Data Source Pattern to handle interaction between UI controls and data.

This can be a problematic issue to deal with when you move forward into the MVP pattern, as typical DataSource classes available tend to access/manage data records directly, without allowing you to put the presenter in the middle of such operation as MVP mandates.

WCSF fixes this problem by providing an ObjectContainerDataSource class which provides you with different events (OnInserting, OnInserted, OnSelecting, etc.) which can be hooked so they are managed from the presenter class, allowing it to call underlaying services, repositories, or any other data access mechanism you decide to implement.

The only problem I faced with this ObjectContainerDataSource control was that it's missing an "OnCreateInstance" method which I can use to handle object instance creation.
If you take into account that we use interfaces instead of concrete objects, on our presentation layer (well, we use interfaces on other layers too, but that's another issue), you will guess why this was a stopping us from using this control. Yes.. instance creation in our scenario is handled by specific factories (in fact Repositories)..

After inspecting ObjectContainerDataSource's code . The easiest solution was creating a new AdvancedDataSource class deriving from ObjectContainerDataSource adding a new OnCreateInstance event. This will allow us to control instance creation, calling our internal objects factories as needed.

Here I will try to outline the main steps I did while creating this derived class:

The first thing, if you are creating a new web controls assembly will be defining a TagPrefix attribute indicating the default Tag for controls contained at this assembly, in this sample case:


[assembly: TagPrefix("Sample.CompositeWeb", "bcw")]


Then, you will need to create a derived class from ObjectContainerDataSource, overriding it's "View" property in order to use you own DataSourceView deriving from ObjectContainerDataSource's counterpart "ObjectContainerDataSourceView":


public class AdvancedDataSource : ObjectContainerDataSource
{
private AdvancedDataSourceView _view;

public event EventHandler<AdvancedDataSourceCreateInstanceEventArgs> CreateInstance
{
add { AdvancedView.CreateInstance += value; }
remove { AdvancedView.CreateInstance -= value; }
}

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

return _view;
}
}

protected virtual AdvancedDataSourceView AdvancedView
{
get { return _view; }
}
}


The you can see I added a new EventHandler of type AdvancedDataSourceCreateInstanceEventArgs which as you might guess will be the event in charge of object instance creation. But in fact, this is just a facade which will in fact be called from AdvancedDataSourceView.

As it seems obvious, now we need to define this new AdvancedDataSourceView, which in fact is where most of the job is done:


public class AdvancedDataSourceView : ObjectContainerDataSourceView
{
private readonly static object CreateInstanceEventKey = new object();
private Type _dataObjectType = null;

/// <summary>
/// Occurs when [create instance].
/// This event will be raised when an object instance creation is required.
/// </summary>
public event EventHandler<AdvancedDataSourceCreateInstanceEventArgs> CreateInstance
{
add { base.Events.AddHandler(CreateInstanceEventKey, value); }
remove { base.Events.RemoveHandler(CreateInstanceEventKey, value); }
}

public AdvancedDataSourceView(AdvancedDataSource owner, string name)
: base(owner, name)
{
}

protected virtual object CreateObjectInstance()
{
var handler = base.Events[CreateInstanceEventKey] as EventHandler<AdvancedDataSourceCreateInstanceEventArgs>;
var e = new AdvancedDataSourceCreateInstanceEventArgs();

if (handler != null)
{
handler(this, e);
}
else
{
e.Instance = Activator.CreateInstance(GetDataObjectType());
}

if (e.Instance == null)
throw new InvalidOperationException("Instance creation failed!");

return e.Instance;
}

...
}


Basically this is just adding handling of this new CreateInstance event which we are adding.. Just one note: In order to maintain backwards compatibility we are calling Activator.CreateInstance if no event handler is registered.

Now, we need to override a few parent methods which relates to instance creation to make them call "CreateObjectInstance" when needed. This was not specially easier, as there were a few private methods which where used by ObjectContainerDataSource for validation and object instance creation. So I was forced into overriding a bit more things than I expected, but in the end, I think the outcome is not so bad ;)


#region Substituted private methods from base class
private object FindInstance(IDictionary keys)
{
return 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;
});
}

private Type GetDataObjectType()
{
if (_dataObjectType == null)
{
if (String.IsNullOrEmpty(DataObjectTypeName))
throw new InvalidOperationException("DataObjectTypeName not set!");

Type type = BuildManager.GetType(DataObjectTypeName, false, true);
if (type == null)
throw new InvalidOperationException("DataObjectType not found!");

_dataObjectType = type;
}
return _dataObjectType;
}
#endregion



And finally, we can override ExecuteInsert, ExecuteUpdate and ExecuteDelete so they do exactly the same they where doing, but calling our special CreateObjectInstance() method:


#region ObjectContainerDataSource overriden methods

// This one is copied from DataSourceView as we need to override a
// check made at base.Insert(..).. (pruiz)
public override void Insert(IDictionary values, DataSourceViewOperationCallback callback)
{
if (callback == null)
{
throw new ArgumentNullException("callback");
}
int affectedRecords = 0;
bool flag = false;
try
{
affectedRecords = this.ExecuteInsert(values);
}
catch (Exception exception)
{
flag = true;
if (!callback(affectedRecords, exception))
{
throw;
}
}
finally
{
if (!flag)
{
callback(affectedRecords, null);
}
}
}

/// <summary>
/// Performs an insert operation on the list of data that the <see cref="DataSourceView"/> object represents.
/// </summary>
/// <param name="values">An <see cref="IDictionary"/> of name/value pairs used during an insert operation.</param>
/// <returns>The number of items that were inserted into the underlying data storage.</returns>
protected override int ExecuteInsert(IDictionary values)
{
Guard.ArgumentNotNull(values, "values");

ObjectContainerDataSourceInsertingEventArgs insertingEventArgs =
new ObjectContainerDataSourceInsertingEventArgs(values);

OnInserting(insertingEventArgs);
if (insertingEventArgs.Cancel)
return 0;

object instance = CreateObjectInstance();
TypeDescriptionHelper.BuildInstance(values, instance);
Data.Add(instance);
OnDataSourceViewChanged(EventArgs.Empty);

int rowsAffected = 1;
ObjectContainerDataSourceStatusEventArgs insertedEventArgs =
new ObjectContainerDataSourceStatusEventArgs(instance, rowsAffected);
OnInserted(insertedEventArgs);

return rowsAffected;
}

/// <summary>
/// Performs a delete operation on the list of data that the
/// <see cref="DataSourceView"/> object represents.
/// </summary>
/// <param name="keys">An <see cref="IDictionary"/> of object or row keys to be deleted by
/// the <see cref="DataSourceView.ExecuteDelete(System.Collections.IDictionary,System.Collections.IDictionary)"/>
/// operation.</param>
/// <param name="oldValues">An <see cref="System.Collections.IDictionary"/> of
/// name/value pairs that represent data elements and their original values.</param>
/// <returns>The number of items that were deleted from the underlying data storage.</returns>
protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues)
{
if (keys == null)
throw new ArgumentNullException("keys");

if (keys.Count == 0)
throw new ArgumentException("keys");

ObjectContainerDataSourceDeletingEventArgs deletingEventArgs =
new ObjectContainerDataSourceDeletingEventArgs(DictionaryHelper.GetReadOnlyDictionary(keys), oldValues);
OnDeleting(deletingEventArgs);
if (deletingEventArgs.Cancel)
return 0;

int rowsAffected;
object instance = FindInstance(keys);
if (instance == null)
{
rowsAffected = 0;
}
else
{
Data.Remove(instance);
rowsAffected = 1;
}
instance = CreateObjectInstance();
TypeDescriptionHelper.BuildInstance(oldValues, instance);
TypeDescriptionHelper.BuildInstance(keys, instance);
OnDataSourceViewChanged(EventArgs.Empty);

ObjectContainerDataSourceStatusEventArgs deletedEventArgs =
new ObjectContainerDataSourceStatusEventArgs(instance, rowsAffected);
OnDeleted(deletedEventArgs);

return rowsAffected;
}

/// <summary>
/// Performs an update operation on the list of data that the
/// <see cref="DataSourceView"/> object represents.
/// </summary>
/// <param name="keys">An <see cref="System.Collections.IDictionary"/> of object or
/// row keys to be updated by the update operation.</param>
/// <param name="values">An <see cref="System.Collections.IDictionary"/> of name/value
/// pairs that represent data elements and their new values.</param>
/// <param name="oldValues">An <see cref="System.Collections.IDictionary"/> of name/value
/// pairs that represent data elements and their original values.</param>
/// <returns>The number of items that were updated in the underlying data storage.</returns>
protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues)
{
if (keys == null)
throw new ArgumentNullException("keys");

if (keys.Count == 0)
throw new ArgumentException("keys");

Guard.ArgumentNotNull(values, "values");

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

object newInstance = CreateObjectInstance();
TypeDescriptionHelper.BuildInstance(keys, newInstance);
TypeDescriptionHelper.BuildInstance(values, newInstance);
int rowsAffected;
object oldInstance = FindInstance(keys);
if (oldInstance != null)
{
int index = Data.IndexOf(oldInstance);
Data[index] = newInstance;
rowsAffected = 1;
}
else
{
rowsAffected = 0;
}
OnDataSourceViewChanged(EventArgs.Empty);

ObjectContainerDataSourceStatusEventArgs updatedEventArgs = new ObjectContainerDataSourceStatusEventArgs(newInstance, rowsAffected);
OnUpdated(updatedEventArgs);

return rowsAffected;
}
#endregion


As a final note, and just in case you are wondering why am I overriding "Insert()" method, you need to know that ObjectContainerDataSource was performing an assert whether DataObjectType was a concrete object and had a parameterless constructor.
As thus, I needed to override this method providing a new one which does all the job by itself without calling base.Insert(..).

Well, that's it. Now you have an ObjectContainerDataSource which can work with interfaces, abstract classes, etc. and which is able to relay on you own code for object instance creation purposes.

Greets.

2 comments:

  1. Thanks Pablo, that helped very much!

    ReplyDelete
  2. Hi

    I think the guys should never have used this peace of code in the first place:
    Activator.CreateInstance

    They have got ObjectBuilder, so why not get a instance from ObjectBuilder and only when it does not exist in ObjectBuilder use Activator.CreateInstance.

    Would make life a bit simpler.

    ReplyDelete