Archives
-
My first real-world experience with Web Client Software Factory (WCSF)
Introduction
WCSF has existed since January 2007. Although I looked at it before, this is the first time I used it in a real project. Since ASP.NET MVC has been available, a frequent question for a development team is the choice between WCSF or MVC. I would not comment on how our team arrived the decision but will comment on the comparison at the end of this post. In this post, I will attempt to document what I learnt, both for myself and for anyone who encounter the similar situation.
What is WCSF
Web Client Software Factory (WCSF) is an application framework that works with Microsoft ASP.NET webform. In a typical webform application, there is a significant amount of logic in the code behind file that takes input from request, server controls and call business components. The the code would set the state of the server components or redirect to another URL based on the return of business components. The code is also responsible for reading and setting the states such as session, application and viewstate. The most important objective of WCSF is to separate the logic from the code behind file into the module project as much as possible so that these logic can be tested using Visual Studio test project or another testing framework such as NUnit.
WCSF projects can be created using a WCSF Visual Studio extension. The new extension contains a new WCSF solution template:
Once the solution is created, projects can be added to the solution using the add module wizard:
Module is a basic organization unit in WCSF. A module actually contains several projects. The following picture shows the organization of the Contacts module:
As seen in the picture above, when we add the Contacts module, WCSF adds a folder Contacts in the web project and also add a Contacts library project. When we add a web page, WCSF adds a view interface and a presenter class into the Views folder of the module project. WCSF uses model-view-presenter (MVP) pattern so that the responsibility for each page is divided between a model, a view and a presenter. I have seen people talking about the complexity and the steep learning curve of WCSF. However, some knowledge of the lift cycle would make it much easier to understand.
WCSF Lift Cycle
Whether we create a new WCSF web site or modify an existing web site to work with WCSF, the first thing is that the the Web Application must inherit from Microsoft.Practices.CompositeWeb.WebClientApplication or its subclass. This can be done by setting the following in the global.asax page:
<%@ Application Language="C#" Inherits="Microsoft.Practices.CompositeWeb.WebClientApplication" %>
This will allow WCSF to participate in the application initialization process. Specifically, it will search the Web.Config in the application root directory and sub directories to find WCSF configuration. A typical WCSF configuration section looks like:<compositeWeb>
<modules>
<module name="Contacts" assemblyName="MVPWithCWABQuickStart.Contacts" virtualPath="~/Contacts">
</module>
</modules>
</compositeWeb>The configuration inforamtion tells WCSF to load the module assembly and run the code in the ModuleInitializer class.
A page or a web control that participate in the WCSF life cycle needs to inherit from the Microsoft.Practices.CompositeWeb.Web.UI.Page or the Microsoft.Practices.CompositeWeb.Web.UI.UserControl class. This allows the object builder to instantiate the presenter/controller objects and inject them into the view through dependency injection.
The WCSF web page and the Model-View-Presenter pattern
Whenever we add a page to the web project, WCSF will also create a view interface and a presenter. The web page inherits from the Microsoft.Practices.CompositeWeb.Web.UI.Page class and implements the view, as see in the following code snippets:
public partial class ContactsList : Microsoft.Practices.CompositeWeb.Web.UI.Page, IContactsListView
{
private ContactsListPresenter _presenter;
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this._presenter.OnViewInitialized();
}
this._presenter.OnViewLoaded();
}
[CreateNew]
public ContactsListPresenter Presenter
{
set
{
this._presenter = value;
if (value != null)
{
this._presenter.View = this;
}
}
get
{
return this._presenter;
}
}
}The inheritance from the Microsoft.Practices.CompositeWeb.Web.UI.Page class would allow the page to participate in the life cycle of a WCSF page which uses ObjectBuilder to inject dependent class when the page is constructed. The CreateNew atribute instructs ObjectBuilder to construct a new instance of ContactsListPresenter and assign it to the ContactList class. WCSF creates the view and presenter does not enforce how the model is created. There are two flavors of Model-View-Presenter (MVP) pattern used in WCSF: PassView and Supervising Controller.
With Passive View, the Presenter is responsible for interacting with the business layer (the Model in this case). The view interface needs to expose every fields that the presenter would read and set. The web page would simply expose the control values as get and set properties. For example, the interface:
public interface IContactsListView
{
int SelectedIndex { get; set;}
}and the implementation:
public int SelectedIndex
{
get { return CustomersGridView.SelectedIndex; }
set { CustomersGridView.SelectedIndex = value; }
}In the Supervising Controller, the Model is often a View Model that contains the data to be presented. The presenter would pass an instance of view model to the view and view is responsible for rendering the view model, often through a data binding mechanism. For example:
public interface IContactDetailView
{
void ShowCustomer(Customer customer);
}Comparing the two flavors, the Passive View requires a large view interface with many members. To add a new field, you would need to modify both the view interface and the view implementation. In contrast, with supervising controller, you would only need to modify the view model to add a new field. However, some data binding logic are handled in the view and is not testable.
On Modulization
In our project, we have a fairly complicated page that is furthered spitted into several user controls developed by several developers at the same time. Each user control is a view by itself and has its own presenter. However, these user controls often need to share the same time and communicate with each other. That is where the Controller comes into the picture. Fortunately, the WCSF project already contains a controller and the presenter template already contains the code to inject the controller; we only need to uncomment the code. For example:public class ContactsListPresenter : Presenter<IContactsListView>
{
IContactsController _controller;
public ContactsListPresenter([CreateShared] IContactsController controller)
{
_controller = controller;
}
public IContactsController Controller
{
get { return _controller; }
}
…
}In order to have all the user controls share the same instance of the controller, we have to change the dependency-injection attribute from CreateNew to CreateShared. This way, the entire request would share the same copy of the controller. The CreateShared attribute as well as the code that needs to be added to global.aspx can be downloaded from the WCSF contrib project.
With all the user controls getting the same instance of the controller, the user controls can interact with each other by calling controller methods and subscribe to events fired by the controller.
Unit Testing
One major objective of WCSF is that the classes can be unit tested outside of a web server. This would requires creating mock view that implements the view interfaces to interact with presenters and controller. For this reason, one want to leave out as much as logic from the code behind as possible so they can be unit tested. In addition, the module project should be free of reference to request, session and other web server objects so that they can be tested outside a web server. The WCSF has StateValue<T> and StateDependency classes for injecting session value into the module classes. The WCSF contrib also has a QueryStringValue<T> class for injecting the query string parameters.
Impression
My impression is that WCSF works with an existing web form application very well. It can be used to split large project or complicated pages into smaller pieces to be worked simultaneously. We did not encounter any significant limitation with WCSF.
The next question is whether we want to WCSF or ASP.NET MVC since this is a FAQ. The choice is really between WebForm and ASP.NET MVC. WCSF is just a way to make WebForm project more organized. I would say that WebForm is still the most productive framework (although it does not produce the most optimal HTML code). WebForm is a mature framework and has a abundant third party controls. If you have limited budget, performance is not major concern, or you use lots rich dhtml controls like treeview, WebForm is still the way to go. On the other hand, if you have a bigger budget, needs to product optimal Html code, or is concerned with the future growth of the project, and are comfortable to deal with the occasional situation of not able to find some server controls so that you have to use some JQuery client-side controls, then MVC is the way to go.