Introduction
One of the challenges of learning new software development technologies is finding a good working example that can guide you through the learning process. Most demonstrations I’ve seen through the years often show you a working example that front-ends the world famous Northwind demo database. Often, these demonstration applications show you a master-detail sales order GUI that uses a layer or two of plug-and-play plumbing. I’ve never really learned anything looking at these applications. What is really needed is something closer to a real world application that has just enough meat and potatoes to facilitate the learning process.Sample Application
Solution and Projects
This sample application consists of four separate Visual Studio 2010 projects that are combined into one solution as follows:MvcWebSite– This is the MVC web project that contains the Views and the Controllers. The Views will contain both JavaScript and HTML, and the Controllers will interact with the Data Model and the business logic layer.DataModels- TheDataModelsproject consists of the data entities that this application requires. The data model is separated into its own library so it may be shared across the enterprise.CodeProjectBLL– TheCodeProjectBLLDLL class library will contain all the necessary business logic layer and data access code. This layer is often called the Model.CodeProjectUnitTests– This DLL class library will contain the unit tests that will be used for testing the application with the NUnit test runner.
The Data Model
Application development usually starts with defining your data entities and designing a database. For this sample application, the main data entity is patient information. After building the database tables for this application, the following class was created that exposes the underlying data model:using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DataModels
{
/// <summary>
/// Patient Data Model
/// </summary>
public class Patient
{
public long PatientID { get; set; }
public string MedicalID { get; set; }
public string SocialSecurityNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public DateTime EffectiveDate { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string PhoneNumber { get; set; }
}
}
With the MVC framework as with other frameworks, it is tempting to just add your data model class under the Models
folder of the MVC project. In the corporate world, you often have to
develop and support more than one application that supports and shares
the same data and accompanying data structures. For this reason, I like
to create a separate project for all my data classes as a DLL library
project so these classes can be shared across different applications
across the corporate enterprise.
The View - Patient Maintenance and Search
At this point, I will walk through adding a patient from the Patient Maintenance View. PatientMaintenance.aspx is located in a folder under Views called Patient. Adding a new patient will require the user to enter a patient first name and last name, date of birth, a medical id, etc. Once this information is entered, the user can press the SAVE button.<h3>Patient Information</h3>
<form method="post" action="./" id="PatientMaintenanceForm">
<table style="background-color: #ebeff2; width: 100%; border:solid 1px #9fb8e9"
cellpadding="2" cellspacing="2">
<tr><td> </td></tr>
<tr>
<td>Medical ID:</td>
<td style="width:200px"><input name="MedicalID" type="text" id="MedicalID" /></td>
<td>Effective Date:</td><td>
<input name="EffectiveDate" type="text" id="EffectiveDate"
style="margin:0px 5px 0px 0px" />
</td><td style="width:40%"> </td></tr>
<tr>
<td>First Name:</td>
<td style="width:200px"><input name="FirstName" type="text" id="FirstName" /></td>
<td>Last Name:</td>
<td><input name="LastName" type="text" id="LastName" /></td>
<td style="width:40%"> </td>
</tr>
<tr>
<td>SSN:</td>
<td style="width:200px">
<input name="SocialSecurityNumber" type="text" id="SocialSecurityNumber" /></td>
<td>Date Of Birth:</td>
<td><input name="DateOfBirth" type="text" id="DateOfBirth" />
</td>
</tr>
<tr>
<td>Address Line 1:</td>
<td style="width:200px"><input name="AddressLine1" type="text" id="AddressLine1" />
</td>
<td>Address Line 2:</td>
<td><input name="AddressLine2" type="text" id="AddressLine2" /></td>
</tr>
<tr>
<td>City:</td>
<td><input name="City" type="text" id="City" /></td>
<td>State:</td>
<td>
<select id="State" name="State">
<option value=""></option>
</select>
</td>
</tr>
<tr>
<td>Zip Code:</td>
<td><input name="ZipCode" type="text" id="ZipCode" /></td>
<td>Phone Number:</td>
<td><input name="PhoneNumber" type="text" id="PhoneNumber" /></td>
</tr>
<tr>
<td> </td>
</tr>
</table>
<table style="background-color:#D3DCE5; margin-top:10px; width:100% ">
<tr>
<td>
<input id="btnSave" type="button" onclick="SavePatient();" value="Save" />
<input id="btnReset" type="button" value="Reset" onclick="ResetPage();" />
</td>
</tr>
</table>
<br />
<div id="DivMessage"> </div>
</form>
</div>
One of the nice features of MVC is that it provides a mechanism for
binding your HTML form fields to a business model object through an UpdateModel command
in the controller layer. In the above HTML patient form, I have given
each textbox an id and name that matches the properties of my patient
data model. When this form is submitted to the controller, the
controller will bind and map the values of the form to the patient
object. jQUERY
jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. JQuery also has various plug-ins and widgets to enhance the development of the web UI. This sample application uses two jQuery UI add-ons, the date picker and the masked input plug-in. I downloaded these two plug-ins from jQuery.com and made a reference to the script files in the master page of this application. JQuery also comes with custom themes for look and feel. For the date picker, I chose to download the Pepper Grinder theme. The masked input plug-in allows you to format dates, phone number, social security numbers, etc. that are not available out of the box with MVC and standard HTML.<head runat="server">
<title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
link href="../../Content/css/PepperGrinder/jquery-ui-1.8.2.custom.css"
rel="stylesheet" type="text/css" />
The jQuery Date Picker: Submitting Data to the Server
When the user presses the Save button, theSavePatient JavaScript function gets executed in the browser. The SavePatient function
uses the jQuery serialize method to convert the values of the HTML form
fields into a notation that is based on the traditional HTML query
string notation. This string is sent to the server when executing an HTML POST.<script language="javascript" type="text/javascript">
function SavePatient() {
formData = $("#PatientMaintenanceForm").serialize();
DisableForm();
setTimeout(PostChangesToServer, 1000);
// simulate a delay to the server
}
function PostChangesToServer() {
$.post("/Patient/AddPatient", formData,
function (data, textStatus) {
PatientUpdateComplete(data);
}, "json");
}
</script>
Disabling the Form
function DisableForm() {
formIsDisabled = true;
$('#PatientSearchForm :input').attr('disabled', true);
$('#PatientSearchForm').css("cursor", "wait");
$('#PatientMaintenanceForm :input').attr('disabled', true);
$('#PatientMaintenanceForm').css("cursor", "wait");
}
To submit the form data to the server, the application will use
jQuery to execute an AJAX POST request to the server. Since the call to
the server is asynchronous, the user could still perform other functions
on this page during the submit request that may cause an undesirable
situation. To prevent this, the form needs to be disabled. Most web
sites often display a progress indicator to notify the user that the
application is busy and the form is grayed out. I often find this
approach less than idea and less esthetic in most cases. In this sample
application, a more traditional approach is taken that is often seen in
the desktop application world. The application simply changes the cursor
to an hourglass and disables the buttons and links on the page.AJAX POST TO SERVER
Once the form is disabled, the jQueryPOST method is executed. The POST method calls the server asynchronously with the supplied form field data. The MVC route of “/Patient/AddPatient” is referenced which is basically making a call to the PatientController class and executing the AddPatient method of the controller. When the request returns from the server, a JSON object is returned to the callback function “PatientUpdateComplete”. JSON is a lightweight format for exchanging data between the client and server. It is often used in Ajax applications because of its simplicity and because its format is based on JavaScript object literals.
AddPatient Controller Method
/// <summary>
/// Add Patient
/// </summary>
/// <returns></returns>
public JsonResult AddPatient()
{
bool returnStatus;
string returnErrorMessage;
List<string> returnMessage;
PatientBLL patientBLL = new PatientBLL();
Models.PatientViewModel patientViewModel = new Models.PatientViewModel();
this.TryUpdateModel(patientViewModel);
Patient patient = patientBLL.AddPatient(
patientViewModel,
out returnMessage,
out returnStatus,
out returnErrorMessage);
patientViewModel.UpdateViewModel(patient, typeof(Patient).GetProperties());
patientViewModel.ReturnMessage = returnMessage;
patientViewModel.ReturnStatus = returnStatus;
return Json(patientViewModel);
}
Another tempting thing to do with the MVC Framework is to put the
business logic and data access code directly in the controller class. I
strongly believe that all business logic and data access code should
reside in separate projects outside the MVC project. This facilitates
the sharing of application code across multiple business applications.
It is also my belief that the controller class should be nothing more
than a pass-through or a middleman between the View and the rest of the
application. The controller should just bind data from the View to a
business object and then execute the business logic layer code that
resides outside the MVC project in a class library.Patient View Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DataModels;
using System.Reflection;
using CodeProjectBLL;
namespace MVCWebSite.Models
{
public class PatientViewModel : DataModels.Patient
{
public long CurrentPageNumber { get; set; }
public long PageSize { get; set; }
public long TotalPages { get; set; }
public long TotalRows { get; set; }
public List<string> ReturnMessage { get; set; }
public bool ReturnStatus { get; set; }
public string SortBy { get; set; }
public string SortAscendingDescending { get; set; }
}
}
The first thing that the AddPatient method does is instantiate the business logic layer class and a PatientViewModel class. The PatientViewModel class inherits from the Patient Data
Model and includes additional properties that are needed for the View
including properties for returning status and messages from the server.Data Binding
The key line of code in the controller method is theTryUpdateModel statement. This statement will populate the properties in the PatientViewModel class with the values from the Patient Maintenance
View. It does this based on mapping the data elements by name. As
stated previously, the textbox objects in the View have the same
property names in the PatientViewModel.The
AddPatient Controller method then proceeds to call the patientBLL.AddPatient method
which inserts the patient information into a SQL Server database and
then returns a new patient object with some reformatted data. In this
case, the data entered is folded to uppercase, saved in the database and
then returned back to the controller.Update Patient View Model and Return JSON
When thepatientBLL.AddPatient has successfully executed, the controller executes the UpdateViewModel method. This is a custom method that uses .NET Reflection to update the properties in the PatientViewModel from the patient object. This functionality is similar to the TryUpdateModel command and basically maps and updates property values by name in the other direction.Finally, the
AddPatient controller method returns the PatientViewModel object
as a JSON object that can be parsed by the View. The nice thing is that
MVC automatically translates your class object to a JSON object.Update the View
<script language="javascript" type="text/javascript">
function PatientUpdateComplete(jsonPatient) {
EnableForm();
firstName = $("#PatientMaintenanceForm #FirstName");
firstName.scrollIntoView();
if (jsonPatient.ReturnStatus == true) {
$("#PatientMaintenanceForm #FirstName").val(jsonPatient.FirstName);
$("#PatientMaintenanceForm #LastName").val(jsonPatient.LastName);
$("#PatientMaintenanceForm #AddressLine1").val(jsonPatient.AddressLine1);
$("#PatientMaintenanceForm #AddressLine2").val(jsonPatient.AddressLine2);
$("#PatientMaintenanceForm #City").val(jsonPatient.City);
$("#PatientMaintenanceForm #State").val(jsonPatient.State);
$("#PatientMaintenanceForm #ZipCode").val(jsonPatient.ZipCode);
$("#PatientMaintenanceForm #PhoneNumber").val(jsonPatient.PhoneNumber);
$("#PatientMaintenanceForm #SocialSecurityNumber").val(
jsonPatient.SocialSecurityNumber);
$("#PatientMaintenanceForm #MedicalID").val(jsonPatient.MedicalID);
$("#PatientMaintenanceForm #DateOfBirth").val(
jsonPatient.PatientDateOfBirth);
$("#PatientMaintenanceForm #EffectiveDate").val(
jsonPatient.PatientEffectiveDate);
$("#PatientMaintenanceForm #PatientID").val(jsonPatient.PatientID);
}
var returnMessage = "";
for ( i=0; i<jsonPatient.ReturnMessage.length; i++ )
{
returnMessage = returnMessage + jsonPatient.ReturnMessage[i] + "<br>";
}
if (jsonPatient.ReturnStatus == true) {
$("#DivMessage").css("background-color", "#ebeff2");
$("#DivMessage").css("border", "solid 1px #9fb8c9");
$("#DivMessage").css("color", "#36597f");
$("#DivMessage").css("padding", "5 5 5 5");
}
else {
$("#DivMessage").css("background-color", "#f4eded");
$("#DivMessage").css("border", "solid 1px #d19090");
$("#DivMessage").css("color", "#762933");
$("#DivMessage").css("padding", "5 5 5 5");
}
$("#DivMessage").html(returnMessage);
}
As previously stated, the server returns a JSON object back to the JavaScript callback function PatientUpdateComplete.
In the example above, jQuery is used to parse the JSON object and
update the form fields in the View and checks and displays return status
information. Basically, this example made a round trip AJAX call using
jQuery. function EnableForm() {
formIsDisabled = false;
$('#PatientSearchForm :input').removeAttr('disabled');
$('#PatientSearchForm').css("cursor", "default");
$('#PatientMaintenanceForm :input').removeAttr('disabled');
$('#PatientMaintenanceForm').css("cursor", "default");
}
Finally, the last step is to enable the HTML form to allow the user to continue.
Unit Testing
One of the main areas that is cited as a benefit of using the new ASP.NET MVC Framework is that you can obtain a 'clean separation of concerns'. What this means is that your application code is now more testable compared to ASP.NET Web Forms. MVC controllers replace Web Form code-behind files. Since controllers are standard class objects, they can now be unit tested directly with a unit testing tool.To unit test this sample application, I downloaded three open source projects: NUnit, MVCContrib and Rhino Mocks:
- NUnit is an open source unit-testing framework for all .NET languages and can be downloaded from http://www.nunit.org.
- MVCContrib is also an open source project for the ASP.NET MVC
framework. This project adds additional functionality on top of the MVC
Framework. This sample application will use the
TestHelperlibrary for unit testing. Download it from http://mvccontrib.codeplex.com.
- Rhino Mocks is a dynamic mock object framework for the .NET
platform. Its purpose is to ease testing by allowing the developer to
create mock implementations of custom objects and verify the
interactions using unit testing. MVCContrib uses this framework when
using the
TestHelperlibrary. Download it from http://www.ayende.com/projects/rhino-mocks/downloads.aspx.
AddPatient Controller method, I created a method in my CodeProjectUnitTests project called Test_AddPatient and made a reference to the NUnit and MVCContrib frameworks.using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using MvcContrib.TestHelper;
using MVCWebSite;
using MVCWebSite.Models;
using MVCWebSite.Controllers;
using System.Web.Mvc;
using System.Collections.Specialized;
using System.Configuration;
using DataModels;
namespace CodeProjectUnitTests
{
/// <summary>
/// Unit Tests
/// </summary>
[TestFixture]
public class UnitTests
{
/// <summary>
/// Test Add Patient
/// </summary>
[Test]
public void Test_AddPatient()
{
TestControllerBuilder builder = new TestControllerBuilder();
string uniquePatientKey = GenerateUniqueID();
builder.Form["MedicalID"] = uniquePatientKey;
builder.Form["SocialSecurityNumber"] = uniquePatientKey;
builder.Form["FirstName"] = "William";
builder.Form["LastName"] = "Gates";
builder.Form["AddressLine1"] = "Microsoft Corporation";
builder.Form["AddressLine2"] = "One Microsoft Way";
builder.Form["City"] = "Redmond";
builder.Form["State"] = "WA";
builder.Form["ZipCode"] = "98052-7329";
builder.Form["PhoneNumber"] = "(425)882-8080";
builder.Form["DateOfBirth"] = "10/28/1955";
builder.Form["EffectiveDate"] = "01/01/1975";
PatientController patientController =
builder.CreateController<PatientController>();
JsonResult jsonResult = (JsonResult)patientController.AddPatient();
dynamic jsonData = jsonResult.Data;
Assert.AreEqual(jsonData.ReturnStatus, true);
Assert.Greater(jsonData.PatientID, 0);
}
}
}
The first thing that the test method above does is create a TestControllerBuilder object. This object is part of the MVCContrib library under the MvcContrib.TestHelper namespace. The TestControllerBuilder not only creates an instance of the controller, but it also mocks out the HttpContext, Session and form data. It also provides access to MVC ViewData, TempData, etc. The unit test above mocks form data, creates the controller, and executes the
AddPatient method of the Patient controller.
The controller returns JSON which can be inspected and tested by the
test method using NUnit assert commands. Since the properties of the
JSON object are anonymous at compile time, the test method declares the
data from the JSON object as dynamic as introduced in C#. This means I
can reference the properties but these properties will not be evaluated
or resolved until runtime execution.NUnit TEST RUNNER
Loading the
CodeProjectUnitTests assembly into the NUnit
test runner, you can now execute your unit tests running directly
against the MVC controller class outside and without IIS or the ASP.NET
development server running.Additional Functionality - Patient Searching
This sample application also includes a second HTML form demonstrating patient search functionality that returns and builds an HTML grid that includes support for paging and sorting./// <summary>
/// Patient Search
/// </summary>
/// <returns></returns>
public PartialViewResult PatientSearch()
{
long totalRows;
long totalPages;
bool returnStatus;
string returnErrorMessage;
PatientBLL patientBLL = new PatientBLL();
Models.PatientViewModel
patientViewModel = new Models.PatientViewModel();
this.UpdateModel(patientViewModel);
List<Patient> patients = patientBLL.PatientSearch(
patientViewModel,
patientViewModel.CurrentPageNumber,
patientViewModel.PageSize,
patientViewModel.SortBy,
patientViewModel.SortAscendingDescending,
out totalRows,
out totalPages,
out returnStatus,
out returnErrorMessage);
ViewData["patients"] = patients;
patientViewModel.TotalPages = totalPages;
patientViewModel.TotalRows = totalRows;
ViewData.Model = patientViewModel;
return PartialView("PatientSearchResults");
}
When hitting the SEARCH button in the View, the PatientSearch method
of the controller class is executed through an jQuery Ajax call. The
business logic layer returns a generic list of patient objects that are
stored in the ViewData object. The ViewData object is the standard way of passing application data to the View.The paging functionality is controlled by passing in the values for the current page number and the page size. Only a single page of patient objects is returned from the business layer based on these two parameters. After the first page is rendered, the user can press the next page link which increments the current page number by one to get the next set of patients from the database.
In this example, a JSON object is not passed back to the View, This time, a partial view result is being returned. The goal is to dynamically generate a grid using a MVC view user control named “PatientSearchResults.ascx” as follows:
<table><tbody> <%
int i = 0;
foreach (var patient in (IEnumerable<DataModels.Patient>)ViewData["patients"])
{ %>
<tr style="height:25px; color:Black;
background-color:<%= i++ % 2 == 0 ? "#D3DCE5" : "#ebeff2" %>">
<td style="width: 20%">
<%= Html.Encode(patient.MedicalID)%>
</td>
<td style="width: 20%">
<%= Html.Encode(patient.SocialSecurityNumber)%>
</td>
<td style="width: 20%">
<a href="javascript:GetPatientDetails('<%= Html.Encode(patient.PatientID)%>');">
<%= Html.Encode(patient.LastName)%></a>
</td>
<td style="width: 20%">
<%= Html.Encode(patient.FirstName)%>
</td>
<td style="width: 40%">
</td>
</tr>
<% } %>
</tbody>
</table>
The view user control generates the needed HTML for the grid by iterating through the generic list of patients in the ViewData Model object. All this HTML is then passed back to the original JavaScript call: function PostSearchToServer() {
$.post('/Patient/PatientSearch', formData, function (returnHtml) {
$("#DivPatientSearchResults").html(returnHtml);
currentPageNumber = $("#PatientSearchForm #CurrentPageNumber").val();
totalPages = $("#PatientSearchForm #TotalPages").val();
if (totalPages > 0)
SetPagingLinks(currentPageNumber, totalPages);
EnableForm();
});
}
When the HTML is returned from the view user control, the DIV tag inside the PatientSearch.ascx” view user control is dynamically updated with the returned HTML.<div id="DivPatientSearchResults">
<% Html.RenderPartial("PatientSearchResults",Model); %>
</div>
For those who have experience with classic ASP development prior to the arrival of Microsoft .NET, will find the <% %>
syntax familiar. When MVC 1.0 was initially released, I was not too
thrilled to go back to this coding style. But as I reacquainted myself
with the syntax, I was able to move forward with the technology. Later I
would discover the different ways of limiting this server side coding
style by either using jQuery to parse JSON objects, generating the HTML
from a class library or implementing one of the various alternative View
Engines that are available to you. Most notably, I was impressed with
the Spark View Engine (http://sparkviewengine.com).
The idea behind the Spark View Engine is to allow the HTML to dominate
the flow of the View and allow the server side code to fit seamlessly.One of the nice things I also like about the MVC framework is that you can now have more than one form on your page, compared to ASP.NET Web Forms which only allowed you to have one form per page. Having multiple forms on your web page allows you to simulate AJAX update panels and partial page postback functionality similar to Web Forms. By overriding the
ACTION attribute of the HTML form through
JavaScript, you can create very complex pages that can postback to
various different MVC controllers and methods.
No comments:
Post a Comment