At my new project I am responsible for the configuration management. We are using Visual Studio 2010 Premium and Team Foundation Server 2010 (TFS). The app we are going to build is an ASP.NET MVC Web application as the presentation layer that communicates to the data store via a services layer . The data store will be a SQL Server database. For now the service layer is not implemented, because the build is the first thing I do.

Continuous Integration
Because the app is going to be a product that is very important for the organization (core business), I am going to use Continuous Integration (CI), every check-in will result in building of the product and running all our unit-tests. CI is very easy to achieve with TFS.

Nightly Build
In addition to the unit-testing and building of the product, I want a nightly build where the build also deploys the application to a test web server, deploys the database to a separate test SQL Server and when all these tasks have succeeded run automated web tests on the web application, so the state of the application is always known and the testers will have a report with the test results first thing in the morning. I am aware of Lab Management, which makes these configuration’s easier to manage, but as I mentioned we are using VS Premium for now, so I can not leverage Lab Management at this moment in time.

MSDeploy
To deploy a web application using Team Build I want to leverage MSDeploy. I had to install MSDeploy on the test webserver, the application will be deployed on. The installation and configuration of MSDeploy was not straightforward to say the least.

Here are some links to help with that:

Auto deploy Web application using Team Build
After MSDeploy is working on the webserver, the deployment of web applications using Team Build 2010 is a question of setting the right MSBuild arguments in the new Build settings window of the Build definition feature.

Process tab build settings build definition Team Build 2010
Process tab build settings build definition Team Build 2010

The following arguments where needed to make my configuration work, deploying via MSDeploy to another machine running IIS:

 

/p:DeployOnBuild=True /p:DeployTarget=MSDeployPublish /p:MSDeployPublishMethod=RemoteAgent /p:MsDeployServiceUrl="machinename webserver/msdeployagentservice" /p:DeployIisAppPath="BuildTest" /p:username="domain\Username" /p:password=P@ssword

Listing 1: MSBuild arguments

The most interesting arguments are:
MSDeployPublishMethod: InProc or RemoteAgent
MSDeployServiceUrl, because I use RemoteAgent, I could not use https://machinename:8172/msdeploy.axd (msbuild puts http before the url…), I took some time to figure out that the service listens to http://machinename/MSDEPLOYAGENTSERVICE also.

Web.config transformation
.NET Framework 4.0 comes with the web.config transformation feature, a web.config has a shadow file per build type, so a web.debug.config and a web.release.config.

<connectionstrings><add name="BuildtestConnectionString" connectionstring="Data Source=SQLMACHINE;Initial Catalog=DATABBASENAME;Integrated Security=false;uid=user;password=P@ssw0rd" providername="System.Data.SqlClient" xdt:transform="SetAttributes" xdt:locator="Match(name)" /></connectionstrings> 

Listing 2: web.config transform web.release.config

When the project is build as a release build, the values of the attributes in the connectionstring are changed to reflect the values in the connectionstring in listing 2.

This was all I needed to do to make Team Build deploy my web app to another server, I will report on how I configured Team Build to deploy the database (an vsts database project) in a follow up post.

Henry Cordes
My thoughts exactly…

Be the first to rate this post

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

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

I downloaded the Hanselminutes podcast nr 202, and only by reading in the title the word “WebformsMVC”, my interest was drawn.
I googled for it and found http://webformsmvp.com. A framework for using the MVP pattern in Webforms. I see the  positive side, familiarity if you know webforms, using your control-libraries, but still get the benefits of testability and the benefits you get with a decoupled architecture.

Also want to know more? Take a look at the featureset here.

My thoughts exactly…
Henry Cordes

Be the first to rate this post

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

In a recent post, I talked about a project where the requirement is to create modules that can be used in an ASP.NET webapplication and in Sharepoint 2007, where I wanted to have separations of concerns. The way I solved this is: creating ASP.NET UserControl libraries (inside their own ASP.NET Webapplication project) that we package inside Sharepoint webparts. During development we test and debug the modules in a separate ASP.NET webapplication. We use pre-build events to copy the ascx files from the UserControl libraries to this webapplication and because we set a reference to the  webapplications, the dll’s are available already. Scott Guthrie has a tutorial on his website that explains how we do this. 

In the post I said I wanted to leverage Unity as our Inversion of Control Container and I was thinking about using the WCSF, because I wanted to use the MVP design pattern.
After some prototyping, I came to the conclusion using WCSF was not practical for our purpose.  Still I wanted to use the MVP pattern, because of unit-testing and loose coupling needs. I could not use MVC, because of the Sharepoint requirements.

The Model-View-Presenter pattern is different from MVC or Model-View-Controller in the sense that with MVC the Controller is where the request comes in and the Controller is where you control (what’s in a name…) your View and what you get from the Model.
With MVP the request comes in at the View, the View than delegates it to the Presenter that will get data from the Model and returns it back to the View.

Model View Presenter pattern Pic 1.: Model View Presenter pattern

The solution I finally found to be working really good is using the MVP design pattern and Unity in the following manner.

Model View Presenter UserControl diagramPic 2.:Model View Presenter Class Diagram 

As you can see in picture 2 the Model, View and Presenter all are implementing an interface. Practicing Interface based programming sets the door wide open for leveraging an Inversion of Control container, or IoC container. In my case I am using Unity, because I am doing this project for a client who is heavily into using Microsoft tools. All Presenters are derived from the abstract class Presenter<TView>.


Pic 3.: Interfaces used in MVP diagram

Why the interfaces?
Using Interfaces for all these parts of our component makes it easy to use an IoC container. Our objects are loosely coupled and we can switch out the concrete implementations easily, because we program against the interfaces. Say we want to test the Presenter and to avoid having a dependency on a data store or some service we want to switch the Model out for an implementation that returns the same hard coded objects each time. all we have to do is create this ‘mock’ Model that implements the IZaakModel interface (on Picture 3) and we are good to go.
To make this even more appealing: if we use an IoC container we only have to change the configuration in the container to start to use this other Model…

Here a listing of the Presenter of picture 2.

   1:  using System;
   2:  using Microsoft.Practices.Unity;
   3:  using HC.Interfaces.Zaak;
   4:  using HC.Mvp;
   5:  using HC.Domain;
   6:   
   7:  namespace HC.Presenters.Zaak
   8:  {
   9:      public class ZaakDetailPresenter: Presenter<IZaakDetailsView>, IZaakDetailsPresenter
  10:      {
  11:          private Zaak CurrentZaak{ get; set;}
  12:   
  13:          [Dependency]
  14:          public IZaakService Service { get; set;}
  15:   
  16:          private IZaakModel _model;
  17:          public ZaakDetailPresenter([Dependency] IZaakModel model)
  18:          {
  19:              _model = model;
  20:              _model.CurrentZaakChanged += new EventHandler<DataEventArgs<Zaak>>(_model_CurrentZaakChanged);
  21:          }
  22:   
  23:          void _model_CurrentZaakChanged(object sender, DataEventArgs<Zaak> e)
  24:          {
  25:              CurrentZaak = e.Data;
  26:              View.ShowCurrentZaakOnView(CurrentZaak);
  27:          }
  28:   
  29:          public override void OnViewInitialized()
  30:          {
  31:              base.OnViewInitialized();
  32:          }
  33:   
  34:          public override void OnViewLoaded()
  35:          {
  36:              View.ShowCurrentZaakOnView(CurrentZaak);
  37:          }
  38:   
  39:      }
  40:  }

Listing 1: Presenter

 

Because the Presenter (in listing 1) is derived from the Presenter<TView> abstract class, it is necessary to tell the Presenter of what type its View will be. Because we  use the Interface again, all we have to do is let Unity (our IoC container) do the work to supply the concrete implementation for this Interface. On line 13 of listing 1 we see the Dependency attribute, in this case the attribute will be used by Unity to resolve the concrete implementation of the IZaakService interface for the property (Service) on which the attribute is decorated that is configured.

The Model
The Model from picture 1 implements the Interface IZaakModel, and is responsible for getting the data if the Presenter asks for it and for letting the Presenter know that it has got this data and handing it over to the Presenter.

   1:  using System;
   2:  using Microsoft.Practices.Unity;
   3:  using HC.Domain;
   4:  using HC.Mvp;
   5:  using HC.Interfaces.Zaak;
   6:   
   7:  namespace HC.Models
   8:  {
   9:      public class ZaakModel : IZaakModel
  10:      {
  11:          public event EventHandler<DataEventArgs<Zaak>> CurrentZaakChanged;
  12:   
  13:          private IZaakService _datasource;
  14:          private Zaak _CurrentZaak;
  15:   
  16:          public ZaakModel([Dependency] IZaakService datasource)
  17:          {
  18:              _datasource = datasource;
  19:          }
  20:   
  21:          #region IZaakModel Members
  22:          public void LoadCurrentZaakById(int zaakId)
  23:          {
  24:              CurrentZaak = _datasource.GetZaakById(zaakId);
  25:          }
  26:   
  27:          public Zaak CurrentZaak
  28:          {
  29:              get { return _CurrentZaak; }
  30:              set
  31:              {
  32:                  _CurrentZaak = value;
  33:                  OnCurrentZaakChanged(new DataEventArgs<Zaak>(_CurrentZaak));
  34:              }
  35:          }
  36:   
  37:          public virtual void OnCurrentZaakChanged(DataEventArgs<ZaakExtended> e)
  38:          {
  39:              if (CurrentZaakChanged != null)
  40:                  CurrentZaakChanged(this, e);
  41:          }
  42:          #endregion
  43:      }   
  44:  }

Listing 2.: The Model

In listing 2 the Model is listed, is also depending on Unity for the resolving of the concrete implementations for interfaces. It implements IZaakModel  and thus implements the LoadCurrentZaakById method and the CurrentZaak property. When the  LoadCurrentZaakById (line 22, listing 2) is called it fills the CurrentZaak property calling a method on an object that implements the IZaakService interface (the private _datasource variable that is set by the ZaakModel constructor using Unity).

When the CurrentZaak property is filled the setter (line 30, listing 2) is used and in the setter the CurrentZaakChanged event is raised. Every object that subscribes to this event will recieve the change.

In listing 1 we can see on line 20 the Presenter has attached the event and will recieve this change. On line 26 of listing 1 the ShowCurrentZaakOnView method of the View is called.

The View
In ASP.NET a request will load an aspx page, the view is where the request starts, the view than delegates responsibility to the Presenter.

   1:  using System;
   2:  using Microsoft.Practices.Unity;
   3:  using HC.Interfaces.Zaak;
   4:  using HC.Mvp;
   5:  using HC.Domain;
   6:   
   7:  namespace HC.ZaakModule
   8:  {
   9:      public partial class ZaakDetails : System.Web.UI.UserControl,IZaakDetailsView
  10:      {
  11:          IZaakDetailsPresenter _presenter;
  12:          public event EventHandler UserControlLoaded;
  13:          [Dependency]
  14:          public IZaakDetailsPresenter Presenter
  15:          {
  16:              get
  17:              {
  18:                  return _presenter;
  19:              }
  20:              set
  21:              {
  22:                  if (value == null)
  23:                      throw new ArgumentNullException("value is null");
  24:              
  25:                  _presenter = value;
  26:                  _presenter.View = this;
  27:              }
  28:          }
  29:      
  30:          protected void Page_Load(object sender, EventArgs e)
  31:          {
  32:              if (!this.IsPostBack)
  33:              {
  34:                  this._presenter.OnViewInitialized();
  35:              }
  36:              this._presenter.OnViewLoaded();
  37:          }
  38:          
  39:          #region Control Init (Unity)
  40:          protected override void OnInit(EventArgs e)
  41:          {
  42:              IUnityContainer container = Container.Instance;
  43:              _presenter = container.Resolve<IZaakDetailsPresenter>();
  44:              _presenter.View = this;
  45:              base.OnInit(e);
  46:          }
  47:          #endregion
  48:      
  49:          #region IZaakDetailsView Members
  50:          public void ShowCurrentZaakOnView(Zaak zaak)
  51:          {
  52:              DetailsAlgemeenControl.CurrentZaak = zaak;
  53:              DetailsVoortgangControl.CurrentZaak = zaak;
  54:          }
  55:          #endregion
  56:      }
  57:  }

Listing 3.: The View

On line 50 of listing 3 the OnInit method of the UserControl (our view) is listed. Inside the body of this method we can see that we instantiate a variable named container (type IUnityContainer) and put a Container.Instance in it. On the next line (line 53) we fill the private member _presenter (the private member that is used in our property Presenter) by letting the container resolve the interface  IZaakDetailsPresenter. On the next line (line 54) we tell the Presenter that it’s View is this control. We have to do this here, because the View is where the request comes in. So this is the first component of our App that is loaded. Because my requirement is to create modules that can be used in Sharepoint or in ASP.NET, I cannot use the Global.asax to resolve the concrete implementations.

The ascx, or html is very straightforward, so i am not showing it, because I feel it is in the way of what i want to tell. I hope it is clear enough without it.

The Container
So how can
the container variable that is of Type IUnityContainer, an interface, on line 52 know what concrete class to Resolve? and where does Container come from. What does Container.Instance do?
In a class called container I create a UnityContainer using the Singleton design pattern:

   1:  using System;
   2:  using Microsoft.Practices.Unity;
   3:  using HC.Models;
   4:  using HC.Interfaces.Zaak;
   5:  using HC.Services;
   6:  using HC.Presenters.Zaak;
   7:   
   8:  namespace HC.ZaakModule
   9:  {
  10:      public class Container
  11:      {
  12:   
  13:          private static IUnityContainer instance;
  14:   
  15:          private Container() { }
  16:   
  17:          public static IUnityContainer Instance
  18:          {
  19:              get
  20:              {
  21:                  if (instance == null)
  22:                  {
  23:                      instance = new UnityContainer();
  24:   
  25:                      instance.RegisterType<IZaakModel, ZaakModel>(new ContainerControlledLifetimeManager())
  26:                          .RegisterType<IZaakService, ZaakService>()
  27:                          .RegisterType<IZaakView, Zaken>()
  28:                          .RegisterType<IZaakPresenter, ZaakPresenter>()
  29:                          .RegisterType<IZaakListView, ZaakList>()
  30:                          .RegisterType<IZaakSearchPresenter, ZaakSearchPresenter>()
  31:                          .RegisterType<IZaakSearchView, ZaakSearch>()
  32:                          .RegisterType<IZaakListPresenter, ZaakListPresenter>()
  33:                          .RegisterType<IZaakDetailsView, ZaakDetails>()
  34:                          .RegisterType<IZaakDetailsPresenter, ZaakDetailPresenter>();
  35:                  }
  36:                  return instance;
  37:              }
  38:          }
  39:      }
  40:  }

Listing 4.: The Container

 

Singleton
Using the singleton design pattern
the registering of the concrete implementation to the interfaces will be done only once, as you can see in the module I am building there are more components that follow the same pattern, so using a singleton was in my opinion the way to go in this case.
I choose to use a simple version of the singleton, I make a class called container, this class has a private static variable called instance (of type IUnityContainer).
The class has a private constructor, so the class cannot be directly created. Furthermore the class has a public static property (type IUnityContainer) that can only get a single instance of a IUnityContainer, because in the getter we check if the private member called instance (type IUnityContainer) is null or has been filled already, if it is filled the instance it contains is returned, but if it is null a new UnityContainer is instantiated and using a Fluent Interface all Interfaces and their concrete implementations are registered inside it before we pass it to the instance variable, after that the instance it contains is returned.

On line 25 of listing 4 the RegisterType has a parameter and the following RegisterType methods of the fluent interface have not. With new ContainerControlledLifeManager() as a parameter into the RegisterType method we tell Unity to resolve the concrete implementation as a singleton. In the case of the Model I need it to be a singleton, because the model is the only place some state need to be held in my architecture.

Configuration file
Configuring the registration in a  configuration file is also possible, in my case I did not wanted to do it, but I can understand that in some cases that is exactly what you want.

Conclusion
Ofcourse a lot more has to be considered when using this architecture, but I hope this article helps in understanding how you can use Dependency injection or Inversion of control and the Model view Presenter pattern to create maintainable and robust applications.

Henry Cordes
My thoughts exactly…

Currently rated 4.3 by 6 people

  • Currently 4.333333/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

I am in the middle of the analysis fase for the project I mentioned in the post about using WCSF for ASP.NET (and Sharepoint) Usercontrols.  I know how I am going to integrate the UserControls into SharePoint in such a way, that the controls can be used in SharePoint and in custom ASP.NET Web applications.
I am going to use the MVP pattern without the help of WCSF or CWAB, but with the help of Unity. I will post an article in more detail about this in the near future.
For now I want to share a simple ASP.NET composite control I need, to make it easier to have watermark functionality for every textbox on a page, without having to add a TextBoxWatermarkExtender for every TextBox.

The way I accomplish this is by creating a composite control with a TextBox and a TextboxWatermarkExtender on it.

The code:

   1:  using System;
   2:  using System.ComponentModel;
   3:  using System.Web.UI;
   4:  using System.Web.UI.WebControls;
   5:  using AjaxControlToolkit;
   6:   
   7:  namespace HC.Web.Controls
   8:  {
   9:   
  10:       [DefaultProperty("Text"),
  11:        ValidationProperty("Text"),
  12:        Description("TextBox with Watermark capabilities"),
  13:        ToolboxData("<{0}:WatermarkTextBox runat=server></{0}:WatermarkTextBox>")]
  14:      public class WatermarkTextBox: CompositeControl
  15:      {
  16:          #region Private members
  17:           private TextBox textBox;
  18:           private TextBoxWatermarkExtender watermarkExtender;
  19:   
  20:           private string _DefaultWatermarkText = "Watermark";
  21:          #endregion
  22:   
  23:          #region Properties
  24:           /// <summary>
  25:           /// Gets the ClientID of the textbox
  26:           /// </summary>
  27:          [Bindable(true),
  28:          Category("Behavior"),
  29:          Description("The TextBoxClientID of the WatermarkTextBox"),
  30:          DefaultValue("")]
  31:          public string TextBoxClientID
  32:           {
  33:               get
  34:               {
  35:                   EnsureChildControls();
  36:                   return textBox.ClientID;
  37:               }
  38:           }
  39:   
  40:           /// <summary>
  41:           /// Gets or sets the Text in the textbox
  42:           /// </summary>
  43:           [Bindable(true),
  44:          Category("Behavior"),
  45:          Description("The text of the textbox"),
  46:          DefaultValue("")]
  47:           public string Text
  48:           {
  49:               get
  50:               {
  51:                   EnsureChildControls();
  52:                   return textBox.Text;
  53:               }
  54:               set
  55:               {
  56:                   EnsureChildControls();
  57:                   textBox.Text = value;
  58:               }
  59:           }
  60:   
  61:           /// <summary>
  62:           /// Gets or sets the CssClass of the Textbox
  63:           /// </summary>
  64:           [Bindable(true),
  65:           Description("The css class that is used when the textbox is in it's normal state"),
  66:           Category("Appearance"),
  67:           DefaultValue("")]
  68:           public override string CssClass
  69:           {
  70:               get
  71:               {
  72:                   EnsureChildControls();
  73:                   return textBox.CssClass;
  74:               }
  75:               set
  76:               {
  77:                   EnsureChildControls();
  78:                   textBox.CssClass = value;
  79:               }
  80:           }
  81:   
  82:           /// <summary>
  83:           /// Gets or sets the CssClass for the watermark state of the Textbox
  84:           /// </summary>
  85:           [Bindable(true),
  86:            Description("The css class that is used when the textbox is in it's watermark state"),
  87:            Category("Appearance"),
  88:            DefaultValue("")]
  89:           public string WatermarkCssClass
  90:           {
  91:               get 
  92:               { 
  93:                   EnsureChildControls();
  94:                   return watermarkExtender.WatermarkCssClass;
  95:               }
  96:               set 
  97:               { 
  98:                   EnsureChildControls();
  99:                   watermarkExtender.WatermarkCssClass = value;
 100:               }
 101:           }
 102:   
 103:           /// <summary>
 104:           /// Gets or sets the Text for the watermark
 105:           /// </summary>
 106:          [Bindable(true),
 107:           Category("Appearance"), 
 108:           Description("The (watermark)text that is shown when the textbox is in it's watermark state"),
 109:           DefaultValue("")]
 110:          public string WatermarkText 
 111:          {
 112:               get 
 113:               { 
 114:                   EnsureChildControls();
 115:                   return watermarkExtender.WatermarkText;
 116:               }
 117:               set 
 118:               { 
 119:                   EnsureChildControls();
 120:                   watermarkExtender.WatermarkText = value;
 121:               }
 122:          }
 123:   
 124:           /// <summary>
 125:           /// Gets or sets DefaultWatermarkText, cannot be empty
 126:           /// </summary>
 127:          [Bindable(true),
 128:          Category("Appearance"),
 129:          Description("The default (watermark)text that is shown when the textbox is in it's watermark state, and the WatermarkText property is empty"),
 130:          DefaultValue("")]
 131:          public string DefaultWatermarkText
 132:          {
 133:              get { return _DefaultWatermarkText; }
 134:              set 
 135:              { 
 136:                  if (value.Length > 0)
 137:                      _DefaultWatermarkText = value; 
 138:              }
 139:          }
 140:          #endregion
 141:   
 142:          protected override void RecreateChildControls()
 143:          {
 144:              EnsureChildControls();
 145:          }
 146:   
 147:          protected override void CreateChildControls()
 148:          {
 149:              SetTextBoxIdIfEmpty();
 150:              this.Controls.Add(textBox);
 151:              watermarkExtender.ID = textBox.ID + "_waterMarkExtender";
 152:              watermarkExtender.TargetControlID = textBox.ID;
 153:   
 154:              if (WatermarkText == "")
 155:              {
 156:                  WatermarkText = DefaultWatermarkText;
 157:              }
 158:              this.Controls.Add(watermarkExtender);
 159:          }
 160:   
 161:          #region Worker Methods
 162:          /// <summary>
 163:          /// Fills textBox ID if empty.
 164:          /// Format: ID + '_textBox'
 165:          /// </summary>
 166:           private void SetTextBoxIdIfEmpty()
 167:          {
 168:              if (string.IsNullOrEmpty(textBox.ID))
 169:              {
 170:                  textBox.ID = this.ID + "_textBox";
 171:              }
 172:          }
 173:          #endregion
 174:        
 175:           #region C'tor
 176:           public WatermarkTextBox()
 177:           {
 178:               textBox = new TextBox();
 179:               watermarkExtender = new TextBoxWatermarkExtender();
 180:           }
 181:           #endregion
 182:   
 183:      }
 184:  }

Listing: 1

As you can see in the code in listing 1,in the override of the CreateChildControls the actual adding of the controls to the control collection of the composite container is done.
It is important to make sure the textbox has a valid ID. Than you need to set the TargetControlID of the TextboxWatermarkExtender equal to the ID of the TextBox and the WatermarkText needs to be filled.

EnsureChildControls()
When you built a composite control make sure to call EnsureChildControls, so you can be sure the controls are accesible and no object reference not set exception is thrown.
A simple but quite useful example.

Henry Cordes
My thoughts exactly…

Currently rated 4.7 by 3 people

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

At the moment I am in the middle of the analysis phase for a project where the product will be constructed of several (User)Controls that contain certain blocks of functionality, these controls together will create the product.
Our first client needs this product to integrate with SharePoint 2007 (MOSS 2007), but my product manager makes it real clear that at least some of the ‘blocks of functionality’ or ‘components’ need to have the possibility be integrated in existing ASP.NET web applications.

So the challenge I face is creating controls that can be integrated inside SharePoint and custom ASP.NET web apps. Another challenge is that we really want to use the MVP, or MVC pattern, to increase the level of (unit)testability and maintainability. 
Also with testability in mind I want to use some kind of IoC container (Inversion of Control), to make it easier to swap out certain functionalities for others (also a Need To Have requirement), be it for unit testing, or swapping out data store's .

Web Client Software Factory
With all these requirements the Web Client Software Factory came to mind, it leverages the Composite Web Application Block and can use Unity (ObjectBuilder) as Dependency Injection or Inversion of Control Container. When I follow the Quick start however it is quite easy to create a webpage that acts as the View and uses a Presenter and a Controller (all interface based!).
The problem I get is when I want to create a UserControl  as a View that acts indepentently with it’s own Presenter and  Controller, as i am seeing it, the WCSF uses the containing page of a UserControl as the part that maps everything together.

I want to make a really independent control, that can exist all by itself. The learning curve was quite steep, because of my lack of knowledge of the WCSF and CWAB.

Approaches
I am not sure which route I will take in the end, but I am trying different approaches:

  1. Using Return of SmartPart to host a UserControl that consists of Usercontrols built using the Web Client Software Factory;
  2. Create a WebPart (derived from CompositeWeb.SharePoint.Web.UI.WebPart) that hosts UserControls (that are derived from CompositeWeb.SharePoint.Web.UI.UserControl), using the CompositeWeb.Sharepoint.dll and guidance published on the WCSF CodePlex site

SmartPart
For now I created both in a really simple form and find that the SmartPart has a real nice deployment model, you install the SmartPart using a installer and you only have to activate the Smartpart in the SiteCollection to make it available in the available Webpart’s list in SharePoint. You than make a folder with the name ‘usercontrols’ in the root of the sharePoint site, where  you put the .ascx files you want to be hosted. The dll belonging to the UserControl and it’s dependencies are to be placed into the bin directory of the same SharePoint site. I read somewhere that the installer sets the site´s security to full trust, but I can not verify that.

WebPart derived from CompositeWeb.SharePoint.Web.UI.WebPart
The Webpart solution gives a real nice compatibility model. You derive from these types and your webpart and UserControls work in SharePoint and in am ASP.NET webapp. This is achieved through the BuildItemWithCurrentContext method in the SPWebClientApplication class (CompositeWeb.SharePoint.dll). This method runs different code depending on the running HttpApplication implementation. You have to manually edit the global.asax file in the root of the SharePoint site. You have to edit the web.config manually to make several changes, one of them is to set the security to full trust! Part of the web.config changes is to add the PublicKeytoken for the webpart in an attribute of the Safecontrol element for the webpart. Furtermore you have to place the assemblies and their dependencies into the bin directory of the Sharepoint site. Of course you could automate these task by creating an installer, but I think it makes clear that there are lots of manual tasks to be done.

At this point in time I have not decided either way, but I will post when I have.

My thoughts exactly…
Henry Cordes

Currently rated 4.3 by 3 people

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

I am really proud that my first .NET Magazine article is publiced.
I wrote this article with my co-worker Dennis van de Laar. The topic is AJAX and Javascript.


Dutch .NET Magazine #22

pdfAjax is niet meer weg te denken

It is a small download, it''s PDF and it is Dutch Smile

Henry Cordes
My thougts exactly...

Currently rated 5.0 by 1 people

  • Currently 5/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

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