Posted by & filed under AthleticHost, Web Development.

I have been looking for a good framework that would encourage good development practices through separation of concerns and testability in my DotNetNuke modules. I was listening to Scott Hanselman’s Hanselminutes Podcast #202 and discovered Web Forms MVPTatham Oddie and Damian Edwards have developed a framework for those us of us who are using web form’s in our projects.

Web Forms MVP provides some interesting features.  A few of the features include:

  • Composite views
  • Asynchronous tasks
  • Shared presenters
  • Presenter messaging
  • Works with HttpHandlers and web services.
  • Custom presenter factories to allow IOC integration.
    To start you will need Web Forms MVP CTP 7 if you are going to be developing for DotNetNuke.   I intially struggled with CTP 6 and through Tatham’s quick responses on the Web Forms MVP Google Group found there was a fix for this in CTP 7.  CTP 7 should be released soon, however, it looks like you should be able to build it from the source until then.

The power of the MVP pattern allows for us to have clean markup in our user control and the ability to isolate and test the logic that drives the form.  Web Forms MVP uses the supervising presenter pattern with the model loosely following the presentation model pattern.

    The following example is a  simple ‘contact us’ form.   This first pass of the contact form will just display a friendly ‘thank you’ message.  Later posts will include persistence and notification features for the contact form. 
image image

Since the view doesn’t contain any logic it can be laid out using embedded code blocks (<%= Model.FirstName %>)

<%
    panelContactUs.Visible = Model.ShowForm;
    panelMessage.Visible = !Model.ShowForm; 
%>        
<fieldset id="panelContactUs" runat="server" visible="true" class="cssform">
    <legend>Please enter your information and we will contact you soon.</legend>
    <p>
        <asp:Label ID="lblFirstName" runat="server" Text="First name:" AssociatedControlID="txtFirstName"></asp:Label>
        <asp:TextBox ID="txtFirstName" runat="server"></asp:TextBox>
    </p>
    <p>
        <asp:Label ID="lblLastName" runat="server" Text="Last name:" AssociatedControlID="txtLastName"></asp:Label>
        <asp:TextBox ID="txtLastName" runat="server"></asp:TextBox>  
    </p>
    <div style="margin-left: 150px;">
        <asp:Button ID="btnSubmit" runat="server" Text="Submit" onclick="btnSubmit_Click" />
    </div>  
</fieldset>

<div id="panelMessage" runat="server">
    <p>Dear <%=Model.FirstName %> <%=Model.LastName %>,</p>
    <p>
    Thank you for your interest in AthleticHost.  We are looking forward to working with 
    you to bring your athletic department online.  
    </p>
    <p>
    We will be contacting you soon to discuss your the specific needs of your athletic program.  
    </p>
    <p>
    Please feel free to contact us at <a href="mailto:support@athletichost.com">support@athletichost.com</a>
    </p>
    <p>
    Sincerely,<br />
    Aaron Jackson<br />
    Athletic Host
    </p>
</div>

The code behind for the control is only responsible for binding the view to the presenter and passing along the events that occur in the view, so that the presenter can react to them.  Here is the code behind for the ViewContactUs control.

[PresenterBinding(typeof(ContactUsPresenter))]
partial class ViewContactUs : PortalModuleBase, IActionable, IContactUsView
{
    protected override void OnInit(EventArgs e)
    {
        PageViewHost.Register(this, Context);
        base.OnInit(e);
    }
    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        OnSubmit(txtFirstName.Text, txtLastName.Text);
    }

    public event EventHandler<SubmitContactUsEventArgs> SubmitContactUs;
    private void OnSubmit(string firstName, string lastName)
    {
        if (SubmitContactUs != null)
        {
            SubmitContactUs(this, new SubmitContactUsEventArgs
                                      {
                                          FirstName = firstName, 
                                          LastName = lastName
                                      });
        }
    }

    private ContactUsModel model;
    public ContactUsModel Model
    {
        get
        {
            if (model == null)
                throw new InvalidOperationException("The Model property is currently null, however it should have been automatically initialized by the presenter. This most likely indicates that no presenter was bound to the control. Check your presenter bindings.");

            return model;
        }
        set
        {
            model = value;
        }
    }
}

Binding the view to the presenter is normally handled by inheriting from MvpUserControl, however the ViewContactUs control needs to work with DNN.  DNN modules need to inherit from  DNN’s PortalModuleBase and since multiple inheritance is not possible in C# we have a problem.   Thankfully we can override the OnInit method and register our view with a single line of code.

protected override void OnInit(EventArgs e)
{
    PageViewHost.Register(this, Context);
    base.OnInit(e);
}

Views that don’t have any events would be complete at this point.  Views that react to events, such as, the click event on submit button in ContactUs need to be passed to the presenter.    The presenter is responsible for subscribing to the view events when it is bound to the view.  Look at the constructor of the ContactUsPresenter below.  You can see the SubmitContactUs event on the view being subscribed to by the ContactUsPresenter. 

public class ContactUsPresenter : Presenter<IContactUsView>
{
    public ContactUsPresenter(IContactUsView view) 
        : base(view)
    {
        View.SubmitContactUs += View_SubmitContactUs;
        view.Model.ShowForm = true;
        view.Model.Message = string.Empty;
    }

    public override void ReleaseView()
    {
        View.SubmitContactUs -= View_SubmitContactUs;
    }

    void View_SubmitContactUs(object sender, SubmitContactUsEventArgs e)
    {
        View.Model.FirstName = e.FirstName;
        View.Model.LastName = e.LastName;
        View.Model.Message = e.FirstName + " " + e.LastName;
        View.Model.ShowForm = false;
    }
}

Finally, a look at the unit test that was written for the presenter.  I choose to use xUnit and Moq to handle the testing.    Since the view implements the IContactUsView interface the unit test can mock the view and allow for the presenter to be tested without dependencies on DNN.   In fact, this presenter could be reused on any other web forms project.

[Fact]
public void ContactUsPresenter_Sets_Message_OnSubmit()
{
    // Arrange
    var view = new Mock<IContactUsView>();
    view.SetupAllProperties();
    var presenter = new ContactUsPresenter(view.Object);

    // Act
    view.Raise(v => v.Load += null, new EventArgs());
    view.Raise(v => v.SubmitContactUs += null, 
        new SubmitContactUsEventArgs("Chester", "Tester", 
            "ctester@test.com", "http://www.test.com", 
            "This is a test of the emergancy broadcast system..."));  
    presenter.ReleaseView();

    // Assert
    Assert.Contains("Chester Tester", view.Object.Model.Message);
    Assert.Equal("Chester", view.Object.Model.FirstName);
    Assert.Equal("Tester", view.Object.Model.LastName);
    Assert.False(view.Object.Model.ShowForm);
}

Download the ContactUs module source.

Trackbacks/Pingbacks

  1.  aaronkjackson.com » Building DotNetNuke Modules Using Web Forms MVP | DNN Blog

Leave a Reply

  • (will not be published)