Ever since ASP.NET MVC Framework 2.0 has released I did not have a change to work with the new validation logic.
The DataAnnotations that are the only place where you have to keep logic related to validation, no more having to write the same logic in several places.

Today I wanted to give it a try, since I can delete lots of code from my views, if it works as promised.
I want to use jquery.validate.js (ships out of the box with Visual Studio 2010 and ASP.NET MVC Framework 2.0. But to my surprise only files that support the MicrosoftMvcValidation.js are available with the project template for a ASP.NET MVC 2 Web Application.

MicrosoftMvcJQueryValidation.js
With the ASP.NET MVC 2 RTM release a file named MicrosoftMvcJQueryValidation.js was shipped. I downloaded the RTM once again and got the MicrosoftMvcJQueryValidation.js file.

In my view I now add the following script tags:



Listing 1: Script tags

Enable validation on a view clientside
Than below script tags, I put the following call to enable validation on the client.

<% Html.EnableClientValidation(); %>

Listing 2: Html.EnableClientValidation

This call (listing 2) bubbles up the validation rules as Json data, the MicrosoftMvcJQueryValidation.js leverages jQuery to do the client side validation.

I added the code from listing 1 and 2 onto the  logon.aspx from a vanilla ‘file > new project > ASP.NET MVC 2 Web Application’ and it worked as I expected.

Contents MicrosoftMvcJQueryValidation.js
Because it took me some effort to get the MicrosoftMvcJQueryValidation.js I will put the code here:

/// 
/// 

// register custom jQuery methods

jQuery.validator.addMethod("regex", function(value, element, params) {
    if (this.optional(element)) {
        return true;
    }

    var match = new RegExp(params).exec(value);
    return (match && (match.index == 0) && (match[0].length == value.length));
});

// glue

function __MVC_ApplyValidator_Range(object, min, max) {
    object["range"] = [min, max];
}

function __MVC_ApplyValidator_RegularExpression(object, pattern) {
    object["regex"] = pattern;
}

function __MVC_ApplyValidator_Required(object) {
    object["required"] = true;
}

function __MVC_ApplyValidator_StringLength(object, maxLength) {
    object["maxlength"] = maxLength;
}

function __MVC_ApplyValidator_Unknown(object, validationType, validationParameters) {
    object[validationType] = validationParameters;
}

function __MVC_CreateFieldToValidationMessageMapping(validationFields) {
    var mapping = {};

    for (var i = 0; i < validationFields.length; i++) {
        var thisField = validationFields[i];
        mapping[thisField.FieldName] = "#" + thisField.ValidationMessageId;
    }

    return mapping;
}

function __MVC_CreateErrorMessagesObject(validationFields) {
    var messagesObj = {};

    for (var i = 0; i < validationFields.length; i++) {
        var thisField = validationFields[i];
        var thisFieldMessages = {};
        messagesObj[thisField.FieldName] = thisFieldMessages;
        var validationRules = thisField.ValidationRules;

        for (var j = 0; j < validationRules.length; j++) {
            var thisRule = validationRules[j];
            if (thisRule.ErrorMessage) {
                var jQueryValidationType = thisRule.ValidationType;
                switch (thisRule.ValidationType) {
                    case "regularExpression":
                        jQueryValidationType = "regex";
                        break;

                    case "stringLength":
                        jQueryValidationType = "maxlength";
                        break;
                }

                thisFieldMessages[jQueryValidationType] = thisRule.ErrorMessage;
            }
        }
    }

    return messagesObj;
}

function __MVC_CreateRulesForField(validationField) {
    var validationRules = validationField.ValidationRules;

    // hook each rule into jquery
    var rulesObj = {};
    for (var i = 0; i < validationRules.length; i++) {
        var thisRule = validationRules[i];
        switch (thisRule.ValidationType) {
            case "range":
                __MVC_ApplyValidator_Range(rulesObj,
                    thisRule.ValidationParameters["minimum"], thisRule.ValidationParameters["maximum"]);
                break;

            case "regularExpression":
                __MVC_ApplyValidator_RegularExpression(rulesObj,
                    thisRule.ValidationParameters["pattern"]);
                break;

            case "required":
                __MVC_ApplyValidator_Required(rulesObj);
                break;

            case "stringLength":
                __MVC_ApplyValidator_StringLength(rulesObj,
                    thisRule.ValidationParameters["maximumLength"]);
                break;

            default:
                __MVC_ApplyValidator_Unknown(rulesObj,
                    thisRule.ValidationType, thisRule.ValidationParameters);
                break;
        }
    }

    return rulesObj;
}

function __MVC_CreateValidationOptions(validationFields) {
    var rulesObj = {};
    for (var i = 0; i < validationFields.length; i++) {
        var validationField = validationFields[i];
        var fieldName = validationField.FieldName;
        rulesObj[fieldName] = __MVC_CreateRulesForField(validationField);
    }

    return rulesObj;
}

function __MVC_EnableClientValidation(validationContext) {
    // this represents the form containing elements to be validated
    var theForm = $("#" + validationContext.FormId);

    var fields = validationContext.Fields;
    var rulesObj = __MVC_CreateValidationOptions(fields);
    var fieldToMessageMappings = __MVC_CreateFieldToValidationMessageMapping(fields);
    var errorMessagesObj = __MVC_CreateErrorMessagesObject(fields);

    var options = {
        errorClass: "input-validation-error",
        errorElement: "span",
        errorPlacement: function(error, element) {
            var messageSpan = fieldToMessageMappings[element.attr("name")];
            $(messageSpan).empty();
            $(messageSpan).removeClass("field-validation-valid");
            $(messageSpan).addClass("field-validation-error");
            error.removeClass("input-validation-error");
            error.attr("_for_validation_message", messageSpan);
            error.appendTo(messageSpan);
        },
        messages: errorMessagesObj,
        rules: rulesObj,
        success: function(label) {
            var messageSpan = $(label.attr("_for_validation_message"));
            $(messageSpan).empty();
            $(messageSpan).addClass("field-validation-valid");
            $(messageSpan).removeClass("field-validation-error");
        }
    };

    // register callbacks with our AJAX system
    var formElement = document.getElementById(validationContext.FormId);
    var registeredValidatorCallbacks = formElement.validationCallbacks;
    if (!registeredValidatorCallbacks) {
        registeredValidatorCallbacks = [];
        formElement.validationCallbacks = registeredValidatorCallbacks;
    }
    registeredValidatorCallbacks.push(function() {
        theForm.validate();
        return theForm.valid();
    });

    theForm.validate(options);
}

// need to wait for the document to signal that it is ready
$(document).ready(function() {
    var allFormOptions = window.mvcClientValidationMetadata;
    if (allFormOptions) {
        while (allFormOptions.length > 0) {
            var thisFormOptions = allFormOptions.pop();
            __MVC_EnableClientValidation(thisFormOptions);
        }
    }
});

Listing 3: Contents MicrosoftMvcJQueryValidation.js

Henry Cordes
My thoughts exactly…

Currently rated 4.2 by 5 people

  • Currently 4.2/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

SDN Article on MVC Framework

Published 3/9/2009 by Henry in ASP.NET | C#

My article (in dutch) with the title: ASP.NET Webapplications finally unit-testable, or (ASP.NET webapplicaties eindelijk unit-testbaar in dutch) is been published in the 100th edition of the SDN Magazine (Software Developers Network). SDN 100th Magazine in pdf format and is available for download.

VP100100th SDN Magazine

Henry Cordes
My thoughts exactly…

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

ASP.NET MVC Preview 3 is released. You can download it from here:
http://www.microsoft.com/downloads/details.aspx?FamilyID=92F2A8F0-9243-4697-8F9A-FCF6BC9F66AB&displaylang=en

I took the following quote from ScottGu's blog which is a dutch translation!
Je kan hier een geintegreerd pakket van de ASP.NET MVC Preview 3 setup downloaden. Als je dat wil, kan je ook de ASP.NET MVC Preview 3 framework broncode en de framework unit tests hier downloaden.
Which means something along the line of:
You can download an integrated ASP.NET MVC Preview 3 setup package here.  You can also optionally download the ASP.NET MVC Preview 3 framework source code and framework unit tests here.

The way Microsoft is open about the code they write, is awsome to me.
The 'ScottGu teams' really turning the game upside down, IMHO it is so cool and insightfull to look at the way the MVC Framework team sets up the framework. The opportunity to look at their tests is another learning experience to me.
So download an learn, is what I say!

Henry Cordes
My thoughts exactly...

Currently rated 3.0 by 1 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

When building applications re-using components that contain functionality that is used in more than one place in that application is best practice. More than that it is one of the fundamentals of OO development.

What about with the MVC framework?
Can we use Controls, or UserControls? Because the MVC Framework does not use ViewState, events can not be used, so how do we use these Controls with the MVC Framework?
With the MVC Framework at this moment in time we can use the MVCToolkit, this Toolkit has UIHelpers, with these UIHelpers it is easier to create Textboxes, Dropdownlists, RadioButtons etc in the View.
But what if we have a grid with the same datasource and columns that we need on different Views? How do we go about in creating say a custom 'GridControl' UserControl that we can use on one or more in our MVC Framework application?

Creating a custom Grid UserControl
First we create a new MCV View User Control Item, through a right-click 'Add > New Item...'.  

New MVC View User Control
New MVC View User Control

We name the Control: 'Grid'. Than we open the Code-Behind Grid.cs file and you will see that the UserControl does not inherit System.Web.UI.UserControl, but inherits from System.Web.Mvc.ViewUserControl.
Because we want to show data in a grid, we need a way to let the Control use the same data as the containing ViewPage.
For this purpose we can use (like the System.Web.Mvc.ViewPage) the System.Web.Mvc.ViewUserControl syntax to provide our control with the object that we want to bind the grid to.

   1:  using System;
   2:  using System.Web;
   3:  using System.Web.Mvc;
   4:  using HC.Data.AuditHistory;
   5:   
   6:  namespace HC.Web.AuditTool.Views.Controls
   7:  {
   8:      public partial class Grid : ViewUserControl<AuditData>
   9:      {
  10:      }
  11:  }
Listing 1

All we have to do now is make sure our grid looks oke and shows the right data in the right place:

   1:  <table cellpadding="0" cellspacing="0" class="ListArea">
   2:      <tr>
   3:          <td id="tdDataArea">
   4:              <table id="gridBodyTable" cellpadding="1" cellspacing="0" class="List-Data">
   5:                  <colgroup>
   6:                      <col class="DataColumn List-SortedColumn"   />
   7:                      <col class="DataColumn"  />
   8:                      <col class="DataColumn"  />
   9:                  </colgroup>                                               
  10:  <% 
  11:      int numberOfRow = 0;
  12:      foreach (var auditHistory in ViewData.AuditHistory)
  13:      {
  14:  %>                                                         
  15:                  <tr class="List-Row">
  16:                      <td class="DataCell">
  17:                          <%=auditHistory.FieldDisplayName%>
  18:                       </td>
  19:                      <td class="DataCell">
  20:                          <nobr >
  21:                              <%=auditHistory.PreData == "" ? "&nbsp;" : auditHistory.PreData%>
  22:                          </nobr>
  23:                      </td>
  24:                      <td class="DataCell">
  25:                          <nobr >
  26:                              <%=auditHistory.UserName%>
  27:                          </nobr>
  28:                      </td>
  29:                  </tr>                                                 
  30:  <%
  31:      }
  32:  %>
  33:              </table>
  34:          </td>
  35:      </tr>
  36:  </table>
Listing 2

Now we open the first ViewPage in designmode and drag and drop the UserControl on the ViewPage. In the ViewPage we also make sure we inherit from ViewPage and use the ViewPage syntax:

   1:  using System.Web.Mvc;
   2:  using HC.Data.AuditHistory;
   3:   
   4:  namespace HC.Web.AuditTool.AuditTool.Views.Auditing
   5:  {
   6:      public partial class List : ViewPage<AuditData>
   7:      {
   8:      }
   9:  }
Listing 3

In our Controller we fill the ViewData with data and call the View, so the ViewPage loads and holds the ViewData. Next the UserControl is loaded by the ViewPage and it's ViewData is filled by the ViewPage's ViewData.

   1:  using System.Web.Mvc;
   2:   
   3:  using HC.Data.AuditHistory;
   4:  using HC.Web.AuditTool.Models;
   5:   
   6:  public class AuditingController : Controller
   7:  {
   8:      [ControllerAction]
   9:      public void Index()
  10:      {
  11:          AuditData auditData = AuditingDataModel.GetAuditData();
  12:          ViewData.Add("AuditHistory", auditData);
  13:          RenderView("List", auditData);
  14:      }
  15:  }
Listing 4

The result is a grid on a web page.
But the beaty is that whenever a view(page) uses the same object for data, the grid will work. 

I hope I made clear that with the MVC Framework we lose ViewState and events, but we can still re-use our Controls, be it in a slightly different way than we are used to.

Henry Cordes
My thoughts exactly...

Currently rated 4.3 by 3 people

  • Currently 4.333333/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

I started using the MVC Framework. I wanted to see if it is for me, because I am a big fan of TDD, but with web apps for me at least, it always is a struggle.
After having a bit of a startup problem, I really started to see the point after a while. The decoupling of concerns, with the Controller as entrypoint, that gets the data from a model and hands it over to the View is a really elegant solution.

Model View Presenter
With the MVP pattern (Model View Presenter) the aspx Page was the entrypoint, it delegated the call to the Presenter only to be called again. This is no decoupling.
With MVC the controller is the spider in the web. The greatest advantages I see in this Framework (or designpattern) is:

  • Allows extensive unit testing (use any unit testing framework you want to (MS Test, NUnit, MBUnit, etc).
  • Separation of concerns, all core contracts within the framework are interface based and therefore mockable
  • No more UrlRewriting, you can model your Url's completely(UrlRoutingEngine)
  • Extensible and pluggable (it is possible to use IOC containers like Windsor, Spring.Net, NHibernate, etc)

A picture to show how the project templates look in the Project Explorer, this is how we create an MVCApplication.

New MVC Application Project

By choosing the 'ASP.NET MVC Web Application and Test' projecttemplate, we get a Test project in our solution also. The MvcApplication's structure is structured around folders.
We get a Content folder, where the css file is located. And we get the: Models, Views and Controllers folders.

New Project structure

Also we get a Test Project in the solution. So how do we test a controller, how do we go about in isolating this 'unit' (the controller), so we can really 'unit test' it?

Interface for DataSource
I want to really decouple the controller so I can test only the functionality provided by the controller.

It needs to be possible to switch the real model for a 'test' or 'mock' version, this apllies also to the view. It also must be possible for the view to be 'mocked' like that.  

What I want is a to show a page containing AuditHistory. So I started to create an Interface that defines the DataSource (IAuditHistoryDataSource).

   1:  using System;
   2:  using System.Collections.Generic;
   3:   
   4:  namespace MvcApplication.AuditInfo
   5:  {
   6:      public interface IAuditHistoryDataSource
   7:      {
   8:          List<AuditHistory> GetAuditHistoryData();
   9:          List<AuditHistory> GetAuditHistoryDataByUserName(string userName);
  10:      }
  11:  }
Code listing 1: 'IAuditHistoryDataSource'

For the applications data I used LINQ to SQL, that allows me to select the database and with the click of a button generating the code to get the data from my database. Of course you can use whatever datasource you want, LINQ to Entities, LINQ to AD, plain old Datasets, ORM created datasources, NHibernate etc.

The name of the LINQ to SQL class is AuditDataContext:

LINQ to SQL Datamodel

The Model gets a private read-only IAuditHistoryDataSource property that is instantiated with a new instance of AuditDataContext when the setter is called while the private variable _DataSource is null.
Using the 'Inversion of Control' or 'Dependency Injection' pattern (which actually is nothing more than paramaterizing the constructor with the object you want the caller to be responsible for). The Model contains only code that calls methods on this IAuditHistoryDataSource Datasource.

   1:  using System;
   2:  using System.Collections.Generic;
   3:   
   4:  using MvcApplication.Data.AuditHistory;
   5:   
   6:  namespace MvcApplication.AuditInfo.Models
   7:  {
   8:      public class AuditDataModel
   9:      {
  10:          private IAuditHistoryDataSource _DataSource;
  11:          private IAuditHistoryDataSource DataSource
  12:          {
  13:              get 
  14:              { 
  15:                  if (_DataSource == null)
  16:                  {
  17:                      _DataSource = new AuditDataContext();
  18:                  }
  19:                  return _DataSource;
  20:              }
  21:          }
  22:   
  23:          /// <summary>
  24:          /// Constructs an instance of AuditDataModel inversing control of the datasource to the caller
  25:          /// </summary>
  26:          /// <param name="dataSource"></param>
  27:          public AuditDataModel(IAuditHistoryDataSource dataSource)
  28:          {
  29:              _DataSource = dataSource;
  30:          }
  31:   
  32:          /// <summary>
  33:          ///  Gets all AuditItems (by calling a method on the IAuditHistoryDataSource)
  34:          /// </summary>
  35:          /// <returns></returns>
  36:          public List<AuditHistory> GetAuditHistoryData()
  37:          {
  38:              return DataSource.GetAuditHistoryData();
  39:          }
  40:   
  41:          /// <summary>
  42:          ///  Gets all AuditItems belonging to a particular User with UserName equal 
  43:          ///  to parameter (by calling a method on the IAuditHistoryDataSource)
  44:          /// </summary>
  45:          /// <param name="userName"></param>
  46:          /// <returns></returns>
  47:          public List<AuditHistory> GetAuditHistoryDataByUserName(string userName)
  48:          {
  49:              return DataSource.GetAuditHistoryDataByUserName(userName);
  50:          }
  51:      }
  52:  }
Code listing 2

Using this technique it is now easy to mock the datasource for test purposes. Doing TDD we first write our test, than we write code. To keep this post understandable I choose to write it the other way around. I do not want to bore you with all my mistakes and refactorings, but provide the working solution.
Next I will list the Controller, which like the Model, uses the 'Inversion of Control' or 'Dependency Injection' pattern and takes a IAuditHistoryDataSource as a parameter in its constructor. The Controller than passes this dataSource to the constructor of the Model it uses.

   1:  using System;
   2:  using System.Web;
   3:  using System.Web.Mvc;
   4:  using System.Collections.Generic;
   5:   
   6:  using MvcApplication.Data.AuditHistory;
   7:  using MvcApplication.AuditInfo.Models;
   8:      
   9:  namespace MvcApplication.AuditInfo.Controllers
  10:  {
  11:   
  12:      public class AuditingController : Controller
  13:      {
  14:          private AuditDataModel AuditData;
  15:   
  16:          public AuditingController()
  17:          {
  18:              AuditData = new AuditDataModel();
  19:          }
  20:   
  21:          public AuditingController(IAuditHistoryDataSource dataSource)
  22:          {
  23:              AuditData = new AuditDataModel(dataSource);
  24:          }
  25:   
  26:          [ControllerAction]
  27:          public void Index()
  28:          {
  29:              List<AuditHistory> auditHistory = AuditData.GetAuditHistoryData();
  30:              ViewData.Add("AuditHistoryAll", auditHistory);
  31:              RenderView("List", auditHistory);
  32:          }
  33:   
  34:          [ControllerAction]
  35:          public void List(string id)
  36:          {
  37:              List<AuditHistory> auditHistory = AuditData.GetAuditHistoryDataByUserName(id);
  38:              ViewData.Add("AuditHistoryByUserName", auditHistory);
  39:              RenderView("List", auditHistory);
  40:          }
  41:      }
  42:  }
Code listing 3

As you can see the MVC Framework uses attributes to define Actions (ControllerAction). The Framework uses these so the right Action (method) is mapped to a url.
Scott Guthrie wrote a couple of blogposts on the subject. He wrote a series of 4 posts, this is part 1: http://weblogs.asp.net/scottgu/archive/2007/11/13/asp-net-mvc-framework-part-1.aspx

Tests
I want to write tests against the Controller, To do this, first I have to find a way to override the part where the view gets rendered. We do not want to render the view, we want to test if the controller gets the data and passes it to the (right) view.  In the Test project we create a AuditingControllerMock which inherits from the Controller we want to test. In the AuditingControllerMock we override the RenderView method and the only thing we do in it is get the viewName passed in through a parameter and assign it to a property: 'RenderViewAction'.

   1:  using MvcApplication.AuditInfo.Controllers;
   2:  using MvcApplication.Data.AuditHistory;
   3:   
   4:  namespace MvcApplication.AuditInfo.Test
   5:  {
   6:      public class AuditingControllerMock: AuditingController
   7:      {
   8:          public string RenderViewAction { get; set; }
   9:   
  10:          public AuditingControllerMock(IAuditHistoryDataSource dataSource):base(dataSource)
  11:          {
  12:          }
  13:   
  14:          protected override void RenderView(string viewName, string masterName, object viewData)
  15:          {
  16:              RenderViewAction = viewName;
  17:          }
  18:      }
  19:  }
Code listing 4

In the actual Test project I decided to use Rhino Mocks, because it helps me mock the DataSource in just a few lines of code.

   1:  using System.Collections.Generic;
   2:  using Microsoft.VisualStudio.TestTools.UnitTesting;
   3:   
   4:  using Rhino.Mocks;
   5:   
   6:  using MvcApplication.Data.AuditHistory;
   7:  using System;
   8:   
   9:  namespace MvcApplication.AuditInfo.Test.Controllers
  10:  {
  11:      
  12:      /// <summary>
  13:      ///  This is a test class for AuditingControllerTest and is intended
  14:      ///  to contain all AuditingControllerTest Unit Tests
  15:      ///</summary>
  16:      [TestClass()]
  17:      public class AuditingControllerTest
  18:      {
  19:          /// <summary>
  20:          ///A test for Index, needs to return all items in list
  21:          ///</summary>
  22:          [TestMethod()]
  23:          public void AuditingController_Get_All_AuditHistory_items_IndexTest()
  24:          {
  25:              MockRepository mocks = new MockRepository();
  26:   
  27:              IAuditHistoryDataSource mockDataSource = mocks.CreateMock<IAuditHistoryDataSource>();
  28:              List<AuditHistory> auditHistoryList = GetBaseTestList();
  29:   
  30:              AuditingControllerMock controller = new AuditingControllerMock(mockDataSource);
  31:              Expect.Call(mockDataSource.GetAuditHistoryData()).Return(auditHistoryList);
  32:   
  33:              mocks.ReplayAll();
  34:              controller.Index();
  35:              mocks.VerifyAll();
  36:   
  37:              Assert.IsNotNull(controller.ViewData["AuditHistoryAll"], "Controller AuditHistoryAll returned null");
  38:              Assert.IsTrue(((List<AuditHistory>)controller.ViewData["AuditHistoryAll"]).Count > 0, 
  39:                              "AuditHistoryAll did not return expected number of items AuditHistory Count is not greater than 0");
  40:   
  41:          }
  42:          
  43:          /// <summary>
  44:          ///A test for List(userName), returns all AuditHistory belonging to a particular user
  45:          ///</summary>
  46:          [TestMethod()]
  47:          public void AuditingController_Get_AuditHistory_By_UserName_ListTest()
  48:          {
  49:              MockRepository mocks = new MockRepository();
  50:              
  51:              IAuditHistoryDataSource mockDataSource = mocks.CreateMock<IAuditHistoryDataSource>();
  52:              List<AuditHistory> auditHistoryList = GetBaseTestList();
  53:   
  54:              AuditingControllerMock controller = new AuditingControllerMock(mockDataSource);
  55:              Expect.Call(mockDataSource.GetAuditHistoryDataByUserName(UserNameToTest)).Return(auditHistoryList);
  56:   
  57:              mocks.ReplayAll();
  58:              controller.List(UserNameToTest);
  59:              mocks.VerifyAll();
  60:   
  61:              Assert.IsNotNull(controller.ViewData["AuditHistoryByUserName"], "Controller AuditHistoryByUserName returned null");
  62:              Assert.IsTrue(((List<AuditHistory>)controller.ViewData["AuditHistoryByUserName"]).Count > 0, 
  63:                              "AuditHistoryByUserName did not return expected number of items AuditHistory Count is not greater than 0");
  64:          }
  65:   
  66:          
  67:          private Guid UserIDToTest = new Guid("aa6d5659-fd41-44f1-b838-8c88b0b753e6");
  68:          private Guid ObjectIDToTest = new Guid("1b884382-d51a-4b42-a1f0-d1d1b1adc864");
  69:          private int ObjectTypeToTest = 1;
  70:          private string UserNameToTest = "Bill";
  71:   
  72:          private List<AuditHistory> GetBaseTestList()
  73:          {
  74:              List<AuditHistory> auditHistoryList = new List<AuditHistory>
  75:              {
  76:                  GetAuditHistory(UserNameToTest,
  77:                                   UserIDToTest,
  78:                                   "Test operation",
  79:                                   ObjectTypeToTest,
  80:                                   ObjectIDToTest,
  81:                                   DateTime.Now,
  82:                                   "Audit data to test"),
  83:                  GetAuditHistory(UserNameToTest,
  84:                                   UserIDToTest,
  85:                                   "Test operation 2",
  86:                                   ObjectTypeToTest,
  87:                                   ObjectIDToTest,
  88:                                   DateTime.Now,
  89:                                   "Audit data 2 to test"),
  90:                  GetAuditHistory(UserNameToTest,
  91:                                   UserIDToTest,
  92:                                   "Test operation 3",
  93:                                   ObjectTypeToTest,
  94:                                   ObjectIDToTest,
  95:                                   DateTime.Now,
  96:                                   "Audit data 3 to test")
  97:              };
  98:              return auditHistoryList;
  99:          }
 100:   
 101:          private AuditHistory GetAuditHistory(string userName,
 102:                                               Guid userId,
 103:                                               string operation,
 104:                                               int objectType,
 105:                                               Guid objectId,
 106:                                               DateTime modifiedOn,
 107:                                               string auditData)
 108:          {
 109:              AuditHistory audit = new AuditHistory();
 110:              audit.AuditData = auditData;
 111:              audit.AuditID = Guid.NewGuid();
 112:              audit.ModifiedOn = modifiedOn;
 113:              audit.ObjectId = objectId;
 114:              audit.ObjectType = objectType;
 115:              audit.Operation = operation;
 116:              audit.UserId = userId;
 117:              audit.UserName = userName;
 118:   
 119:              return audit;
 120:          }
 121:      }
 122:  }
Code listing 5

In both tests I create a Mock of the Datasource by passing the IAuditHistoryDataSource to the CreateMock method of Rhino Mocks. I assign a filled List of Type AuditHistory to the variable auditHistoryList with the following line:

   52:  List auditHistoryList = GetBaseTestList(); 
Code listing 6

Next I tell Rhino Mock what method it needs to expect to be called and what that method must return, with the line:

   55:  Expect.Call(mockDataSource.GetAuditHistoryDataByUserName(UserNameToTest)).Return(auditHistoryList); 
Code listing 7

With the next three lines we tell Rhino Mocks it needs to pay attention(line 1. In line 2 we call the ControllerAction 'List' on our Controller. In line 3 Rhino Mocks Verifies if all our assertions are being met:

   57:  mocks.ReplayAll();
   58:  controller.List(UserNameToTest);
   59:  mocks.VerifyAll();
Code listing 8

After that I check if the returned data in the View is not null and if the number of items in the List of type AuditHistory is greater than 0.

My conclusion is that the testability indeed is lots better with the MVC Framework. There is a learning curve, it seemed a bit strange to use the 'classic ASP' way of 'spagetthi coding' in the html, but the separation of concerns is really a big advantage, that not only provides better testing but also better maintainable code.
If the server controls are obsolete now is too much to say, but teams that will use this Framework are going to have to learn another way of working and cannot use the server controls we got used to.
I really want to try out the MVC Toolkit, which provides UIHelpers and otherstuff that is usefull when using the MVC Framework, I did not get the change to work with it, but when I do I hope to post about it here.

MVC Framework links:
http://www.codeplex.com/MVCContrib 
ASP.NET Forum on MVC Framework 

Henry Cordes
My thoughts exactly...

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

ASP.NET MVC Framework

Published 10/27/2007 by Henry in ASP.NET | Patterns

On Scott Guthrie's blog I saw that there is going to be a framework to build ASP.Net applications using the Model View Controller pattern.

What is the Model View Controller Pattern?
The MVC design pattern provides the ability to vary different software components separately. The pattern improves the software robustness and reusability.
The MVC pattern helps the software designer to fulfill the object-oriented design principles, such as the Open Closed Principle(OCP).
The idea of the OCP is that developers must decide early on in the analysis or design phase which parts of the system will be expanded later and which will stay fixed.
According to the OCP principle, the design is extended by adding new code and classes by inheriting existing base classes rather than modifying the existing ones.
Completed and tested code is declared closed, and it is never modified.


Classic design pattern Model View Controller
  • Model: Contains and manipulates the data in the program.  These are the components of the application that are responsible for maintaining state.  Often this state is persisted inside a database
  • View: defines how the data of the model is presented to the user; the view forwards received commands and requests to the controller. These are the components responsible for displaying the application's user interface. 
  • Controller:Defines how the user interface reacts to received commands and requests. These are the components responsible for handling end user interaction, manipulating the model, and ultimately choosing a view to render to display UI.  In a MVC application the view is only about displaying information - it is the controller that handles and responds to user input and interaction.

The idea is that the Presentation layer (GUI) easily can be switched from for example an ASP.Net web user interface for a windows Forms user interface.
The discussion if this is practical and will be used in real world applications always comes up when discussing this pattern.
When we switch the UI for a Unit test framework, this discussion is almost always turned over in favor for the pattern, because (Unit)testing ASP.Net Webpages can be quite cumbersome at times.
So just for the sake of (unit) testing our code alone this pattern can really add value to our work as developers.
The maintainability of the code should benefit also, because all the layers of our application are really separated and encapsulated.

The ASP.NET MVC Framework will be fully integrated with ASP.NET, which means it supports existing ASP.NET features like forms/windows authentication, URL authorization, membership/roles, output and data caching, session/profile state management, health monitoring, configuration system, the provider architecture, etc.
It will be pluggable and extensible for example: you can optionally plug-in your own view engine, routing policy, parameter serialization, etc. 
It also supports IOC container models (Windsor, Spring.Net, NHibernate) for using existing dependency injection.
You can unit test the application without having to run the Controllers within an ASP.NET process (making unit testing fast).
You can use any unit testing framework you want to do testing ( NUnit, MBUnit, MS Test).

I really am interested and will try to follow this, frankly I can't wait to try out a CTP.

Henry Cordes
My thoughts exactly...

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5