Click here to Skip to main content
Click here to Skip to main content

Rolling Your Own MVC with ASP.NET 2.0 Web Forms

, 25 Mar 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Rolling your own MVC with ASP.NET 2.0 Web Forms

Introduction

If you are an experienced Microsoft ASP.NET Web Forms developer and you have been keeping up with the latest news and blogs from the Microsoft community, then you have probably heard about the Microsoft ASP.NET MVC Framework just recently released to the web.

One of the promises of the MVC Framework, among other value-added features, is that it will provide a stronger separation between the different layers of a web application. The MVC Framework does this by completely separating the web page (called “The View” in MVC terminology) from the code-behind file (now called “The Controller”). The back-end business and data layers separate into the “The Model”, hence the name MVC. This enhanced separation of concerns improves and facilitates automated Unit Testing of ASP.NET web applications.

As I have been anticipating the just released version 1.0 of the MVC Framework, and looking forward to learning and using this new framework, an idea came to mind for incorporating the current and very popular ASP.NET 2.0 Web Forms framework. My idea was to try and simulate the MVC design pattern into a current ASP.NET Web Forms application.

Sample Application

The sample application in this article is derived from my previous CodeProject article, “Developing and Testing a WPF Application Using Routed UI Commands and MbUnit”. In that article, I separated the different layers of a WPF desktop application and Unit Tested it using the MbUnit Framework automated testing tool.

This sample application is a simple ASP.NET 2.0 Web Forms application that allows the user to enter, update, and save patient information. The information entered will pass through the different layers of a typical n-tier web application, and the data will be saved into a Microsoft SQL-Server Express 2005 database.

PatientEntry_new.jpg

For this application, I created several separate projects and included them into a single Visual Studio 2008 solution. The first project I created was the myMVCWebsite ASP.NET web project, which will contain and run the PatientEntry.aspx web form.

After laying out and designing the Patient Entry web form, I immediately proceeded to create a separate myMVCController class project. Basically, my goal was to take all the logic that would have gone into the code-behind file, PatientEntry.aspx.cs, and move it into the myMVCController class. This way, later on, I could Unit Test the controller class logic with a testing framework.

Moving the logic out of the code-behind file required the passing of the Page object of the ASP.NET web form to the controller class. The ASP.NET runtime engine builds the page object through the initial phases of the page life cycle, prior to executing the code-behind code for the web form. Once the code-behind code executes, the page object will already contain all the ASP.NET web controls that were defined in the PatientEntry.aspx file.

When the Page_Load event fires in the code-behind for PatientEntry.aspx, I create the controller object and pass the page object through the controller’s constructor. Each button click event on the form will call a method in the controller. The code-behind file basically becomes a pass-through mechanism to the controller.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using myMvcController;
public partial class _PatientEntry : System.Web.UI.Page 
{
    myMvcController.Controller _controller;

    /// <summary>
    /// Page Load
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Page_Load(object sender, EventArgs e)
    {
        _controller = new myMvcController.Controller(this);
        if (Page.IsPostBack == false)
            _controller.Initial_Page_Load();
    }
    /// <summary>
    /// Save Patient
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void btnSave_Click(object sender, EventArgs e)
    {
        _controller.btnSave_Click();              
    }
    /// <summary>
    /// Patient ID has changed
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void txtPatientID_TextChanged(object sender, EventArgs e)
    {
        _controller.GetPatientInformation();        
    }
    /// <summary>
    /// Reset Screen
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void btnReset_Click(object sender, EventArgs e)
    {
        _controller.btnReset_Click();
    }
}

As you can see in the code-behind file above, there is very little code. Except for the Page.IsPostBack statement, all the code will be handed off to the controller. I had to keep the Page.IsPostBack statement in the code-behind file because it is a read-only property and can only be set by the ASP.NET runtime and not by the code.

Other items that should remain in the code-behind file include references to the ASP.NET intrinsic objects such as Request, Response, Server, Application, and Session. If the web application needs to access these objects, then they should be passed to the controller as parameters when executing methods in the controller. This way, the controller can be tested without a web server, since these objects may not be available when testing the controller in a separate testing tool.

The Controller

Now, it’s on to the controller class. I created the controller class in a separate project called myMVCController. I did this so I could test the controller in a testing tool outside of the ASP.NET runtime.

In addition, I could also perform these tests without the need for a web server. As stated before, the controller class gets instantiated from the Patient Entry code-behind, and the page object gets passed into the controller through the controller’s constructor.

The key piece of code required in the controller was accessing the objects inside the web form. Since all the web form objects are part of the page object that was passed into the controller, all that was needed was to call the FindControl method of the page object. Doing this allowed the controller to access and update all the labels and textboxes on the web form.

/// <summary>
/// Load Controls
/// </summary>
private void LoadControls()
{
    _txtPatientID = (TextBox)_page.FindControl("txtPatientID");
    _txtFirstName = (TextBox)_page.FindControl("txtFirstName");
    _txtLastName = (TextBox)_page.FindControl("txtLastName");
    _txtAddressLine1 = (TextBox)_page.FindControl("txtAddressLine1");
    _txtAddressLine2 = (TextBox)_page.FindControl("txtAddressLine2");
    _txtCity = (TextBox)_page.FindControl("txtCity");
    _txtState = (TextBox)_page.FindControl("txtState");
    _txtZipCode = (TextBox)_page.FindControl("txtZipCode");
    _txtDateOfBirth = (TextBox)_page.FindControl("txtDateOfBirth");
    _lblMessage = (Label)_page.FindControl("lblMessage");
    _txtPatientIdentity = (HiddenField)_page.FindControl("txtPatientIdentity");
}

After creating the textbox objects, the controller then loads the patient information into a patient object and simply passes the information off to the business tier (often called the “The Model” in the MVC world).

/// <summary
/// Save Patient Information
/// </summary>
public void btnSave_Click()
{ 
   long patientIdentity;
   string validationMessage; 
   bool returnStatus; 
   long patientIdentity;
   string validationMessage;    
   bool returnStatus;
   LoadControls();
   myMvcPatient.Patient patient = new myMvcPatient.Patient();
   patient.PatientIdentity = Convert.ToInt64(_txtPatientIdentity.Value);
   patient.PatientID = _txtPatientID.Text.ToUpper();
   patient.FirstName = _txtFirstName.Text.ToUpper();
   patient.LastName = _txtLastName.Text.ToUpper();
   patient.AddressLine1 = _txtAddressLine1.Text.ToUpper();
   patient.AddressLine2 = _txtAddressLine2.Text.ToUpper();
   patient.City = _txtCity.Text.ToUpper();
   patient.State = _txtState.Text.ToUpper();
   patient.ZipCode = _txtZipCode.Text.ToUpper();
   patient.DateOfBirth = _txtDateOfBirth.Text.ToUpper();

   myMvcBusinessLayer.BusinessLayer business = 
                      new myMvcBusinessLayer.BusinessLayer();

   _lblMessage.Visible = true; 
   returnStatus = business.SavePatient(patient, out patientIdentity, 
                                                out validationMessage);
   if (returnStatus == true)
   {
      _lblMessage.Text = "Patient Successfully Added"; 
   }
   else
   { 
     _lblMessage.Text = validationMessage;
   }
   _txtPatientIdentity.Value = patientIdentity.ToString();
}

The Business and Data Tiers - The Model

The business and data tiers were also created in separate Visual Studio projects, myMVCBusinessLayer and myMVCDataLayer, respectively. Doing this allows me to test these layers separately and independently from the rest of the application.

Unit Testing with Gallio and MbUnit

In the real world, Unit Testing would be performed in parallel with development (and if you were following test driven development practices, you would be creating your Unit Tests before developing your application code). However, since this is a demonstration, I will discuss my Unit Testing approach for this sample application here.

The first step to testing my web application was to determine how it should be tested. Searching the Internet, I found many ways people were testing their web applications. Some people were testing their application by directly invoking the web page itself through several different testing tools by simulating click events etc. After spinning my wheels and consuming a lot of research time, I came to a conclusion. I decided to keep it simple, and to create some web form objects programmatically in a Unit-Testing tool.

To Unit Test this application, I downloaded the Gallio Automation Platform from http://www.gallio.org. The Gallio Automation Platform is an open, extensible, and neutral system for .NET that provides a common object model, runtime services, and tools (such as test runners) that may be leveraged by any number of test frameworks.

For my tests, I’ll be using MbUnit v3.0.5, which is the latest version of MbUnit, and comes packaged with Gallio.

To begin Unit Testing, I created a class project in Visual Studio, and called the project myMVCUnitTesting and started coding my Unit Tests. The initial code I needed to write was to create an HtmlForm object that contains all objects that exist on the PatientEntry web form that needed to be tested.

/// <summary>
/// Generate Html Form
/// </summary>
/// <returns></returns>
private HtmlForm GenerateHtmlForm()
{ 
    HtmlForm form = new HtmlForm();
    form.ID = "myTest";

    _txtAddressLine1 = new CustomTextBox();
    _txtAddressLine1.ID = "txtAddressLine1";
    _txtAddressLine2 = new CustomTextBox();
    _txtAddressLine2.ID = "txtAddressLine2"; 
    _txtCity = new CustomTextBox();
    _txtCity.ID = "txtCity";
    _txtDateOfBirth = new CustomTextBox();
    _txtDateOfBirth.ID = "txtDateOfBirth"; 
    _txtFirstName = new CustomTextBox(); 
    _txtFirstName.ID = "txtFirstName";
    _txtLastName = new CustomTextBox();
    _txtLastName.ID = "txtLastName"; 
    _txtPatientID = new CustomTextBox(); 
    _txtPatientID.ID = "txtPatientID";
    _txtPatientIdentity = new HiddenField(); 
    _txtPatientIdentity.ID = "txtPatientIdentity";
    _txtState = new CustomTextBox();
    _txtState.ID = "txtState";
    _txtZipCode = new CustomTextBox();
    _txtZipCode.ID = "txtZipCode";
    _lblMessage = new Label();
    _lblMessage.ID = "lblMessage";

    form.Controls.Add(_txtAddressLine1);
    form.Controls.Add(_txtAddressLine2);
    form.Controls.Add(_txtCity);
    form.Controls.Add(_txtState);
    form.Controls.Add(_txtZipCode);
    form.Controls.Add(_txtDateOfBirth);
    form.Controls.Add(_txtFirstName);
    form.Controls.Add(_txtLastName);
    form.Controls.Add(_txtPatientID);
    form.Controls.Add(_txtPatientIdentity);
    form.Controls.Add(_lblMessage);
    return form;
}

As you may have noticed, in the above example, I created the text boxes as CustomTextBox objects. Initially, I created these controls as regular textbox controls, but when I started testing the controller class, the Focus methods would fail when the controller tried setting focus to a textbox. This occurred because the Focus method is only supported when running inside the ASP.NET runtime engine.

The solution to solving this problem was to create my own custom textbox object within my test project, inherit from the standard TextBox control, and override and suppress the Focus method all together. I did not need to test for focus, as this is more of a visual test.

/// <summary>
/// Create a custom Textbox that suppresses the Focus Method
/// </summary>
public class CustomTextBox : TextBox
{ 
    /// <summary>
    /// Suppress the Focus method 
    /// </summary>
    public override void Focus()
    {
        // The Focus method is suppressed in the Unit Test
        // because we are not creating a Page object
        // from the ASP.NET Runtime.
    }
}

To test the web form’s main functionality (which is to create a patient record), I created a test method called Create_A_Patient. In this method, I create a page object, generated my HtmlForm, and added the form to the page object. The page object is passed to the controller upon creation of the controller object. The Unit Test then proceeds to find the controls in the page object, and then populates the controls with patient information.

The Unit Test then calls the controller’s btnSave_Click() method and executes MbUnit’s Assert method to validate that the test was successful.

/// <summary>
/// Test Save A Patient
/// </summary>
[Test]
public void Create_A_Patient()
{
    long patientIdentity;
    Page testPage = new Page();
    testPage.Controls.Add(GenerateHtmlForm());
    myMvcController.Controller controller = 
             new myMvcController.Controller(testPage);

    //
    // initialize result message
    //
    _lblMessage = (Label)testPage.FindControl("lblMessage");
    _lblMessage.Text = "";
    //
    // Create Textbox objects
    //
    _txtPatientIdentity = 
          (HiddenField)testPage.FindControl("txtPatientIdentity");
    _txtPatientID = (TextBox)testPage.FindControl("txtPatientID");
    _txtLastName = (TextBox)testPage.FindControl("txtLastName");
    _txtFirstName = (TextBox)testPage.FindControl("txtFirstName");
    _txtDateOfBirth = (TextBox)testPage.FindControl("txtDateOfBirth");
    _txtAddressLine1 = (TextBox)testPage.FindControl("txtAddressLine1");
    _txtAddressLine2 = (TextBox)testPage.FindControl("txtAddressLine2");
    _txtCity = (TextBox)testPage.FindControl("txtCity");
    _txtState = (TextBox)testPage.FindControl("txtState");
    _txtZipCode = (TextBox)testPage.FindControl("txtZipCode");
    //
    // set up patient information
    //
    _txtPatientIdentity.Value = "";
    _txtPatientID.Text = "MSCEO";
    _txtLastName.Text = "Gates III";
    _txtFirstName.Text = "William Henry";
    _txtDateOfBirth.Text = "10/28/1955";
    _txtAddressLine1.Text = "Microsoft Corporation";
    _txtAddressLine2.Text = "One Microsoft Way";
    _txtCity.Text = "Redmond";
    _txtState.Text = "WA";
    _txtZipCode.Text = "98052-7329";
    //
    // Save patient information
    //
    controller.btnSave_Click();
    //
    // Test results
    //
    _lblMessage = (Label)testPage.FindControl("lblMessage");
    _txtPatientIdentity = (HiddenField)testPage.FindControl("txtPatientIdentity"); 
    patientIdentity = Convert.ToInt64(_txtPatientIdentity.Value);
    TestLog.WriteLine("sql server identity value = " + patientIdentity.ToString());
    //
    // Assertions
    //
    Assert.AreEqual("Patient Successfully Added",_lblMessage.Text );
    Assert.GreaterThan(patientIdentity, 0);
}

After coding the remainder of my tests, I proceeded to load my test assembly into Gallio’s Incarus GUI Test Runner and executed the Unit-Tests.

testing4.jpg

Houston, we have success! My Unit Tests executed successfully.

*Note: Prior to running the tests, I had to create an app.config file in my Unit Test project and add the connection string to connect to the SQL Server 2005 Express database.

This is a small example of Unit Testing. Trying to develop good Unit Testing techniques is an art all by itself. One does not really appreciate automated Unit Testing until his/her application starts to grow legs and becomes a monster or a killer application. Regression testing a monster application without automated testing is almost impossible. Having it in place will save you time and help prevent bugs from making their way into production code.

Conclusion

This article was a simple example of applying the MVC design pattern into the current ASP.NET Web Forms framework. Moving forward, I will be investigating and learning about the ASP.NET MVC framework for developing web applications. It will be interesting to see its adoption in the marketplace. At a minimum, it will have several niche markets, especially for those who are practicing test driven development, or for those who want to control what gets sent down the wire to the user’s browser from their web application. It is well known by now that the HTML generated by ASP.NET is very bloated. For the time being, the current ASP.NET Web Forms framework is still very popular, successful, and will be around for some time. It is today’s COBOL of the Microsoft web development world.

History

  • 24th March, 2009: Initial version
  • 24th March, 2009: Added source code and modified image

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mark J. Caplin
Software Developer Caplin Systems
United States United States
Mark Caplin has specialized in Information Technology solutions for 25 years. Specializing in full life-cycle development projects for both enterprise-wide systems and Internet/Intranet based solutions.
 
For the past ten years or so, Mark has specialized in the Microsoft .NET framework using both C# and VB.NET as his tools of choice.
 
Mark currently works for e-Builder Inc, www.e-builder.net. e-Builder is a SaaS software company specializing in Construction Program Management Software. If you are a talented Microsoft .NET developer and you are looking for a new opportunity with a rapidly growing company, please send me your resume.
 
When not coding, Mark enjoys playing tennis, listening to U2 music, watching Miami Dolphins football and watching movies in Blu-Ray technology.
 
In between all this, his wife of over 20 years, feeds him well with some great home cooked meals.
 
...

Comments and Discussions

 
GeneralGood One 4 PinmemberShemeemsha RA2-Oct-14 20:23 
GeneralMy vote of 2 PinmemberGilesHinton10-Feb-10 4:00 
GeneralMy vote of 2 PinmemberJ Xie12-Oct-09 5:28 
GeneralHey i do similar work PinmemberLeblanc Meneses31-Mar-09 19:34 
GeneralRe: Hey i do similar work PinmemberMark J. Caplin1-Apr-09 5:03 
GeneralWe can go further ... PinmemberJeremy Likness31-Mar-09 3:50 
GeneralRe: We can go further ... PinmemberMark J. Caplin31-Mar-09 4:26 
GeneralRe: We can go further ... PinmemberLeblanc Meneses31-Mar-09 19:44 
QuestionHave you considered things like the following? PinmemberJeff Lindholm24-Mar-09 10:34 
AnswerRe: Have you considered things like the following? PinmemberMark J. Caplin24-Mar-09 11:00 
GeneralCheck out Dynamic Data Pinmemberdeloford23-Mar-09 23:18 
GeneralRe: Check out Dynamic Data PinmemberMark J. Caplin24-Mar-09 4:29 
GeneralMVC has already been released Pinmemberzlezj23-Mar-09 22:47 
GeneralRe: MVC has already been released PinmemberMark J. Caplin24-Mar-09 4:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141220.1 | Last Updated 25 Mar 2009
Article Copyright 2009 by Mark J. Caplin
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid