This article is cross-posted from my personal blog.
In an earlier blog I mentioned that since DotNetNuke 5.3, we have supported MVP (Model View Presenter) style Module Development using the WebForms MVP project as a basis. In this blog I will begin to explore how we can use the new DotNetNuke base classes ModulePresenter and ModuleView to build fully testable modules.
There are four components for each MVP Module Control.
- An Interface which defines the View
- A Model class which will be passed between the Presenter and the View
- A Concrete View class (the User Control which renders the content to the Response stream) and
- A Presenter Class which manages everything
As an example lets look at the code for the Taxonomy Manager module. This module was first included in DNN 5.3, but it will be updated/refactored in DNN 5.4 (as we made a few changes to the base classes), so the code examples in this blog are from the latest source code.
IVocabularyListView Interface
We will start with the Interface which defines the View (Listing 1).
Listing 1: The IVocabularyListView Interface
|
1: Public Interface IVocabularyListView
2: Inherits IModuleView(Of VocabularyListModel)
3:
4: Event AddVocabulary As EventHandler
5:
6: Sub ShowAddButton(ByVal showButton As Boolean)
7: End Interface
|
The first thing to notice is that the Interface inherits from the IModuleView(Of VocabularyListModel) interface. IModuleView(Of T) is a base interface for all DotNetNuke Modules (Listing 2).
Listing 2: The IModuleView(Of T) Interface
|
1: Public Interface IModuleView(Of T As {Class, New})
2: Inherits IView(Of T)
3:
4: Event Initialize As EventHandler
5:
6: Sub ProcessModuleLoadException(ByVal ex As Exception)
7:
8: Sub ShowMessage(ByVal messageHeader As String, ByVal message As String, _
9: ByVal messageType As ModuleMessageType)
10: End Interface
|
As long as Module Developers create their concrete Views by inheriting from the base View class (ModuleView) they do not have to implement the three members of this base Interface, as these are implemented in the base class.
The important thing to learn from both these listings is that in general the interfaces contain only methods and events (actions) – no properties (state). The Model class provides any state information.
VocabularyListModel Class
We now turn to the model. This class contains anything that we need to move from the Presenter to the View. In the case of the VocabularyListModel, it has three properties (Listing 3)
Listing 3: The VocabularyListModel Class
|
1: Public Class VocabularyListModel
2:
3: Private _IsEditable As Boolean
4: Private _NavigateUrlFormatString As String
5: Private _Vocabularies As IList(Of Vocabulary)
6:
7: Public Property IsEditable() As Boolean
8: Get
9: Return _IsEditable
10: End Get
11: Set(ByVal value As Boolean)
12: _IsEditable = value
13: End Set
14: End Property
15:
16: Public Property NavigateUrlFormatString() As String
17: Get
18: Return _NavigateUrlFormatString
19: End Get
20: Set(ByVal value As String)
21: _NavigateUrlFormatString = value
22: End Set
23: End Property
24:
25: Public Property Vocabularies() As IList(Of Vocabulary)
26: Get
27: Return _Vocabularies
28: End Get
29: Set(ByVal value As IList(Of Vocabulary))
30: _Vocabularies = value
31: End Set
32: End Property
33:
34: End Class
|
The three properties are:
- IsEditable – a boolean property that indicates that the current user has “Edit” rights – we will set this in the presenter and use its value in the View to optionally render ane “Edit” hyperlink column.
- NavigateUrlFormatString – a string property that we can set in the Presenter and is used in the View to generate the correct hyperlink for the Vocabulary row.
- Vocabularies – a list of Vocabulary objects that we will bind to a grid in the View.
VocabularyListPresenter Class
The VocabularyListPresenter Class controls the action. As can be seen in Listing 4 it inherits from the ModulePresenter base class. The base class is a Generic Type, taking two type parameters – the View Interface – IVocabularyListView and the Model VocabularyListModel.
Listing 4: The VocabularyListPresenter Class
|
1: Public Class VocabularyListPresenter
2: Inherits ModulePresenter(Of IVocabularyListView, VocabularyListModel)
3:
4: Private _VocabularyController As IVocabularyController
5:
6: Public Sub New(ByVal view As IVocabularyListView)
7: Me.New(view, New VocabularyController(New DataService()))
8: End Sub
9:
10: Public Sub New(ByVal listView As IVocabularyListView, _
11: ByVal vocabularyController As IVocabularyController)
12: MyBase.New(listView)
13: Arg.NotNull("vocabularyController", vocabularyController)
14:
15: _VocabularyController = vocabularyController
16:
17: AddHandler View.AddVocabulary, AddressOf AddVocabulary
18:
19: End Sub
20:
21: Protected Overrides Sub OnInit()
22: MyBase.OnInit()
23:
24: View.Model.Vocabularies = (From v In _VocabularyController.GetVocabularies() _
25: Where v.ScopeType.ScopeType = "Application" _
26: OrElse v.ScopeType.ScopeType = "Portal" _
27: AndAlso v.ScopeId = PortalId _
28: Select v) _
29: .ToList()
30:
31: View.Model.IsEditable = IsEditable
32: View.Model.NavigateUrlFormatString = NavigateURL(TabId, _
33: "EditVocabulary", _
34: String.Format("mid={0}", ModuleId), _
35: "VocabularyId={0}")
36: End Sub
37:
38: Protected Overrides Sub OnLoad()
39: MyBase.OnLoad()
40:
41: View.ShowAddButton(IsEditable)
42: End Sub
43:
44: Public Sub AddVocabulary(ByVal sender As Object, ByVal e As EventArgs)
45: Response.Redirect(NavigateURL(TabId, _
46: "CreateVocabulary", _
47: String.Format("mid={0}", ModuleId)))
48: End Sub
49:
50: End Class
|
The first constructor is required by the "WebFormsMvp” framework as the framework uses this constructor to bind the View and the Presenter together. The presenter class uses an IVocabularyController instance to work with the business layer (API), and the second constructor allows us to pass an arbitrary IVocabularyController instance - for example, when testing.
If we look back at Listing 1, the IVocabularyListView Interface has a single method – ShowAddButton - and a single Event – AddVocabulary. We “wire up” the event to a method in our Presenter in Line 17.
Both the OnInit and OnLoad methods override methods in the base class. These methods are “wired" up” in the base class to respond to the OnLoad and OnInit methods of the Page Lifecycle.
In OnInit we set all of the properties of the Model and in OnLoad we call the ShowAddButton of the View Interface to display the “Add Vocabulary” button.
Finally in the AddVocabulary EventHandler we can redirect control to the “CreateVocabulary” control.
Experienced Module Developers will notice that the code we have in the VocabularyListPresenter is essentially the same code we would normally create in the code-behind file of a standard ASP.NET User Control.
The advantage of placing the code in the presenter class is that we can test it. I will go into the testing aspects in a future blog, but first lets complete the review of the pattern by reviewing the concrete View class.
VocabularyList Class
The code behind file for the VocabularyList User Control is shown in Listing 5.
Listing 5: The VocabularyList Class
|
1: GetType(VocabularyListPresenter))> _
2: Partial Public Class VocabularyList
3: Inherits ModuleView(Of VocabularyListModel)
4: Implements IVocabularyListView
5:
6: Public Event AddVocabulary(ByVal sender As Object, ByVal e As EventArgs) _
7: Implements IVocabularyListView.AddVocabulary
8:
9: Public Sub ShowAddButton(ByVal showButton As Boolean) _
10: Implements IVocabularyListView.ShowAddButton
11: addVocabularyButton.Visible = showButton
12: End Sub
13:
14: Private Sub addVocabularyButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
15: Handles addVocabularyButton.Click
16: RaiseEvent AddVocabulary(Me, e)
17: End Sub
18:
19: Private Sub vocabulariesGrid_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) _
20: Handles vocabulariesGrid.PreRender
21: Dim hyperlinkColumn As DnnGridHyperlinkColumn = TryCast(vocabulariesGrid.Columns(0), _
22: DnnGridHyperlinkColumn)
23: If hyperlinkColumn IsNot Nothing Then
24: hyperlinkColumn.Visible = Model.IsEditable
25: hyperlinkColumn.DataNavigateUrlFormatString = Model.NavigateUrlFormatString
26: End If
27:
28: End Sub
29: End Class
|
Lines 1-4 are the most important part and they are required for the WebFormsMVP framework to “wire-up” the view to the presenter. The PresenterBinding attribute tells the framework that this view should be bound to the VocabularyListPresenter presenter. The View must inherit from the ModuleView(Of VocabularyListModel) base class and it must implement the IVocabularyListView interface.
Line 6 implements the Event AddVocabulary defined in the IVocabularyListView interface, and lines 9-12 implement the ShowAddButton method of the interface.
We try and keep the code in this class to a minimum as it is not testable, so the ShowAddButton method simply sets the visibility of the addVocabularyButton button.
Lines 14-17 handle the click event of the addVocabularyButton button and it in turn raises the AddVocabulary Event. Our presenter is already setup to handle this event.
Finally in lines 19-28, we handle the PreRender event of the grid. In this method we set two properties of the hyperlink Column depending on values of the properties of the Model.
This blog was just an overview of the important classes of the MVP implementation in DNN 5.3/4. In future blogs I will dig deeper into the base classes and what they offer module developers, and show how you can write tests for your presenters.