In my last three posts (here, here, and here) I documented the slight detours I took to address some perceived shortcomings in two of my tools-of-choice for the SkillPortal Project: CIFactory and MbUnit. Now that those are resolved favorably, I can return to the work on the project.
In the last post about the actual project, we finalized (as much as possible at this very early stage) the unit tests for the Presenter class in our Model-View-Presenter style approach to building the user interface of our application. That completed, we can safely (and, some would say, finally) turn to building the actual user interface for our application that will leverage the work we have done thus far in coding our solution.
Biting off what you can chew
Astute readers may have noticed that although our original UI mockup had a considerable complexity to it, we have been working with thus far with only a subset of that functionality in our View and Presenter classes.
If you look closely, you will see even from the names I have chosen for the classes we have developed (IPersonSearchCriteriaView and PersonSearchCriteriaPresenter) that we are currently focused solely on the Person Searching-part of the UI (shown at left) and related code.
This was done for two reasons. First, this is the more complex part of our code and application behavior so it makes sense to concentrate our initial effort here; the rest of our UI mockup is really about displaying and navigating the results from this person-search process. Second, just as good coding practices tell us to encapsulate different behaviors into different classes, so too does good MVP pattern implementation lead us to do the same thing with Views and Presenters. We have a View and a Presenter for the person-searching functionality of our application and we will have a separate View and Presenter for the other behaviors of our application as well.
User Control as View vs. Page as View
This encapsulation of our UI behaviors will give us a considerable amount of flexibility in how we actually implement the Views for our user interface as we design our web site. By keeping the main functions of searching, displaying search results, and then displaying a single person’s skill profile as separate Views (each with its separate Presenter) we make it simple to move these capabilities around in our UI should future needs of the application demand it.
For example, as currently conceived all of these functions are on a single web page in our planned site. If at some point in the future it was decided to create a completely separate page to display an employee’s detailed profile then having an IPersonProfileDetailsView class and a PersonProfileDetailsPresenter class that already exists all on their own would enable this level of flexibility with little if any changes to these classes.
In fact, for larger web projects its not at all an uncommon practice to actually take each of the functional aspects of one’s UI and formally encapsulate them as separate user controls (along with their separate View interfaces and separate Presenter classes) so that each web page can be ‘assembled’ from a kit-of-parts of user controls that already encapsulate all of their needed functionality within them, making it trivial to move capabilities from one web page to another throughout the project’s lifecycle as design demands change.
This approach, often referred to by others as User-Control-as-View instead of the more common Page-as-View style of Model-View-Presenter under ASP.NET, also facilitates easier UI component testing as well. Although we will structure our classes in this manner to facilitate some degree of future flexibility in this project, we aren’t going to go so far as to bother to create individual user controls for each of the primary functions of our User Interface as part of the 1.0 release of the solution. If the future demands lead us to start moving UI elements from page to page (or even to repeat UI elements on multiple pages) then we can look at encapsulating various functionality as user controls at that time. If we need to do this in the future, then our current approach of having a separate class for the Search Criteria View and another for the Search Criteria Presenter will make this change simple to accommodate.
Drag-and-Drop in the designer isn’t entirely Evil
A surprising number of people posting their thoughts on the Internet tend to consider the ability to drag-and-drop controls from the designer toolbox into a web page in Visual Studio to be an inherently evil process, necessarily leading one to create bad software. I certainly won’t argue that its not possible to create bad software this way, but to unilaterally toss out the toolset just because it lets you create bad software strikes me as a little draconian (after all, I can craft a poor database schema using SQL Server, but nobody is clamoring to drop it as a useful tool because of this).
The same is true of using tables as a layout mechanism vs. CSS positioning. There are some times when tables are truly evil as a layout paradigm, but other times when they are perfectly appropriate. To that end, our first attempt at composing the page in the Visual Studio web forms designer is to drag-and-drop some controls into an HTML table with the proper columns and rows as shown in this figure.
Implementing the View Interface
Since our aspx page is acting as our view in this case, our next step is to set the class in the .aspx code-behind page to implement the view we need. The magic that is the Visual Studio IDE has already created a partial class for our aspx page and set it to inherit from System.Web.UI.Page like so…
public partial class _Default : System.Web.UI.Page
All we need to do to tell this class to also implement our View interface is to add it to the class declaration as so…
public partial class _Default : System.Web.UI.Page, IPersonSearchCriteriaView
For a little foreshadowing of what is to come, one can already start to imagine that as our page grows and needs to support other view/presenter combinations, this same Page-derived class will be told to also implement other interfaces we develop and add to our page as well.
Recall that our view interface (IPersonSearchCriteriaView) is defined as shown in the class diagram at left. The view interface is designed to be as ‘dumb’ as possible, exposing really little more than just a collection of properties that enable its corresponding presenter to set two groups of properties: Offices, Regions, Skills, etc. that hold available selection lists for their corresponding controls to bind to and SelectedOfficeId, SelectedRegionId, SelectedSkillId, etc. that hold the id of the selected item in the control that the user has chosen for each of the search criteria elements.
In addition, notice that the view interface defines a single boolean property, IsInitialLoad, that is designed for the presenter to interrogate to determine whether the view is being loaded for the first time or not. This is an attempt to abstract away the page-derived class’ own IsPostBack property so that the view interface is not making an assumption that its concrete implementation will be coming from a webforms world.
The view interface also declares a single method, PopulateSearchCriteriaControlsWithInitialValues(), that is designed to be called by the corresponding presenter at the appropriate time (and with a method name like that, can there be any doubt in anyone’s mind when that appropriate time actually is???? ). This method will be invoked by the presenter when the page is first loaded (e.g., when !IsPostBack in a webforms world), but not when the page is loaded from a postback where the control values are already persisted in viewstate + controlstate.
Finally, the view interface defines a single event that is used to indicate that the user wants to perform a search. In our case, this will be raised by the view when the user clicks the ‘Search’ button, but the point of abstracting it this way is so that whatever control actually asks that the search is performed needs to only raise this event to do so. Our presenter will respond to this event accordingly and do its needed work.
Meeting the Interface Contract
Since our code-behind class, _Default, needs to implement our IPersonSearchCriteriaView we need to go about doing so in order that our code compiles. Step 1 will be to add a field to represent the state of the view…
private bool _isInitialLoad;
For our next step, each of the properties that are designed to hold collections of things need some related fields…
private IList<Office> _offices; private PersonSearchCriteriaPresenter _personSearchCriteriaPresenter; private IList<PersonSkillSearchView> _personSkillSearchResults; private IList<PersonType> _personTypes; private IList<Region> _regions; //etc...
Then for most of these we need a corresponding public property. Our setter for each of these will do a slight bit more than your typical setter that just sets a value however. Our setter for most of these lists that populate the controls will be responsible for adding the "<ALL>" name to the first item in the collection if that item has an Id of 0 (the Id explicitly added to the returned collection by our presenter). The setting of this here in the view seems to fly in the face of the idea of the ‘dumb’ view that does nothing, but I decided this was a display-related action that made sense to leave in the view code as shown in this example of one of the PersonTypes property declaration that eventually gets bound to a control…
public IList<PersonType> PersonTypes { get { return _personTypes; } set { if (value != null && value[0].Id == 0) value[0].Name = "<All>"; _personTypes = value; } }
Later on, I may change my mind and make the presenter responsible for adding this value rather than the view, but for now, this will work although it does sort of feel like a ‘code smell’ where my presenter and my view are now sort of collaborating in a way that feels inappropriate. We’ll see if I come back and revisit this design decision later; I suspect I will have to since anything I feel this uncomfortable about is very likely my subconscious struggling to tell me to stop being an idiot and address the problem I’ve created
Leaving this somewhat questionable design decision as it stands for the moment, our next step is to declare the properties the interface requires that return the Id values of the selected items from the controls. As shown in the following snippet, these properties can just get their values directly from the corresponding controls, since we are now in the View and the View is ‘allowed’ to know about its own controls…
public int SelectedOfficeId { get { return Convert.ToInt16(ddlOffice.SelectedValue); } } public int SelectedPersonTypeId { get { return Convert.ToInt16(ddlPersonType.SelectedValue); } } public int SelectedRegionId { get { return Convert.ToInt16(ddlRegion.SelectedValue); } } public int SelectedSkillCategoryId { get { return Convert.ToInt16(ddlSkillType.SelectedValue); } }
If our implementation of the view should change at some point in the future (either to become a windows form or to use a different control to provide the user a selection method) then the meat of these property declarations would need to change but so long as they are able to return an integer, we are in compliance with the contract specified by the interface and all is well.
Our next step is to comply with the interface’s requirement that we have an event in our view. This can be met with nothing but a straightforward .NET event declaration as so…
/// <summary> /// Occurs when perform search button is clicked. /// </summary> public event EventHandler PerformSearchEvent;
Wiring up The Controls to the Properties
The last part of the interface contract that our web page must satisfy is that of the need for the public method, PopulateSearchCriteriaControlsWithInitialValues(), that is responsible for hooking up the class’ properties filled by the presenter with the controls the view provides. This can be easily accomplished using plain old .NET databinding calls to bind each control to its corresponding property as shown in the following snippet…
public void PopulateSearchCriteriaControlsWithInitialValues() { ddlPersonType.DataSource = this.PersonTypes; ddlPersonType.DataValueField = PersonType.PropertyNames.Id; ddlPersonType.DataTextField = PersonType.PropertyNames.Name; ddlPersonType.DataBind(); ddlRegion.DataSource = this.Regions; ddlRegion.DataValueField = Region.PropertyNames.Id; ddlRegion.DataTextField = Region.PropertyNames.Name; ddlRegion.DataBind(); ddlOffice.DataSource = this.Offices; ddlOffice.DataValueField = Office.PropertyNames.Id; ddlOffice.DataTextField = Office.PropertyNames.Name; ddlOffice.DataBind(); ddlSkillType.DataSource = this.SkillCategories; ddlSkillType.DataValueField = SkillType.PropertyNames.Id; ddlSkillType.DataTextField = SkillType.PropertyNames.Name; ddlSkillType.DataBind(); //etc...
Note a few things about this section of code that stand out (hopefully). First, review the data-binding syntax where the DataValueField and DataTextField are set like…
ddlRegion.DataValueField = Region.PropertyNames.Id; ddlRegion.DataTextField = Region.PropertyNames.Name;
In a typical programmatic data-binding call, these properties of the control would be set using string-literal values like so…
ddlRegion.DataValueField = "Id"; ddlRegion.DataTextField = "Name";
…but this of course leads to terribly brittle code that is not checked at compile-time and will break (at run-time of all things!) if these properties of our Region data-transfer-object (DTO) class ever have their names changed. This points out some of the power of our code-generator approach to building our DTOs: we include code that makes all of the public properties available as static members of the class.
This provides two huge advantages over using string-literals for this:
- as properties of a static instance of the class, they will be updated if/when the DTOs are re-code-generated in response to a database schema change, which will make the code fail at compile-time, vs. throwing a run-time exception
- as static members of the class, they are available to the developer using VisualStudio’s intellisense during development as seen in the following screenshot where the user is setting the DataValueField property of the ddlRegion control…
The other thing you might notice is that we are using standard webforms databinding to wire these controls up to the properties of the view that hold their datasources. A number of MVP proponents seem to espouse the idea that databinding is inherently ‘evil’ and needs to be avoided at all costs. For them, the likely approach to connecting the controls to their related properties that function as their datasources would involve something kind of like the following…
foreach(PersonType p in this.PersonTypes) { ddlPersonType.Items.Add(new ListItem(p.Id, p.Name)); }
…where they would iterate through every item in the property and add them to the dropdown list one at a time.
Personally, I just don’t see the advantage/need for this; while there are all kinds of messy aspects to data-binding in .NET that really should be redesigned around, I see no need to ‘throw the baby out with the bathwater’ and abandon data-binding where its perfectly appropriate even if there are other situations where its completely inappropriate. Besides, the above sample is one that I see over and over again in MVP examples, but I find it at least a little curious that nobody ever posts how to get a more complex control (like a datagrid, etc.) populated without databinding . The bottom line in our world is this: we don’t throw out something just because its part of the webforms universe and used poorly it can lead to trouble; instead we try to learn how to best use the technology given us and avoid the heck out of using it poorly.
Final Housekeeping in our View
Lastly, now that we’ve got everything implemented that satisfies the contract our interface enforces, we need to provide a few event handler methods in our page that anyone who has done any ASP.NET work at all will recognize: Page_Load, Page_Init, and a handler for our search button’s click event.
First things first, our Page_Init() handler…
protected void Page_Init(object sender, EventArgs e) { _isInitialLoad = !IsPostBack; _personSearchCriteriaPresenter = new PersonSearchCriteriaPresenter(this); }
In this handler, all we need to do is set our page (view)’s _isInitialLoad field to !IsPostBack so that our presenter can know if the page is being loaded for the first time or as part of a postback process and can react accordingly within its own code. Then we need to construct an instance of our presenter and store it in the page’s related field so we can get it later. Note that as expected, the call to the presenter’s constructor is being made with the view passing itself (this) in as the argument so the presenter gets a reference to the view to interact with as needed.
Next, our Page_Load() handler…
protected void Page_Load(object sender, EventArgs e) { _personSearchCriteriaPresenter.InitializeView(); }
This is the canonical handler method body in any MVP implementation and is the point where the true beauty and simplicity of this pattern is most obvious: since all the work is done in the presenter, the view needs to do absolutely nothing in its load event but tell the presenter to get started with its work (by calling its InitializeView method as shown). All of the usual mess that happens in a traditional Page_Load() handler is completely gone from the page (although its still around, its just been moved to the presenter, of course).
Lastly, we need a handler for our search button’s click event…
protected void btnSearch_Click(object sender, EventArgs e) { //raise the view's event for the presenter to respond to if (PerformSearchEvent != null) PerformSearchEvent(this, EventArgs.Empty); }
All this handler needs to do is raise the event that’s defined in the view interface (PerformSearchEvent); the presenter will respond to it as needed to actually do the search and fill the view’s properties with the results. Note that if we were willing to accept a slightly tighter coupling between the view and the presenter, we could have just made the presenter respond to the search button’s click event directly, but this would mean that if some other control in a later version of the view wanted to be the trigger that performs the search, the presenter would have to be revised as well. This way, even if a simple mouse-over wants to be the thing that causes the search to take place, all that action needs to do is become the thing that raises the PerformSearchEvent and the presenter need never know about it.
This was a long post (even relative to many of my other ones! ) but in the end it should (hopefully) make alot of the MVP pattern a little clearer, both in terms of how its used in ASP.NET and how it integrates into the rest of what we’re trying to do on this project. With this view created (at last!), we are ready to move on to develop views and presenters for the other aspects of our overall application: the search results display and the employee details display.
More on that next time~!
Hi Steve,
Can you possibly provide the remaining results display for this tutorial.
The series is almost there.
Thanks,
Edgar
What happened to this?