The concept of embedding all of the logic in a single class has always concerned me. In my personal development, I've attempted to get around some of these concerns by embedding logic in other components. This way, the work is being conducted outside of the Page class, which becomes easier to test and isn't lifecycle driven. This is what the MVP pattern was intended to alleviate. By breaking apart the view logic from the presenter, these concerns are properly separated. Through the WebFormsMvp open source project, we get an implementation of the MVP pattern with loose binding and flexibility.
What Is MVP?
The core approach to the MVP pattern is to separate the UI logic from the main component of work performed by the presenter (also called a controller). The presenter focuses on calling the business/data layer, performing any calculations, and any other logic that drives the page but that isn't UI specific. The UI-specific code is handled through the view, which performs data binding, control loading, handle-control events, and other UI-specific features.
Lastly, a model class represents the data that the presenter will pass to the view, which the view will use for whatever it needs to use it for. The model is pretty much a state bag, without any additional logic.
How Does This Change Our Work?
When developing a Web Forms application, previously developers would put all the logic right in the code-behind. For instance, a save button triggers a click event to an event handler, where the page saves the data to the database and refreshes the UI.
Using the MVP pattern, the view raises an event that the presenter attaches to, passing the data to save through the event argument. This event defined in the view (not the original button click event) signals to the presenter to perform the database save and return the updated result set through the model that the presenter passes to the view.
Does this seem like more work? It does result in a higher percentage of lines of code. However, this is miniscule in comparison to the benefits it gives you, such as separation of concerns, a higher percentage of testability, a greater level of abstraction, and loose coupling.
Introducing the WebFormsMvp Framework
Enter the WebFormsMvp open source framework, which utilizes the following components:
• Presenter: This is a class that inherits from Presenter or Presenter<T>.
• View interface: This is an interface that inherits from IView or IView<T>.
• View Implementation: The page or user control that implements the view interface.
• Event Arguments: A custom event argument class is the way to pass data from the view to the presenter.
• Model: This is a class that contains the data needed.
The View Structure
The concept of a view differs from the typical way a Web Forms developer thinks of view. A view is constructed of two parts: an interface and its implementation. The interface inherits from IView or IView<T> (where T is a reference to the model) and defines a series of properties, events, or methods that make up the contract. But as the interface defines the contract, the view implementation must meet its demands. This is where the standard ASP.NET Web Forms objects fulfill this need.
Because ASP.NET pages and user controls are ASP.NET specific, it's hard to pass these as a view instance. By having the user control code-behind class declare an interface, passing a user control instance to the presenter becomes much easier; in a sense, the interface allows key intercommunication through object in the ASP.NET framework.
The WebFormsMvp framework prefers using user controls as a means of displaying a view. The page is obviously present, but typically, no logic resides in the page. In this way, one or more views can appear within a single page. Each of these views can work with its own presenter and even share data between the presenters.
An event signals some action has taken place. The presenter attaches to the view's event (because the view has to define the event in the interface) and can respond in reaction to it. For instance, if the view has a calculate event, the presenter can listen for this event and perform whatever calculation it is responsible for. The view passes information to the presenter via the event argument of the event typically, which means custom event arguments are often created to contain the needed data. By default, the view has a Load event.
Sometimes referred to as a controller, the presenter contains all the business logic; it performs the database work, provides any computational calculations, and more. The presenter class knows nothing about the ASP.NET runtime but may use some of the key runtime objects (such as the current HttpContext). A presenter does not directly have the lifecycle initialize, load, or prerender defined events. However, it does know about the view that has the Load event and can attach to it in the presenter constructor. Presenters can be shared, which means that one presenter can represent multiple views.
What about working with controls (like a TextBox, GridView)? For the presenter, working with controls is outside of the responsibility for the presenter. It's the view's responsibility to update the UI and work with the controls, which the presenter communicates with the view via the model.
The model is nothing but a state bag; it contains no logic, nor should it. It's simply a data-transfer object (DTO), a means to get data from the presenter to the view. The model is important because it does contain the vital data the presenter needs, but contains no computational logic of any sorts.
The following is a sample component that implements the WebFormsMvp pattern of development. To start, the basic building block of the application is the view. The view begins with an interface definition via the code in Figure 1, which may or may not have a model associated with it. A model, similar to the MVC framework, is exposed through the Model property and provides the source of data to the view, as shown in Figure 1.
Whatever the view interface and model defines is all the presenter knows about, as it's the presenter's responsibility to read and write model data and work with the view. It doesn't invoke the view's events though; rather, the presenter only listens to them. These events represent some action that the presenter needs to act upon, such as a save or cancel action occurred. Our first presenter appears in Figure 2. Note how it attaches to these custom view events.
This presenter does a lot of the work within the view's event handlers. For instance, when the view updates an item within the shopping cart, the presenter performs the update work and updates the model that gets passed back to the caller. Additionally, the update broadcasts that an updated shopping cart exists.
The WebFormsMvp framework supports publishing messages through the IMessageBus interface. Presenters can subscribe or publish any message, which simply uses the type as the subscription definition. For this example, any presenter listening to the ShoppingItemEventArgs type receives the updated shopping cart instance. The subscribe handler happens in Figure 3.
Another presenter subscribed to the ShoppingItemEventArgs message. Any presenter subscribing to the ShoppingItemEventArgs type will receive this message and can act accordingly through an action statement. Note that it's important to create a good messaging scheme, one that ensures only the correct subscribers are called, but also that ensures the data needed at the time of subscription will be present.
As I mentioned earlier, the WebFormsMvp framework recommends using user controls as the display mechanism, rather than pages. Our shopping cart display user control implements the IShoppingCartDisplayView interface, and fulfills the contract in Figure 4.
Each view user control does two things: Implement PresenterBinding attribute (either explicitly or by convention) and inherit from MvpUserControl (for IView implementations) or MvpUserControl<TModel> (for IView<TModel> implementations). Notice the user control defines our interface and fulfills the contract.
It may seem ironic that the view implements events and some methods, but the methods don't do any work, except to call their own events. The proper separation of layers requires the presenter to actually perform the delete, update, and get work and simply respond by updating the UI. Additionally, you may wonder why implement these methods at all, since these methods don't actually do much. This is where some of the WebFormsMvp controls come into play.
The shopping cart UI uses a GridView for displaying the data, plus the PageDataSource control to populate and modify the data. The PageDataSource is a custom control that inherits from ObjectDataSource and provides some enhanced features. First, the data source takes the type of object to push/pull through the DataObjectTypeName. This is the type of object passed to the SelectMethod, DeleteMethod, and UpdateMethod operations. In this case, these methods defined here appear in Figure 4—so the purpose of the control is to call methods within the view's implementation directly. The control is displayed in Figure 5.
Data-bound controls are recommended for representing the UI. A DetailsView or FormView control works for displaying a single record of data and is often recommended for doing so. Most of the samples in the WebFormsMvp demo use data-bound controls like the FormView.
Getting the Data Just In Time
As an alternative to binding the model, there was a trick I learned that can help your application request the data it needs at the right time. Since the presenter/view relationship is event driven, at the exact moment a view needs to display data in the UI, it can request the data first via the event and return the results from the presenter through the event argument properties. This is an alternative way for the view to request data.
The first key to getting this to work is to use an event argument that has all the parameters we may need, as shown in Figure 6. Using the PageDataSource control, the control calls a method in the view to get the data. We can use our NeedProductsEventArgs argument to raise the need to load the data to the presenter and return the data to the view, rather than explicitly loading a model, as in Figure 7.
Here is an interesting conundrum with the MVP pattern: In the view's GetProducts method, when the event argument specifies the page index and size to display in the UI, is the view code segment a creep on the boundary of the responsibilities of the view? Is the view supposed to tell the presenter this, or is the presenter supposed to tell the view? After all, the view needs to manage the UI, and the presenter is responsible for processing logic. However, I'm going to argue no boundaries were crossed, because the view is managing the amount of data it needs to displayed in the relate space given in the UI control.
The WebFormsMvp framework also supports AJAX. If you plan to use update panels, this feature works just like any of the other features. For instance, take a look at Figure 8.
The view implementation looks like the code in Figure 9. Using the "just-in-time approach mentioned previously, the NeedProducts event fires at the time the data is needed. Additionally, the timer refreshes the ListView every 5 seconds with new related products, which happens in Figure 9. Note that the WebFormsMVP framework supports using AJAX web services, too.
The WebFormsMVP framework comes with a host of other services useful to you. I already discussed the message bus and how it transfers messages from one host to the other. These are the other features offered by the framework.
The AutoDataBind feature of the MvpUserControl and MvpPage controls how the data may be displayed in the UI. Using the <%# Model.\\[property\\] %> syntax, the data from the model can be directly displayed in the view, if this feature is turned on (set to true). Otherwise, any statements of this kind are ignored. By default, the feature is true and can be turned off if the particular view has no binding statements, as shown in Figure 10. Note that each view can turn on or off the feature without affecting the other views.
Creating the Presenter
By default, each presenter is created from an IPresenterFactory instance. This factory constructs the actual presenter; however, another service is used to locate the presenters. Similar to ASP.NET MVC, the view is loosely related to the presenter through the use of naming convention or by the use of attributes. The service that manages these features implements the IPresenterDiscoveryStrategy interface. For fans of third-party dependency injection tools, the framework includes support for the AutoFac, Castle, and Unity frameworks, each with its own presenter factory.
A presenter is first discovered by the use of convention or by attribute; either it's found by the \\[PresenterBinding\\] attribute or it's defined in a predefined namespace. The PresenterBinder class in the WebFormsMvp.Binding namespace refers to each of these services and has a default implementation; however, you can override them by creating a new instance of each service and assigning to theFactory and DiscoveryStrategy static properties.
The AsyncManager reference (of type IAsyncTaskManager) can register asynchronous tasks performed through threading. To register an asynchronous task, use the RegisterAsyncTask method, which has both begin and end callbacks, plus a timeout callback when an error occurs, as shown in Figure 11.
Manually Assigning Data
If you tend to prefer not to utilize the markup approach to binding data (using data-bound controls or binding statements like <%# %>), it's also possible to manually assign values to the UI. But here are some points of interest before you attempt to do it.
First, presenter binding happens in the InitComplete event. During the presenter binding process, the presenter is instantiated and tied to the view. The model is also instantiated and passed to the view using reflection. This means that the presenter would perform any data loading in the view's load event handler. The load event handler fires first within the ASP.NET lifecycle, then the user control, and finally reaches the presenter.
If you wanted to manually assign values from the model to the UI in the load event, your code will be too early. The view code Load event runs before the presenter loads the model, and as such the model may be null. This is why the data source controls are the best bet for ensuring everything happens at the proper time, but the LoadComplete or PreRender events are other alternatives to embed any code that uses the model or assigns any other properties to UI controls.
If your code establishes any routine UI maintenance without the need of the model, the Init event works just the same as with web forms.
You can download the framework and source on the CodePlex site. Click on the source code tab and select the latest change set. A built-in code-browser displays all the code related to the latest change set.
More documentation is available on the ASP.NET Web Forms MVP site. You can also find presentations and articles, as well as a link to the Google group, on the Web Forms MVP Google group page.
There is third-party support for this framework. First, three "forks" of the code base exist and add additional support to the framework. These are a separate framework download, which you can be download on the Web Forms MVP page. This includes my fork of the code (WebFormsMvpExtensions), in which I intend to include more DI capabilities and raw AJAX extensions, among other features. Contact me if you are interested in contributing.
Also, check out the Contrib project on CodePlex, which also has some extensions to the framework.
A Helpful Framework
Patterns really help us write better code. The MVP pattern is an excellent pattern to use with Web Forms, and the WebFormsMvp framework gives us that flexibility very easily.
Brian Mains ([email protected]) is a Microsoft MVP and consultant with Computer Aid, where he works with nonprofit and state government organizations.