At my new project I am responsible for 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…


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…