Powered by Blogger.

Thursday, July 15, 2010

ASP.NET databinding in an MVP design using the ObjectContainerDatasource

How to utilize the ObjectContainerDatasource from the Web Client Software Factory to perform databinding while maintaining an MVP architecture. Samples are provided for Entity Framework, LINQ to SQL, and ADO.NET.

Introduction

As developers, I imagine, most of us strive toward these two goals:
  1. Write better code.
  2. Write less code.
Sometimes these goals complement each other; refactoring code, for example, can make it more maintainable (=better) and reusable (=less code). At other times, they conflict with each other: applying established object oriented principles can make our code more robust and testable, but often increases its complexity and the amount of code we must write. Similarly, ASP.NET offers many facilities for automating data binding tasks, but these often don’t sit easily with more complex architectures. Fortunately, Microsoft has provided us with the Web Client Software Factory, which contains useful controls and recipes for building enterprise applications that can simplify many common tasks for us. In this article, I’m going to look at using the ObjectContainerDataSource, which comes with the WCSF, to handle databinding in the context of an application built with a Model View Presenter architecture.

Background

It's not my intention to go into a detailed explanation of the MVP pattern. There are other articles on CodeProject already that cover the pattern and its use in ASP.NET. However, it's perhaps worth mentioning that this is not the same as the recently released MVC framework for ASP.NET, although the patterns share some similarities. While it might not seem worthwhile investing the time in implementing an MVP solution manually now that this new framework is with us, doing so allows us to leverage a lot of existing web forms skills and controls we'd have to give up when moving to MVC.

The Problem

ASP.NET provides us with a wide variety of databound controls, which can greatly speed the development process. However, these are closely linked to the datasource model; while manually managing the databinding process is certainly possible, it’s not nearly as convenient. Unfortunately, none of the datasource controls provided out of the box really fit the bill for architectures such as MVP; the only one that comes close is the ObjectDataSource, but ultimately, this isn’t quite suitable either. While we could wire it up to use the pages’ presenter object, it requires extra effort and would make the view more active than we’d like; ultimately, we want the view to passively accept data from the presenter, not be active in demanding it.

The Solution

The ObjectContainerDataSource provided with the Web Client Software Factory acts a passive conduit for data, exposing a DataSource property that we can fill with data from the presenter. It raises events when data is being updated, deleted, and inserted, which we can handle to send the objects back to the presenter to be dealt with. Because it acts a standard data source control, we can use it with databound controls such as FormView, GridView etc., and take advantage of these powerful controls and the simplified APIs they provide.

Using the Code

The sample project contains a simple application to display employee details. It is not intended to be a reference application for implementing the MVP pattern, but rather to demonstrate how the OCDS simplifies databinding and as such should be seen as a somewhat naïve implementation. An ASPX page acts as the view, and contains a ListView configured to perform updates, deletes, and inserts. Three simple data repositories are included: Entity Framework, LINQ to SQL, and a manual ADO.NET implementation which can be freely switched through the web.config. To achieve this, an employee entity interface is defined that the concrete classes in the three data repositories implement:
Collapse
public interface IEmployee
{
int EmployeeID { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
decimal? Salary { get; set; }
int? DepartmentID { get; set; }
bool Active { get; set; }
DateTime? DateOfBirth {get; set; }
IDepartment Department { get; }
}
Similarly, an interface is defined for the Department the employee belongs to:
public interface IDepartment
{
int DepartmentID { get; }
string Name { get; }
}
The EF and LINQ to SQL repositories implement these through partial classes for the auto-generated designer files.
The view implements the following interface. I’ve chosen to have the view communicate with the presenter through events, but other implementations are possible.
public interface IEmployeeListView
{
IList Employees { set; }
IList Departments { set; }
event EventHandler ItemUpdated;
event EventHandler ViewLoaded;
event EventHandler ItemInserting;
event EventHandler ItemInserted;
event EventHandler ItemDeleted;
event EventHandler ItemActioning;
IEmployee SelectedEmployee { get; }
IEmployee NewEmployee { get; set; }
}
For the view implementation, a reference to Microsoft.Practices.Web.UI.WebControls from the WCSF is required, and we need to reference it in the page to use the OCDS:
<%@ Register Assembly="Microsoft.Practices.Web.UI.WebControls" 
Namespace="Microsoft.Practices.Web.UI.WebControls" 
TagPrefix="pp" %>
The controls are then dropped on the page:
<pp:ObjectContainerDataSource runat="server" 
ID="dsEmployees" DataObjectTypeName="System.Object" />
<pp:ObjectContainerDataSource runat="server" 
ID="dsDepartments" DataObjectTypeName="System.Object" />
You might be wondering what the DataObjectTypeName="System.Object" is for. Similar to how the ObjectDataSource works, when performing data manipulation operations, the OCDS will create an instance of the type specified and apply the data bound in the form to its properties. The control requires that we specify a value for the DataObjectTypeName property, and the OCDS performs a check to ensure that it can create an instance of the type specified. In our case, as we are using interfaces to handle the data and there is no way to create a concrete instance of an interface, we can't directly specify the type to use. As we’ll see in a moment though, we won’t be using the type creation facility of the DataSource at all. For the purposes of this sample, we could put any valid type into the DataObjectTypeName, it doesn’t matter.
Data is pushed into the data sources through properties on the page:
public IList Employees
{
set 
{
dsEmployees.DataSource = value;
}
}

public IList Departments
{
set
{
dsDepartments.DataSource = value;
}
}
These are populated in each page load by the presenter. To reduce database activity, I’ve implemented an extremely simple caching mechanism in the sample app, utilising the ASP.NET Cache. Databound controls use the data sources as they would any other data source:
<asp:ListView runat="server" ID="lvEmployees" DataSourceID="dsEmployees" 
DataKeyNames="EmployeeID" InsertItemPosition="LastItem">

<asp:DropDownList runat="server" ID="ddDepartments" 
SelectedValue='<%#Bind("DepartmentID") %>' 
DataValueField="DepartmentID" 
DataTextField="Name" DataSourceID="dsDepartments" 
AppendDataBoundItems="true">
The OCDS exposes a number of events we can handle. For each data manipulation action, there are –ing and –ed events. The –ed events are the ones where the data source tries to create the type specified in the DataObjectTypeName property and we’ll get an error if we let them occur. We will be handling the –ing events, then telling the data source to cancel to ensure these events are never raised. The events all work in a similar way, I’ll go through the updating event here to outline the process.
The updating event is where the magic happens:
void dsEmployees_Updating(object sender, ObjectContainerDataSourceUpdatingEventArgs e)
{
var source = sender as ObjectContainerDataSourceView;
SelectedEmployee = (IEmployee)source.Items[lvEmployees.EditIndex];
OnItemActioning(EventArgs.Empty);
Dictionary changedValues = new Dictionary();
foreach (DictionaryEntry entry in e.OldValues)
{
if(!object.Equals(e.OldValues[entry.Key], e.NewValues[entry.Key]))
changedValues.Add(entry.Key.ToString(), (e.NewValues[entry.Key]));
}
if (changedValues.Count > 0) 
Microsoft.Practices.Web.UI.WebControls.Utility.
TypeDescriptionHelper.BuildInstance(changedValues, SelectedEmployee);
OnItemUpdated(EventArgs.Empty);
e.Cancel = true;
}
By casting the source as an ObjectContainerDataSourceView, we can directly access the original data that was put into the data source and work with that instead of a partially reconstituted object created by the data source. The view’s SelectedEmployee property is set, and then an ItemActioning event is raised. At this point, Entity Framework and LINQ to SQL repositories will attach the item back to their data contexts so change tracking can begin. The ADO.NET repository will just ignore it.
The ObjectContainerDataSourceUpdatingEventArgs variable exposes three dictionaries of values: Keys are the values that were put in the DataKeys properties of the databound control, OldValues are the values the form contained before the update, and NewValues are the updated values. I’ve ignored Keys in this sample; as we’re working with the original object, it is fully populated by default. However, something needs to be specified in the DataKeys property of the control or an exception will be raised. A dictionary of values that have changed is built, then BuildInstance is used to apply the changes to the object. BuildInstance is a useful function that takes a dictionary of property names and values and applies them to an existing object. Next, the ItemUpdated event is raised to tell the presenter that we’re done with our object and it can go ahead and action it. The Cancel property is then set to true to tell the OCDS not to raise the updated event.
Inserting works in a similar fashion, except an empty IEmployee object is supplied by the presenter to be populated, and there are no old values or keys in the EventArgs object. Deleting is simply a matter of picking out the object to be deleted and telling the presenter to delete it.
Working this way greatly simplifies working with change tracked data frameworks like Entity Framework and LINQ to SQL. Rather than having to create objects with original and updated values, we can work with a single object in a much more natural way. If I’d known about this a couple of months ago, it could have saved me a lot of problems I encountered with working with the EF and the ObjectDataSource. Additionally, the view code should be fairly trivial to refactor into a base class, further increasing the code’s reusability.

Closing Thoughts

By utilizing the ObjectContainerDataSource control, we can enjoy the benefits of the ASP.NET databinding model without making too many compromises to our application architecture. However, it’s important to recognize that what we have done isn’t without implications. Although we haven't written the code that does it, we are putting a lot of logic and responsibility into the view that can't easily be tested. In a stricter implementation of MVP, we wouldn't allow the view to autonomously put itself into edit mode, for example. As such, the implementation inevitably moves away from the passive view model of MVP into the supervising controller territory. Personally, this is a decision I’m comfortable with; as with many things, I see it as a compromise between maintaining architectural purity and the real-world need to ship products out of the door, but as always, the pros should be weighed against the cons.

By Pete Sutcliffe

No comments:

Post a Comment

  ©Template by Dicas Blogger.

TOPO