This article introduces a mechanism for implementing the Model View Presenter pattern in ASP.NET in a generic, declarative fashion. The resulting implementation does not require code-behind in the page to deal with the connection of the various elements (though some is still used in the View implementations). The article hereafter leverages several tutorials I've published here on The Code Project in past months:
As a result, this is something of an advanced implementation of Model View Presenter for ASP.NET, but once the initial support libraries are in place, is very lightweight to implement and support.
Why Model View Presenter for ASP .NET 'Felt Wrong'?
The more time-pressed implementations of Model View Presenter will take User Controls (The .ascx files) and use these template files as the basis for views, attaching view logic to the 'Code Behind' elements of the view. This, in and of itself, is not particularly worrisome. Time-allowing it's usually possible to wrap View implementations into actual ASP.NET Server Controls, allowing them to be toolboxed much more easily than User Controls, without the need to apply code-behind. This is a minor problem though, compared to some other issues.
Where a lot of abstraction and robustness falls away tends to be the code-behind on the pages themselves, which seem over-burdened with duties. The typical pattern seems to be:
This leads us to a situation where the page is coded against concrete implementations of multiple aspects. It's not possible to switch the view, presenter or even control how the model gets passed to the presenter without having a developer fire up Visual Studio. It seems the page is acting as a fourth role within the paradigm, acting to weave the various elements together. This duty of care is typically referred to as Orchestration.
What Does an Orchestrator Do?
Taking our ASP.NET example. It seems that any component that would be built to Orchestrate the various associations in the Model View Presenter scenario would need to supply instances of each of the appropriate elements and connect them all together.
The duties of this orchestrator would be:
- Activate an instance of a type implementing the view contract.
- Activate an instance of a type implementing the presenter contract
- Optional: passing through any parameters (the model instance, for example)
An additional special duty for ASP.NET would be:
- Insert the control at some pre-defined point on the page.
The reason for this requirement is that we may have multiple views to create against a single presenter. In that scenario, it's clear that inserting the controls at the location of the
Orchestrator in the markup would not be a useful proposition.
So there we have it, the definition of what we're after. But how do we define such an entity? What would it look like? How can we make it work like the rest of the ASP.NET Framework?
Case Study: ObjectDataSource
A mainstay of ASP.NET WebForms development, the
ObjectDataSource control allows for easy connection of methods to ASP.NET server controls, such as the
Repeater. Let's look at some typical syntax for using this component:
<%-- Define our data source --%>
<asp:ObjectDataSource ID="SomeDataSource" runat="server"
<asp:ControlParameter ControlID="SomeTextbox" />
<%-- Define our target, linking ito the data source --%>
<asp:Repeater ID="RepeaterDisplay" runat="server" DataSourceID="SomeDataSource">
Item display template.
With little/no work a page can be made to use some other sources for components. The underlying method needs only have the right definition, return the right types and you're there. This ability to declaratively wire-up components leads to some great gains:
- Designers can re-use a library of data sources with standard components and re-design the site entirely.
- The data-source itself could be a mock/dummy data source (often useful during design time, or when very early in the development cycle).
- The elements can be composed together on new pages without requiring any new coding be done (not possible if the wire-up was done in the page codebehind).
Keeping this in mind, it's time to turn back to our MVP orchestration issues.
An Orchestrator Control for ASP .NET
Having seen the way other ASP.NET wireup assistants work, it seems logical that any
orchestrator component should be entirely declarative in style. There should be no code involved in connecting pieces together, and nothing that ties a specific group of elements to a specific page or location.
Using the Generics Framework I introduced here on Code Project a few months ago, I was able to produce an #
Orchestrator component that is able to load up user-controls dynamically, creating a presenter, connecting the two and using a complex-parameter input, constructing the model to pass through to the presenter.
In this model, the page itself knows nothing of the design pattern being used. There's no rewrite of ASP.NET's fundamentals (a-la the MVC Framework) and the page is more or less a simple, standard, familiar web-form. The Orchestrator component, during page initialization performs the following tasks:
- Activate any dependencies of the presenter (The Model)
- Activate the presenter, passing through the dependencies
- Activate any views:
- Create user control instances
- Insert user controls within placeholders on the page
- Attach the user control the presenter
This sequence of events occurs during the Init stage of the page-life cycle, allowing standard ASP.NET WebForms events to fire during the
Raise Events stages without any further work. This gives the advantage of meaning that postbacks and other essentials work as-is, even though we're using Model-View-Presenter as our paradigm de-jour.
Revising the Model-View-Presenter Clock Example
The same project this time around is an extension of the previously mentioned MVP implementation of a clock with time-zone support. For reasons of practicality, the time is not pushed every second into the rendered page via AJAX, unlike the WinForms example code, which updates each second.
The implementation this time around involves a web application project, with a User Control that implements our view. All the other elements are dealt with by the exact same code that powers the WinForms application, same model, same presenters, literally just a different view. Once that happens, of course, to be an ASP.NET user control.
The syntax for the use of the
Orchestrator is pretty straightforward and looks like:
<form id="form1" runat="server">
<asp:PlaceHolder ID="ClockViewPlaceHolder" runat="server" />
<CMVP:Orchestrator ID="Orchestrator1" runat="server"
Notice that the mechanism by-which views and presenters are loaded is completely pluggable. This is achieved through implementation of the
IPresenterLoader interfaces. When you spin up the web application, you'll see a screen like this:
Changing the selected time zone in the drop-down will refresh the time, and for the impatient there is a button that will cause the page to reload (bringing with it an updated view of the time!). Once the framework is in place, building new views, presenters and so forth is trivial.
Under the Hood: Generic Controls in ASP.NET
As per my article on ASP.NET Generics, the
Orchestrator is subject to some trickery. When ASP.NET loads the page and starts preparing the various controls for display, the
Orchestrator is swapped for an instance of
OrchestratorImplementation with the correct view and presenter contracts specified as generic parameters, an implementation that leverages the coupled contracts support for bi-directional strong typing. This allows the bulk of the code to be strictly type-safe, as in the following snippet:
TPresenterContract presenter = Presenter.GetPresenterInstance<tpresentercontract, />(this);
foreach (IViewLoader viewLoader in Views)
viewLoader.Connect<tviewcontract, />(this, presenter);
The only point at which any reflection trickery is actually used is the standard ASP.NET data-source parameters. Rewriting those for a tiny performance gain seemed fruitless, especially since the only 'reflection' usage in this case is to load the presenter instance and pass through its constructor parameters.
Today we've more or less introduced enough material to implement a declarative MVP framework for ASP.NET. Pages have been liberated of their duties and knowledge of the underlying types, strong typing of presenter/view operations behind the scenes is guaranteed.
- 11th October, 2009 - Initial draft