Web Client Software Factory - Model-View-Presenter and Page Workflow
by David Hayden ( Microsoft MVP C# ), Filed: Web Client Software Factory
The Web Client Software Factory helps you implement the View-Presenter Pattern ( as they call it ) in your web client interfaces and inject service dependencies into your presenter and other classes automagically. WCSF takes the pain out of implementing the View-Presenter Pattern by providing simple recipes that create the classes, interfaces, test fixtures, and other infrastructure items for you. As we all know, most of the battle of implementing the pattern is creating all the classes, so this is a huge productivity gain.
The documentation is excellent and not only walks you through the guidance packages, but also explains the importance of several design patterns and concepts implemented in the factory: View-Presenter, Application Controller, Service Locator, and Dependency Injection. It also talks about unit testing a presenter using Mocks, which is also good guidance.
Web Client Software Factory Visual Studio Solutions
A WCSF Visual-Studio Solution consists of Business Modules, which are essentially modules associated with web views / pages, and Foundation Modules, which are typically global services used by other modules and not associated with a web page ( view ). You can also have the recipes generate test fixtures, etc. The solution is just guidance and not written in stone. Once you have the infrastructure generated, feel free to move around classes as you see fit.
Web Client Software Factory Example
I put together a quick example using the WCSF recipes that works against a simple Customers Table in SQL Server 2005 Express.

The example consists of a simple GridView that binds to the ObjectContainerDataSource Control that comes with the factory to help with updating and deleting of customers in the Customers Table.
The View
The View, which is the web page, is coded as shown below. ObjectBuilder is automatically injecting the proper presenter into the page. Event handlers do nothing more than pass the information on to the Presenter. Half of this was automatically created by the factory.
public interface IDefaultView
{
IList<Customer> Customers { set; }
}
public partial class Customers_Default : Page, IDefaultView
{
private DefaultViewPresenter _presenter;
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this._presenter.OnViewInitialized();
}
this._presenter.OnViewLoaded();
}
[CreateNew]
public DefaultViewPresenter Presenter
{
set
{
this._presenter = value;
this._presenter.View = this;
}
}
#region IDefaultView Members
public IList<Customer> Customers
{
set { ObjectContainerDataSource1.DataSource = value; }
}
#endregion
protected void CustomersDataSource_Deleted(object sender,
ObjectContainerDataSourceStatusEventArgs e)
{
_presenter.OnCustomerDeleted((Customer)e.Instance);
}
protected void CustomersDataSource_Updated(object sender,
ObjectContainerDataSourceStatusEventArgs e)
{
_presenter.OnCustomerUpdated((Customer)e.Instance);
}
}
The Presenter
The presenter is not tied to a particular view but the IDefaultView Inteface. The actual view is injected via a View Property by the CompositeWeb Framework. No work necessary. The CustomersController (application controller) is also injected via the constructor using ObjectBuilder. Again, no work necessary to set this up.
public class DefaultViewPresenter :
Presenter<IDefaultView>
{
private CustomersController _controller;
public DefaultViewPresenter([CreateNew]
CustomersController controller)
{
this._controller = controller;
}
public override void OnViewLoaded()
{
View.Customers = _controller.GetCustomers();
}
public void OnCustomerDeleted(Customer customer)
{
_controller.DeleteCustomer(customer);
}
public void OnCustomerUpdated(Customer customer)
{
_controller.UpdateCustomer(customer);
}
}
Application Controller
The Application Controller is my responsibility, so I am to blame here for any design problems :) I like to think I followed the overall guidance used by the factory, but this is my first shot at using it. My CustomersController delegates the work of updating and deleting Customers to a global service, ICustomerDataService.
By specifying the following in the Constructor:
[ServiceDependency] ICustomerDataService customerDataService
The framework automatically injects the global service into my Application Controller. Again, no work necessary.
public class CustomersController
{
ICustomerDataService _customerDataService;
public CustomersController([ServiceDependency]
ICustomerDataService customerDataService)
{
_customerDataService = customerDataService;
}
public CustomerCollection GetCustomers()
{
return _customerDataService.GetCustomers();
}
public void UpdateCustomer(Customer customer)
{
_customerDataService.UpdateCustomer(customer);
}
public void DeleteCustomer(Customer customer)
{
_customerDataService.
DeleteCustomer(customer.CustomerId);
}
}
My Foundation Module - CustomerDataService Global Service
I totally cheated here and bastardized the concrete implementation of ICustomerDataService just to get it to work. I am hardcoding stuff, not using factories to create entities, etc. Don't do this in production :) I just wanted to get it to work during my play time :)
public class CustomerDataService : ICustomerDataService
{
static SqlDatabase database =
new SqlDatabase(@"Data Source=...");
#region ICustomerDataService Members
public CustomerCollection GetCustomers()
{
CustomerCollection customers =
new CustomerCollection();
string selectSQL = "Select CustomerId, Name,
EmailAddress FROM Customers";
using (IDataReader dr = database.ExecuteReader
(CommandType.Text, selectSQL))
{
while (dr.Read())
{
customers.Add(new Customer(dr.GetInt32(0),
dr.GetString(1), dr.GetString(2)));
}
}
return customers;
}
public void UpdateCustomer(Customer customer)
{
string updateSQL = "Update Customers SET Name =
@Name, EmailAddress = @EmailAddress
WHERE CustomerId = @CustomerId";
DbCommand updateCommand =
database.GetSqlStringCommand(updateSQL);
database.AddInParameter(updateCommand, "Name",
DbType.String, customer.Name);
database.AddInParameter(updateCommand, "EmailAddress",
DbType.String, customer.EmailAddress);
database.AddInParameter(updateCommand, "CustomerId",
DbType.Int32, customer.CustomerId);
database.ExecuteNonQuery(updateCommand);
}
public void DeleteCustomer(int customerId)
{
string deleteSQL = "DELETE FROM Customers WHERE
CustomerId = @CustomerId";
DbCommand deleteCommand =
database.GetSqlStringCommand(deleteSQL);
database.AddInParameter(deleteCommand, "CustomerId",
DbType.Int32, customerId);
database.ExecuteNonQuery(deleteCommand);
}
#endregion
}
Registering this service in the application is a piece of cake and handled by the CompositeWeb Framework:
globalServices.AddNew(typeof(CustomerDataService),
typeof(ICustomerDataService));
Conclusion
I think the Microsoft Patterns & Practices Team did a great job on the Web Client Software Factory. A lot of good learnings and great productivity gains to be had from using this software factory, and I haven't even talked about workflow which is a big part of the factory. I recommend downloading it.
Source: David Hayden ( Microsoft MVP C# )
Filed: Web Client Software Factory