09/02/2008

MVC with ASP.NET

Model view controller with ASP.NET

 

It’s now generally admitted in the community that Unit testing and TDD (Test Driven Development) are valuable techniques when it comes to increasing the overall quality of our code. Nevertheless unit testing can be costly especially when you’ve applications with a lot of logic implemented in the UI. 

 

Therefore if we want to make our application testable we need to separates the UI from the rest of the application.  Martin Fowler described on his site some patterns that separate and diminish the UI logic to a bare minimum.  They are all variants of the classical MVC (Model View Controller) pattern.  The MVC split the application in 3 parts: the view handles the display, the controller that responds to user gestures and the model that contains the domain logic. 

 

The MVC is the foundation of very popular portal frameworks like Ruby on Rails.  To build web sites applying the MVC pattern with .Net developers can choose among several MVC frameworks like Monorail.  In anyway, MVC frameworks like Monorail are based on completely different paradigm as the ASP.NET framework.  This means that you have to re-learn to program web apps from scratch.  Another setback is that there are no ways to refactor your old ASP.NET applications so that they can fit into the MVC framework.

 

I want to make myself clear, I believe that frameworks like Monorail or the coming System.Web.MVC are the future way of programming web apps in .NET but it demands a considerable amount of effort to learn new frameworks. It’s difficult for someone like me who has invested lots of years in mastering the classical ASP.NET code-behind model to re-learn everything from scratch. In the meantime this should not be an excuse to not make my code more testable.

 

In this post I will explicit through a simple example how to use the model view controller pattern on top of the code-behind model. 

 

We will create a login form with the MVC pattern.

 

Setup your solution

Create a new solution “Zoo” with 3 projects ->

-         ZooWebsite-> ASP.NET web appplication

-         ZooLibrary  -> Class library

-          ZooTest  - Class library

 

- Create a reference from ZooWebsite to ZooLibrary

(ZooWebsite , add reference, project tab select ZooLibrary)

- On ZooLibrary add a reference to System.Web

 

 

The View

To make our code testable it’s very important to be able to decouple the UI from the ASP.NET code-behind.  Therefore we will create an interface our ASP.NET page should implement.  This View interface will represent the contract the UI as to conform to.  When we will test our controller we will not do this with our actual web page but through a mock object that implements the View interface.    

 

Add an interface ILoginView on the project ZooLibrary:

 

namespace ZooApplication.Library

{

     public interface ILoginView

    {

        string ErrorMessage { get;set;}

        string EmailAddress { get;set;}

        string Password { get;set;}

        void RedirectFromLoginPage();

        System.Web.UI.WebControls.Button BtnLogin { get;set;}

    }

}

-> Edit the default aspx page and enter: Welcome you are authenticated!

-> Add the login.aspx to the ZooWebsite project.

 -> Edit the source of the login.aspx part -> add two textboxes, a button, and validators:

 

<%@ Page Language="C#" AutoEventWireup="true" Codebehind="Login.aspx.cs" Inherits="ZooApplication.Website.Login" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Login page</title>

</head>

<body>

    <form id="form1" runat="server">

        <div>

                Login form<br />

                <asp:Label ID="LblErrorMsg" runat="server" Text="Invalid login" Visible="false" ></asp:Label><br />

                Email Address:

                <asp:TextBox ID="TxbEmailAddress" runat="server"></asp:TextBox>

                <asp:RequiredFieldValidator ID="RfvEmailAddress" runat="server" ErrorMessage="Enter your email address!"

                    ControlToValidate="TxbEmailAddress">

                </asp:RequiredFieldValidator>

                <asp:RegularExpressionValidator ID="RevEmailAddress" runat="server" ControlToValidate="TxbEmailAddress"

                    ErrorMessage="Invalid email address!" ValidationExpression="w+([-+.']w+)*@w+([-.]w+)*.w+([-.]w+)*">

                </asp:RegularExpressionValidator></div>

            <div>

                Password:

                <asp:TextBox ID="TxbPassword" runat="server"></asp:TextBox>

                <asp:RequiredFieldValidator ID="RfvPassword" runat="server" ErrorMessage="Enter your password!"

                    ControlToValidate="TxbPassword">

                </asp:RequiredFieldValidator>

            </div>

            <div>

                <asp:Button ID="PageBtnLogin" runat="server" Text="Login" />

            </div>

        </div>

    </form>

</body>

</html>

 

 Because the code-behind is not testable and don’t make part of the SUT (Subject Under Test) we want to diminish the code-behind logic to a bare minimum.   The view responsibility is limited to output  the data coming from our model in a human readable way and to expose user input to the controller. 

Therefore we implement our interface through an aspx page that only contains a set of properties that binds data coming from the controller with our web controls. 

Generally we will try to implement all the presentation logic into the controller.  The only exception here will be the RedirectFromLoginPage() method. 

We could use a provider pattern to abstract this from the page but for the sake of simplicity I’ve decided to implement it directly in the page. 

 

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using ZooApplication.Library;

 

namespace ZooApplication.Website

{

    public partial class Login : System.Web.UI.