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…


When I opened an aspx page, or view in Visual Studio 2008 after a few keystrokes Visual Studio ‘froze’, I could not do anything with it anymore, like a modal dialog was open, only it was not.

Ofcourse I killed the proces in Task Manager and tried again and again, and again, and…
It is realy frustrating, after searching the web for some time i finally found this microsoft connect issue. It looks like MS knew, or should know about this for  some time.

Microsoft Visual Studio Web Authoring Component
The Microsoft Visual Studio Web Authoring Component is used by Microsoft Office and Visual Studio.
I upgraded from Office 2007 to office 2010, which changed the Microsoft Visual Studio Web Authoring Component Visual Studio 2008 is depending on.

The steps to fix this issue are different for the different flavours of Visual Studio (Express, Pro, Team Suite etc.) But here you can find the right steps for your configuration.

I removed the Microsoft Visual Studio Web Authoring Component, installed the version from the Visual studio installation DVD and reinstalled Visual Studio SP1

 

Henry Cordes, My thoughts exactly...
My thoughts exactly…


Doing a migration of a CRM 4.0 system from one environment to another, I ran into this problem:
The target environment was a test environment and the owner of the environment assigned ‘only’ 2 GB of RAM.

CPU 100%
Immediately after I imported the organization using the Deployment Manager, the CrmAsyncService spiked the CPU and the memory load up to nearly 100% constantly…

Solution
Because we migrated from a machine that had enough RAM to a machine with 2GB (on a 64 bit machine…), the solution was to change the values of the fields ‘IntColumn’ in the Table: ‘DeploymentProperties’ of the ‘MSCRM_CONFIG’ database where the ColumName equals ‘AsyncInMemoryHigh’ and ‘AsyncInMemoryLow’.

crm_asyncsettings
Fig 1: DeploymentProperties table

The values that worked for us are:

ColumnName IntColumn
AsyncInMemoryHigh 50
AsyncInMemoryLow 20

These values are a lot higher in a default Crm implementation, something like 5000 and 2000, so the values I use here are only in case of very low memory.

After a restart of the CrmAsyncService (kill process and manually restart service, or wait for it to come up again), the problem was gone and Performance Monitor showed that the workflows (which caused the problem) where processed again.

What these settings do is they dictate the CrmAsyncService to take the amount specified in the AsyncItemsInMemoryHigh IntColumn of tasks to process in memory (and no more than is specified).
The service will process these tasks one at a time, untill it reaches the amount specified in the AsyncItemsInMemoryLow IntColumn, it than takes the amount of tasks in memory, that are specified in the AsyncItemsInMemoryHigh IntColumn again, until there are no more tasks to process.

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…


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