One of the requirements for the MVP pattern is to keep everything out of the UI (View) that does not have to be in the View. Data mapping is one of those things that we would like to move out of the UI. I tend to think of it along these lines, “If I wanted to expose this same functionality in a different UI what do I not want to have to rewrite?”
If the View defines an interface exposing read write properties for all of the data needed, which it should, then the interface could be simply passed to the Presenter and the presenter handling the data mapping. The View does not have to get involved.
The Model should also define an interface exposing read write properties for all of the data that it has. The presenter deals with the Model and the View exclusively through these interfaces. This results in code similar to this:
private void MapData()
{
View.Field1 = Model.Field1;
View.Field2 = Model.Field2;
View.Field3 = Model.Field3;
View.Field4 = Model.Field4;
...
View.FieldN = Model.FieldN;
}
This is very straightforard. All of the nuances of how to display the mapped data is handled by the View as it should be. All of the details for how to get the data is handled by the model as it should be and the Presenter is left tieing the two together.
But this looks like tedious code. Tedious code is error prone code and code that we would like to avoid. If both interfaces,the one exposed for the View and the one exposed for the Model, require the same set of properties, then surely we can use reflection to handle writing this tedious code for us.
Consider this code:
public static void CopyTo(object sourceObject, object targetObject)
{
object value = new object();
object[] param = new object[0];
foreach (PropertyInfo propertyInfo in sourceObject.GetType().GetProperties())
{
MethodInfo getMethod = propertyInfo.GetGetMethod();
// Ensure that there is a get method for the source object.
System.Reflection.PropertyInfo targetPropertyInfo =
targetObject.GetType().GetProperty(propertyInfo.Name);
if ((getMethod != null) && (targetPropertyInfo != null))
{
// Ensure that there is a set method in the target object.
if (targetPropertyInfo.CanWrite )
{
value = getMethod.Invoke(sourceObject, param);
targetPropertyInfo.SetValue(targetObject,
ConvertValueType(value, propertyInfo.PropertyType),
null);
}
}
}
}
This function expects two objects to be passed in. We loop through the properties of the first object looking for matching properties in the second object. If the second object has a matching property and the second object’s property is not read only, we will get the value from the first objest’s property and set it to the second object’s property. The only thing that is required is that the any properties tha the two objects have in common must be of the same type.
Armed with such as CopyTo method, our MapData method from earlier can be reduced to:
public void MapData()
{
CopyTo(Model, View);
}
Regardless of how many properties are involved this one call will handle the data mapping. All properties that are in common will be copied. Properties in the Model that are not in the View will be ignored. Properties that are in the View that are not in the Model will be uninitialized.
Additionally, a generic update method might look similar to this:
public void Update()
{
Model = Model.GetCurrent();
CopyTo(View, Model);
Model.Save();
}
Here we make sure that we have the current model data. We then overwrite any common properties with the values currently in the View. Finally, we save the Model. This works regardless of the number of properties involved. Properties in the Model that are not in the View will not be affected. Properties in the View that are not in the Model will not be persisted.
The presenter makes no assumptions about how the Model gets its current data or what happens when the Save is called. The presenter makes no assumptions about how the View displays its data. The seperation between the UI, business logic, and data storage is maintained. Common, redundnat, potentially error prone data mapping logic is moved out of all three components into a reusable library component called by the presenter.