In few weeks (October 5th and 6th) I will be attending the MTS 2010 conference by Microsoft. On first day (October 5th) I will be speaking on "Rich user interface in ASP.NET MVC applications with jQuery plugins". There will be a little bit of talking and a lot of samples. I'm planning to cover CascadingDropDown, jTemplates, jQuery Treeview, jqGrid and few more.

I will do my best to share the presentation here (video if possible) but it will be all in polish (as it is polish conference).

Recently I was playing around with Cross-Origin Resource Sharing (you can read a W3C Working Draft for it here), which is a mechanism to enable client-side cross-origin requests. As you can see, the last working draft is from 27 July 2010, so the technology is still in development. Let's take a look, how we can use it from JavaScript:
...
try {
//Create XMLHttpRequest and assign it to 'request' variable
...

if (request) {
//Our request URL
var jsonUrl = 'http://localhost:60000/CrossOrigin';
//Check if XMLHttpRequest support CORS
if (request.withCredentials !== 'undefined') {
//If it does, prepare request
request.open('GET', jsonUrl, true);
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.onreadystatechange = function () {
if (request.readyState == 4) {
if (request.status == 200) {
document.getElementById('divTarget').innerHTML = request.responseText;
}
}
};
//If not, than check if XDomainRequest is available
} else if (typeof XDomainRequest != 'undefined') {
//If it is, prepare request
request = new XDomainRequest();
request.open('GET', jsonUrl);
request.onload = function () {
document.getElementById('divTarget').innerHTML = request.responseText;
};
} else {
throw (null);
}
} else {
throw (null);
}

//Send request
request.send(null);
} catch (e) {
//There is no support for CORS, use something else instead (for example JSONP)
...
}
The first condition checks for native support of CORS through XMLHttpRequest. This is the way to go for FireFox, Chrome and Safari - you don't need to write any special code, just standard XMLHttpRequest. The second condition is for Internet Explorer, which supports cross-origin requests through XDomainRequest. I didn't managed to get it to work in Opera.
So we know how to perform request, let's take a look underneath. What you see below, are requests from FireFox:
OPTIONS
Connection: keep-alive
Keep-Alive: 115
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: pl,en-us;q=0.7,en;q=0.3
Host: localhost:60000
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729; .NET4.0E)
Origin: http://localhost:50000
Access-Control-Request-Method: GET
Access-Control-Request-Headers: x-requested-with

GET
Connection: keep-alive
Keep-Alive: 115
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: pl,en-us;q=0.7,en;q=0.3
Host: localhost:60000
Referer: http://localhost:50000/CORS.htm
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729; .NET4.0E)
X-Requested-With: XMLHttpRequest
Origin: http://localhost:50000
The first request (OPTIONS) is a preflight request. Its job is to ensure that the resource is ok with the request. It uses following headers to perform check:
  • Origin - It contains the origin of the request. You should put Access-Control-Allow-Origin header with the same value into response if you accept the origin (you can also use '*' as a value if you consider your resource public).
  • Access-Control-Request-Method - The method which will be used in actual request. In response you should put Access-Control-Allow-Methods header with list of allowed methods separated by commas.
  • Access-Control-Request-Headers - Comma separated list of custom headers which will be attached to request. You should put a list of headers which you accept into Access-Control-Allow-Headers header in response.
The preflight request can be sometimes skipped when the methods are simple (for example IE does it with GET method). When the browser receives proper response, the actual request is being made. It differs from standard request by having Origin header - you should put a proper Access-Control-Allow-Origin into response. Unfortunately Chrome and Safari doesn't put Origin into request, but they still require Access-Control-Allow-Origin in response. That makes using '*" necessary if you want those browser to work.

We now have enough knowledge to start writing our HttpHandler:
public class CrossOriginHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}

public void ProcessRequest(HttpContext context)
{
//Clear the response (just in case)
ClearResponse(context);

//Checking the method
switch (context.Request.HttpMethod.ToUpper())
{
//Cross-Origin preflight request
case "OPTIONS":
//Set allowed method and headers
SetAllowCrossSiteRequestHeaders(context);
//Set allowed origin
SetAllowCrossSiteRequestOrigin(context);
break;
//Cross-Origin actual or simple request
case "GET":
//Disable caching
SetNoCacheHeaders(context);
//Set allowed origin
SetAllowCrossSiteRequestOrigin(context);
//Generate response
context.Response.ContentType = "text/plain";
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.Write("<h1>Hello World! [powered by Cross-Origin Resource Sharing]</h1>");
break;
//We doesn't support any other methods than OPTIONS and GET
default:
context.Response.Headers.Add("Allow", "OPTIONS, GET");
context.Response.StatusCode = 405;
break;
}

context.ApplicationInstance.CompleteRequest();
}
#endregion

#region Methods
protected void ClearResponse(HttpContext context)
{
context.Response.ClearHeaders();
context.Response.ClearContent();
context.Response.Clear();
}

protected void SetNoCacheHeaders(HttpContext context)
{
context.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
context.Response.Cache.SetValidUntilExpires(false);
context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.Cache.SetNoStore();
}
#endregion
}
This code should be easy to understand, but there are two key methods missing. Let's start with the one which puts Access-Control-Allow-Methods and Access-Control-Allow-Headers headers into response:
private void SetAllowCrossSiteRequestHeaders(HttpContext context)
{
//We allow only GET method
string requestMethod = context.Request.Headers["Access-Control-Request-Method"];
if (!String.IsNullOrEmpty(requestMethod) && requestMethod.ToUpper() == "GET")
context.Response.AppendHeader("Access-Control-Allow-Methods", "GET");

//We allow any custom headers
string requestHeaders = context.Request.Headers["Access-Control-Request-Headers"];
if (!String.IsNullOrEmpty(requestHeaders))
context.Response.AppendHeader("Access-Control-Allow-Headers", requestHeaders);
}
Now let's add the one which deals with Origin:
private void SetAllowCrossSiteRequestOrigin(HttpContext context)
{
string origin = context.Request.Headers["Origin"];
if (!String.IsNullOrEmpty(origin))
//You can make some sophisticated checks here
context.Response.AppendHeader("Access-Control-Allow-Origin", origin);
else
//This is necessary for Chrome/Safari actual request
context.Response.AppendHeader("Access-Control-Allow-Origin", "*");
}
As you can see, the method takes the absence of Origin header in Chrome and Safari into consideration.Complete sample application can be found here, go ahead and make some use of it.

Lately, I was in need of retrieving user's IP address. The first place I went looking was REMOTE_ADDR server variable. Unfortunately, due to all proxy servers out there, this variable can be pretty far from user's real IP address. After some googling I have put together a list of HTTP headers that might contain the real IP address:
  • CLIENT-IP
  • X-FORWARDED-FOR
  • X-FORWARDED
  • X-CLUSTER-CLIENT-IP
  • FORWARDED-FOR
  • FORWARDED
It's important to know, that X-FORWARDED-FOR may contain a comma+space separated list of IP addresses.
Having that list in mind, we can write a simple method which will attempt to retrieve user's real IP address:
public string DetermineIP(HttpContext context)
{
if (context.Request.ServerVariables.AllKeys.Contains("HTTP_CLIENT_IP") && CheckIP(context.Request.ServerVariables["HTTP_CLIENT_IP"]))
return context.Request.ServerVariables["HTTP_CLIENT_IP"];

if (context.Request.ServerVariables.AllKeys.Contains("HTTP_X_FORWARDED_FOR"))
foreach (string ip in context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"].Split(','))
if (CheckIP(ip.Trim()))
return ip.Trim();

if (context.Request.ServerVariables.AllKeys.Contains("HTTP_X_FORWARDED") && CheckIP(context.Request.ServerVariables["HTTP_X_FORWARDED"]))
return context.Request.ServerVariables["HTTP_X_FORWARDED"];

if (context.Request.ServerVariables.AllKeys.Contains("HTTP_X_CLUSTER_CLIENT_IP") && CheckIP(context.Request.ServerVariables["HTTP_X_CLUSTER_CLIENT_IP"]))
return context.Request.ServerVariables["HTTP_X_CLUSTER_CLIENT_IP"];

if (context.Request.ServerVariables.AllKeys.Contains("HTTP_FORWARDED_FOR") && CheckIP(context.Request.ServerVariables["HTTP_FORWARDED_FOR"]))
return context.Request.ServerVariables["HTTP_FORWARDED_FOR"];

if (context.Request.ServerVariables.AllKeys.Contains("HTTP_FORWARDED") && CheckIP(context.Request.ServerVariables["HTTP_FORWARDED"]))
return context.Request.ServerVariables["HTTP_FORWARDED"];

return context.Request.ServerVariables["REMOTE_ADDR"];
}

So how about this CheckIP method? The purpose of this method is to ensure that IP address is both a valid IP and does not fall within a private network range:
private bool CheckIP(string ip)
{
if (!String.IsNullOrEmpty(ip))
{
long ipToLong = -1;
//Is it valid IP address
if (TryConvertIPToLong(ip, out ipToLong))
{
//Does it fall within a private network range
foreach (long[] privateIp in _privateIps)
if ((ipToLong >= privateIp[0]) && (ipToLong <= privateIp[1]))
return false;
return true;
}
else
return false;
}
else
return false;
}

Now we are missing only two things. The first one is a method which converts IP from string to long:
private long ConvertIPToLong(string ip)
{
string[] ipSplit = ip.Split('.');
return (16777216 * Convert.ToInt32(ipSplit[0]) + 65536 * Convert.ToInt32(ipSplit[1]) + 256 * Convert.ToInt32(ipSplit[2]) + Convert.ToInt32(ipSplit[3]));
}

private bool TryConvertIPToLong(string ip, out long ipToLong)
{
try
{
ipToLong = ConvertIPToLong(ip);
return true;
}
catch
{
ipToLong = -1;
return false;
}
}

The second thing is an array of private network IP ranges:
private long[][] _privateIps = new long[][] {
new long[] {ConvertIPToLong("0.0.0.0"), ConvertIPToLong("2.255.255.255")},
new long[] {ConvertIPToLong("10.0.0.0"), ConvertIPToLong("10.255.255.255")},
new long[] {ConvertIPToLong("127.0.0.0"), ConvertIPToLong("127.255.255.255")},
new long[] {ConvertIPToLong("169.254.0.0"), ConvertIPToLong("169.254.255.255")},
new long[] {ConvertIPToLong("172.16.0.0"), ConvertIPToLong("172.31.255.255")},
new long[] {ConvertIPToLong("192.0.2.0"), ConvertIPToLong("192.0.2.255")},
new long[] {ConvertIPToLong("192.168.0.0"), ConvertIPToLong("192.168.255.255")},
new long[] {ConvertIPToLong("255.255.255.0"), ConvertIPToLong("255.255.255.255")}
};

Now we are good to go. Please keep in mind, that this approach doesn't guarantee that you will retrieve real IP address, as all of those headers are optional.

A colleague of mine recently had a problem with using jQuery UI Progressbar for reporting progress of asynchronous server side operations initialized via AJAX request. I decided to create a short sample for him and everybody else who might need it. I'm going to show how to do it in ASP.NET MVC and ASP.NET WebForms.
We will start with ASP.NET MVC sample. First thing to do is adding some JavaScript and CSS references:
<link href="<%= Url.Content("~/Content/jquery-ui-1.8.2.css") %>" rel="stylesheet" type="text/css" />
<script src="<%= Url.Content("~/Scripts/jquery-1.4.2.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery-ui-1.8.2.min.js") %>" type="text/javascript"></script>
Markup for Progressbar is very simple (just one div), so we will need only two lines of HTML in this sample:
<button id="operation">Operation</button>
<div id="progressbar" style="width:500px"></div>
Now some interesting stuff begins. We need two controller actions. One for triggering actual process and one for reporting progress:
/// <summary>
/// Action for triggering long running operation
/// </summary>
/// <returns></returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Operation()
{
HttpSessionStateBase session = Session;

//Separate thread for long running operation
ThreadPool.QueueUserWorkItem(delegate
{
int operationProgress;
for (operationProgress = 0; operationProgress <= 100; operationProgress = operationProgress + 2)
{
session["OPERATION_PROGRESS"] = operationProgress;
Thread.Sleep(1000);
}
});

return Json(new { progress = 0 });
}

/// <summary>
/// Action for reporting operation progress
/// </summary>
/// <returns></returns>
[NoCache]
public ActionResult OperationProgress()
{
int operationProgress = 0;

if (Session["OPERATION_PROGRESS"] != null)
operationProgress = (int)Session["OPERATION_PROGRESS"];

return Json(new { progress = operationProgress }, JsonRequestBehavior.AllowGet);
}
What is missing is JavaScript, which will initialize Progressbar, trigger the operation and set timer for updating progress:
<script type="text/javascript">
$(document).ready(function () {
//Progressbar initialization
$("#progressbar").progressbar({ value: 0 });
//Button click event
$("#operation").click(function (e) {
//Disabling button
$("#operation").attr('disabled', 'disabled');
//Making sure that progress indicate 0
$("#progressbar").progressbar('value', 0);
//Perform POST for triggering long running operation
$.post('<%: Url.Action("Operation") %>', function (data) {
//Updating progress
$("#progressbar").progressbar('value', data.progress);
//Setting the timer
window.progressIntervalId = window.setInterval(function () {
//Getting current operation progress
$.get('<%: Url.Action("OperationProgress") %>', function (data) {
//Updating progress
$("#progressbar").progressbar('value', data.progress);
//If operation is complete
if (data.progress == 100) {
//Clear timer
window.clearInterval(window.progressIntervalId);
//Enable button
$("#operation").attr('disabled', '');
}
});
}, 500);
});
});
});
</script>
The result looks like this:
To make it work in ASP.NET WebForms, we need two PageMethods instead of actions:
/// <summary>
/// PageMethod for triggering long running operation
/// </summary>
/// <returns></returns>
[System.Web.Services.WebMethod(EnableSession=true)]
public static object Operation()
{
HttpSessionState session = HttpContext.Current.Session;

//Separate thread for long running operation
ThreadPool.QueueUserWorkItem(delegate
{
int operationProgress;
for (operationProgress = 0; operationProgress <= 100; operationProgress = operationProgress + 2)
{
session["OPERATION_PROGRESS"] = operationProgress;
Thread.Sleep(1000);
}
});

return new { progress = 0 };
}

/// <summary>
/// PageMethod for reporting progress
/// </summary>
/// <returns></returns>
[System.Web.Services.WebMethod(EnableSession = true)]
public static object OperationProgress()
{
int operationProgress = 0;

if (HttpContext.Current.Session["OPERATION_PROGRESS"] != null)
operationProgress = (int)HttpContext.Current.Session["OPERATION_PROGRESS"];

return new { progress = operationProgress };
}
and modified JavaScript:
<script type="text/javascript">
$(document).ready(function () {
//Progressbar initialization
$("#progressbar").progressbar({ value: 0 });
//Button click event
$("#operation").click(function (e) {
//Disabling button
$("#operation").attr('disabled', 'disabled');
//Making sure that progress indicate 0
$("#progressbar").progressbar('value', 0);
//Call PageMethod which triggers long running operation
PageMethods.Operation(function(result) {
if (result) {
//Updating progress
$("#progressbar").progressbar('value', result.progress)
//Setting the timer
window.progressIntervalId = window.setInterval(function () {
//Calling PageMethod for current progress
PageMethods.OperationProgress(function (result) {
//Updating progress
$("#progressbar").progressbar('value', result.progress)
//If operation is complete
if (result.progress == 100) {
//Clear timer
window.clearInterval(window.progressIntervalId);
//Enable button
$("#operation").attr('disabled', '');
}
});
}, 500);
}
});
});
});
</script>
The effect is pretty much the same:
The samples for ASP.NET MVC and ASP.NET WebForms can be downloaded here. Enjoy.

Recently one of my colleagues suggested that I should take a look at Jeditable plugin. This plugin allows in place edition of XHTML elements content. I can imagine few interesting scenarios with this plugin, so I decide to put together a simple sample.
Let's start with some markup, which will represent an NorthWind employee details (you can download NorthWind database from here):
TitleOfCourtesy: <u><%: Model.TitleOfCourtesy %></u><br />
FirstName: <u><%: Model.FirstName %></u><br />
LastName: <u><%: Model.LastName %></u><br />
Title: <b class="edit" id="Title"><%: Model.Title %></b><br />
Address: <b class="edit" id="Address"><%: Model.Address %></b><br />
PostalCode: <b class="edit" id="PostalCode"><%: Model.PostalCode %></b><br />
City: <b class="edit" id="City"><%: Model.City %></b><br />
Country: <b class="edit-select" id="Country"><%: Model.Country %></b><br />
Notes: <i class="edit-area" id="Notes"><%: Model.Notes %></i><br />
<br /><br /><button id="Submit">Submit</button>
I would like to keep this sample a simple as possible, so there will be no fancy design or architecture:
  • fields which will be edited in standard text input are wrapped in <b> element with class 'edit'
  • fields which will be edited in select are wrapped in <b> element with class 'edit-select'
  • fields which will be edited in textarea are wrapped in <i> element with class 'edit-area'
After making that clear, we can add Jeditable functionality to those elements. First we should reference two scripts:
<script src="<%= Url.Content("~/Scripts/jquery-1.4.1.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.jeditable.mini.js") %>" type="text/javascript"></script>
Now we can write some JavaScript:
$(document).ready(function () {
//Standard 'text' inputs
$('.edit').editable($.updateEmployee, {
//Element tooltip
tooltip: 'Click to edit...',
//Input style
style: 'display: inline'
});
//Selects
$('.edit-select').editable($.updateEmployee, {
//Options for select (you can use loadurl if you want to request data from server)
data: "{'USA':'USA','UK':'UK'}",
//Type
type: 'select',
//Text for accept button
submit: 'Accept',
//Element tooltip
tooltip: 'Click to edit...',
//Select style
style: 'display: inline'
});
//TextAreas
$('.edit-area').editable($.updateEmployee, {
//Type
type: 'textarea',
//Text for cancel button
cancel: 'Cancel',
//Text for accept button
submit: 'Accept',
//Element tooltip
tooltip: 'Click to edit...',
//TextArea style
style: 'display: inline'
});
});
The first parameter of editable is usually an URL where browser posts edited content. In that situation, every field is being posted as soon as user finishes editing. There is also a possibility of passing function in first parameter. As you can see, I'm using second approach here. The $.updateEmployee function looks like this:
$.updateEmployee = function (value, settings) {
//Based on the current element, we update the corresponding property of employee
switch ($(this).attr('id')) {
case 'Title':
window.Employee.Title = value;
break;
case 'Address':
window.Employee.Address = value;
break;
case 'PostalCode':
window.Employee.PostalCode = value;
break;
case 'City':
window.Employee.City = value;
break;
case 'Country':
window.Employee.Country = value;
break;
case 'Notes':
window.Employee.Notes = value;
break;
}
//We have to return string, it will be put into element for displaying
return (value);
}
You may wonder where the window.Employee object came from. It's being initialized like this (I know it's ugly, but it's only for sample purposes):
(function ($) {
window.Employee = {
EmployeeID: '<%: Model.EmployeeID %>',
Title: '<%: Model.Title %>',
Address: '<%: Model.Address %>',
PostalCode: '<%: Model.PostalCode %>',
City: '<%: Model.City %>',
Country: '<%: Model.Country %>',
Notes: '<%: Model.Notes %>'
};
})(jQuery);
This object allows me to perform a single POST for all fields, when user clicks Submit button:
$('#Submit').click(function () {
$.ajax({
type: 'POST',
contentType: 'application/json; charset=utf-8',
url: '<%=Url.Action("Details", "Home") %>',
dataType: 'json',
data: $.toJSON(window.Employee)
});
});
You can go ahead and click around on our editable elements:
Complete sample code can be downloaded here, enjoy.

Today I have updated AjaxControlToolkit in one of my ASP.NET 3.5 projects to version 3.5.40412 (there are few important bug fixes for me in this version). If someone would just replace the reference to AjaxControlToolkit.dll (like I did) he would see following error message:

AjaxControlToolkit requires ASP.NET Ajax 4.0 scripts. Ensure the correct version of the scripts are referenced. If you are using an ASP.NET ScriptManager, switch to the ToolkitScriptManager in AjaxControlToolkit.dll.

The message is pretty clear, so I replaced ScriptManager with ToolkitScriptManager:
<AjaxToolkit:ToolkitScriptManager ID="smContent" EnablePageMethods="true" EnableScriptGlobalization="true" EnableScriptLocalization="true" runat="server">
</AjaxToolkit:ToolkitScriptManager>

After that change everything seemed to work just fine, until I decided to log in. It happens that this project is using Sys.Services.AuthenticationService for login and logout operations. Any attempt to login resulted in 'object doesn't support this property or method' message. So something must be wrong with JavaScript's emitted by manager. I made a quick comparison of scripts referenced by ScriptManager and ToolkitScriptManager. Here are the headers of interesting one:
// Name:        MicrosoftAjax.js
// Assembly: System.Web.Extensions
// Version: 3.5.0.0
// FileVersion: 3.5.30729.196


// Name: MicrosoftAjax.js
// Assembly: AjaxControlToolkit
// Version: 3.5.40412.0
// FileVersion: 3.5.40412.2

The MicrosoftAjax.js referenced by ToolkitScriptManager has a lot less content than the one referenced by ScriptManager. Judging by the first error message, this script should be the one from ASP.NET AJAX 4.0 scripts. If that's the case, lets take a look at Microsoft AJAX CDN for ASP.NET AJAX 3.5 and ASP.NET AJAX 4.0. As you can see, in ASP.NET AJAX 4.0 Microsoft has split MicrosoftAjax.js in a lot of smaller files. The one we need is MicrosoftAjaxApplicationServices.js, let's download it and add to ToolkitScriptManager scripts:
<AjaxToolkit:ToolkitScriptManager ID="smContent" EnablePageMethods="true" EnableScriptGlobalization="true" EnableScriptLocalization="true" runat="server">
<Scripts>
<asp:ScriptReference Path="~/Scripts/MicrosoftAjaxApplicationServices.js" />
</Scripts>
</AjaxToolkit:ToolkitScriptManager>

Unfortunately this still doesn't work. The reason is that inline script, which sets default path for service, is executed before the script we have just added. We need to set the path for the service ourselves:
<script type="text/javascript">
Sys.Application.add_init(function() { Sys.Services.AuthenticationService.set_path('<%= ResolveClientUrl("~/Authentication_JSON_AppService.axd") %>'); });
</script>

Now it will work. I hope that this will be helpful for people who will encounter similar problem.

In one of my recent ASP.NET MVC 2 projects I had to make a form divided into few tabs. Each tab had to be validated before user can move to next one. Unfortunately ASP.NET MVC 2 built in validation doesn't provide mechanism for this. Well we are developers, so if there is no built in mechanism, we should create our own. First lets create a ValidationAttribute, which will be used to add information about validation group:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class ValidationGroupAttribute : ValidationAttribute
{
  #region Properties
  /// <summary>
  /// Name of the validation group
  /// </summary>
  public string GroupName { get; set; }
  #endregion

  #region Constructor
  public ValidationGroupAttribute(string groupName)
  {
    GroupName = groupName;
  }
  #endregion

  #region Methods
  public override bool IsValid(object value)
  {
    //No validation logic, always return true
    return true;
  }
  #endregion
}

We will also need a DataAnnotationsModelValidator for this attribute:
public class ValidationGroupValidator : DataAnnotationsModelValidator<ValidationGroupAttribute>
{
  #region Fields
  string _groupName;
  #endregion

  #region Constructor
  public ValidationGroupValidator(ModelMetadata metadata, ControllerContext context, ValidationGroupAttribute attribute)
: base(metadata, context, attribute) 
  {
    _groupName = attribute.GroupName;
  }
  #endregion

  #region Methods
  public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
  {
    //Add informations about validation group to metadata
    var validatioRule = new ModelClientValidationRule
    {
      ErrorMessage = String.Empty,
      ValidationType = "validationGroup"
    };
    validatioRule.ValidationParameters.Add("groupName", _groupName);

    return new[] { validatioRule };
  }
  #endregion
}

Don't forget to register above classes in your Global.asax:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ValidationGroupAttribute), typeof(ValidationGroupValidator));

So this is all we need on server side (please remember, that we don't want to achieve any server side logic, only partial validation on client side before posting the whole form). To make it work on client side, first we need to modify Sys.Mvc.FormContext._parseJsonOptions in MicrosoftMvcValidation.debug.js (or corresponding part in MicrosoftMvcValidation.js):
Sys.Mvc.FormContext._parseJsonOptions = function Sys_Mvc_FormContext$_parseJsonOptions(options) {
  var formElement = $get(options.FormId);
  var validationSummaryElement = (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(options.ValidationSummaryId)) ? $get(options.ValidationSummaryId) : null;
  var formContext = new Sys.Mvc.FormContext(formElement, validationSummaryElement);
  formContext.enableDynamicValidation();
  formContext.replaceValidationSummary = options.ReplaceValidationSummary;
  for (var i = 0; i < options.Fields.length; i++) {
    var field = options.Fields[i];
    var fieldElements = Sys.Mvc.FormContext._getFormElementsWithName(formElement, field.FieldName);
    var validationMessageElement = (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(field.ValidationMessageId)) ? $get(field.ValidationMessageId) : null;
    var fieldContext = new Sys.Mvc.FieldContext(formContext);
    Array.addRange(fieldContext.elements, fieldElements);
    fieldContext.validationMessageElement = validationMessageElement;
    fieldContext.replaceValidationMessageContents = field.ReplaceValidationMessageContents;
    for (var j = 0; j < field.ValidationRules.length; j++) {
      var rule = field.ValidationRules[j];
      //Here goes our small modification
      if (rule.ValidationType == 'validationGroup') {
        fieldContext.validationGroup = rule.ValidationParameters['groupName'];
      }
      else {
        var validator = Sys.Mvc.ValidatorRegistry.getValidator(rule);
        if (validator) {
          var validation = Sys.Mvc.$create_Validation();
          validation.fieldErrorMessage = rule.ErrorMessage;
          validation.validator = validator;
          Array.add(fieldContext.validations, validation);
        }
      }
    }
    fieldContext.enableDynamicValidation();
    Array.add(formContext.fields, fieldContext);
  }
  var registeredValidatorCallbacks = formElement.validationCallbacks;
  if (!registeredValidatorCallbacks) {
    registeredValidatorCallbacks = [];
    formElement.validationCallbacks = registeredValidatorCallbacks;
  }
  registeredValidatorCallbacks.push(Function.createDelegate(null, function () {
    return Sys.Mvc._validationUtil.arrayIsNullOrEmpty(formContext.validate('submit'));
  }));
  return formContext;
}

Now we can write ourselves function, which will perform partial validation based on group name:
Sys.Mvc.FormContext.validateGroup = function Sys_Mvc_FormContext$validateGroup(formId, groupName) {
  //Get form element
  var formElement = $get(formId);
  //Get form context
  var formContext = Sys.Mvc.FormContext.getValidationForForm(formElement);
  //Get form fields
  var fields = formContext.fields;
  //Array for errors
  var errors = [];
  //For each field
  for (var i = 0; i < fields.length; i++) {
    var field = fields[i];
    //If field has validation group and its name matches the one we are looking for
    if (field.validationGroup && field.validationGroup == groupName) {
      //Validate field
      var fieldErrors = field.validate('submit');
      if (fieldErrors) {
        Array.addRange(errors, fieldErrors);
      }
    }
  }
  //Return true it there are no errors, otherwise false
  return (!errors || !errors.length);
}

And our job is done. Now we can call Sys.Mvc.FormContext.validateGroup('formId', 'validationGroup'); whenever we want to perform partial validation. I have created a sample application which make use of those modification, you can download it from my repository. If there is a need for same modification for jQuery validation, let me know and I will look into it.

Today I have found this question on forums.asp.net and decided to take a look at it. I must warn you, the solution might be considered a little bit hacky.
So for starters, we have an ActionLink which looks like this:
<%= Ajax.ActionLink("Delete", "AjaxDeleteItem", new { ItemId = item.ItemId }, new AjaxOptions { Confirm = "Are you sure you want to delete the item?", HttpMethod = "Delete", UpdateTargetId = "divList"}) %>
and we want to secure this Delete action with AntiForgeryToken. First we need to add AntiForgeryToken helper to the view:
<%= Html.AntiForgeryToken() %>
Now let's modify our ActionLink:
<%= Ajax.ActionLink("Delete", "AjaxDeleteItem", new { ItemId = item.ItemId, __RequestVerificationToken = "_" }, new AjaxOptions { Confirm = "Are you sure you want to delete the item?", HttpMethod = "Delete", UpdateTargetId = "divList" })%>
Here comes the hacky part. We will use this additional route value (__RequestVerificationToken = "_") as a placeholder to inject the value of our AntiForgeryToken into links href with a little help of jQuery:
$(document).ready(function () {
//Finding AntiForgeryToken input
var antiForgeryToken = $('input[name=__RequestVerificationToken]');
if (antiForgeryToken.length > 0) {
//Serializing AntiForgeryToken
var antiForgeryTokenSerialized = antiForgeryToken.serialize();
//For each anchor in page
$('a').each(function (index, element) {
//Replace placeholder with serialized AntiForgeryToken
$(element).attr('href', $(element).attr('href').replace('__RequestVerificationToken=_', antiForgeryTokenSerialized));
});
}
});
Our job on client side is done, all the links which had our placeholder in their href now have AntiForgeryToken. We should now add token verification to the controller action:
[AcceptVerbs(HttpVerbs.Delete)]
[ValidateAntiForgeryToken]
public ActionResult AjaxDeleteItem(string ItemId)
{
...
}
If you would run the code now, you would see that it's not working. That's because AntiForgeryToken is designed for POST requests and it expects the token in Request.Form collection. To work around this we will modify the following line of OnAuthorization method in ValidateAntiForgeryTokenAttribute class:
string formValue = filterContext.HttpContext.Request.Form[fieldName];
to this:
HttpVerbs verb;
//Parse HttpVerb from Request.GetHttpMethodOverride()
if (!Enum.TryParse<HttpVerbs>(filterContext.HttpContext.Request.GetHttpMethodOverride(), true, out verb))
//If method couldn't be parsed, assume POST
verb = HttpVerbs.Post;

//Extract token based on HttpVerb
string formValue = String.Empty;
if (verb == HttpVerbs.Post || verb == HttpVerbs.Put)
formValue = filterContext.HttpContext.Request.Form[fieldName];
else
formValue = filterContext.HttpContext.Request.Params[fieldName];
After that small change, this workaround works the way we wanted to. The code was tested only in few simple testcases, so it migth contain some flaws.

This time I'm going to write about using jQuery Autocomplete plugin in ASP.NET MVC. If we want to use the plugin, we should reference following JavaScript and CSS:
<link href="<%= Url.Content("~/Content/jquery.autocomplete.css") %>" rel="stylesheet" type="text/css" />
<script src="<%= Url.Content("~/Scripts/jquery-1.4.1.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.autocomplete.min.js") %>" type="text/javascript"></script>
We will start by creating simple autocomplete with static data (below JavaScript assumes that there is already a input with type text and id region):
//Static data for autocomplete
var regions = ['Eastern', 'Western', 'Northern', 'Southern'];

$(document).ready(function () {
//Adding autocomplete to region input
$("#region").autocomplete(regions, {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
//The number of items in the select box
max: 4,
//Fill the input while still selecting a value
autoFill: true,
//Only suggested values are valid
mustMatch: true,
//The comparison doesn't looks inside
matchContains: false
});
});
Now we will extend regions so it contains objects with regionId and regionDescription properties. We want to display the regionDescription in autocomplete and put selected regionId into input of type hidden. To achieve this, we will use autocomplete callbacks for formatting data and result. But first let's change markup:
<%: Html.Hidden("regionId", -1) %>
<label for="regionDescription">Region:</label> <%: Html.TextBox("regionDescription")%>
And here is modified JavaScript:
//Static data for autocomplete
var regions = [
{ regionId: 1, regionDescription: 'Eastern' },
{ regionId: 2, regionDescription: 'Western' },
{ regionId: 3, regionDescription: 'Northern' },
{ regionId: 4, regionDescription: 'Southern' }
];

$(document).ready(function () {
//Adding autocomplete to region input
$("#regionDescription").autocomplete(regions, {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
//The number of items in the select box
max: 4,
//Fill the input while still selecting a value
autoFill: true,
//Only suggested values are valid
mustMatch: true,
//The comparison doesn't looks inside
matchContains: false,
//Callback which will format items for autocomplete list
formatItem: function (data, index, max) {
//Display only descriptions in list
return data.regionDescription;
},
//Callback which will format items for matching
formatMatch: function (data, index, max) {
//Match only descriptions
return data.regionDescription;
},
//Callback which will format result
formatResult: function (data, index, max) {
//Display only description as result
return data.regionDescription;
}
//Callback which will be called when user chooses value
}).result(function (event, data, formatted) {
//Set region id as hidden input value
if (data)
$("#regionId").val(data.regionId);
else
$("#regionId").val('-1');
});
});
What if we want to achieve the same behavior, but with remotely loaded data? We should start by adding markup for new inputs:
<%: Html.Hidden("territoryId", -1)%>
<label for="territoryDescription">Territory:</label> <%: Html.TextBox("territoryDescription")%>
The JavaScript isn't that much different, we just need to pass URL instead of array with data (you can also adjust local cache size):
//Adding autocomplete to territoryDescription input
$("#territoryDescription").autocomplete('<%: Url.Action("Territories", "Home") %>', {
//Don't fill the input while still selecting a value
autoFill: false,
//Only suggested values are valid
mustMatch: true,
//The comparison doesn't looks inside
matchContains: false,
//The number of query results to store in cache
cacheLength: 12,
//Callback wich will format items for autocomplete list
formatItem: function (data, index, max) {
//Display only descriptions in list
return data[1];
},
//Callback which will format items for matching
formatMatch: function (data, index, max) {
//Match only descriptions
return data[1];
},
//Callback which will format result
formatResult: function (data, index, max) {
//Display only description as result
return data[1];
}
//Callback which will be called when user chooses value
}).result(function (event, data, formatted) {
//Set territory id as hidden input value
if (data)
$("#territoryId").val(data[0]);
else
$("#territoryId").val('-1');
});
Controller action will receive three parameters: q - the string we should search for, limit - maximum number of items we should return (equal to max option) and timestamp. The plugin is expecting quite unusual response. It should be a string, where '\n' is a row delimeter and '|' is cells separator. So the action will look like this (I will skip database access code):
public ContentResult Territories(string q, int limit, Int64 timestamp)
{
StringBuilder responseContentBuilder = new StringBuilder();
IQueryable<Territory> territories = _repository.GetTerritories(q, limit);
foreach (Territory territory in territories)
responseContentBuilder.Append(String.Format("{0}|{1}\n", territory.TerritoryID, territory.TerritoryDescription));

return Content(responseContentBuilder.ToString());
}
So our autocomplete is now populated from server. Lets take it one step further, and make territory dependant on region. We will use extraParams for this:
//Adding autocomplete to territoryDescription input
$("#territoryDescription").autocomplete('<%: Url.Action("Territories", "Home") %>', {
//Don't fill the input while still selecting a value
autoFill: false,
//Only suggested values are valid
mustMatch: true,
//The comparison doesn't looks inside
matchContains: false,
//The number of query results to store in cache
cacheLength: 12,
//Extra parameters which will be put into query string
extraParams: {
regionId: function () { return $("#regionId").val(); }
},
//Callback which will format items for autocomplete list
formatItem: function (data, index, max) {
//Display only descriptions in list
return data[1];
},
//Callback which will format items for matching
formatMatch: function (data, index, max) {
//Match only descriptions
return data[1];
},
//Callback which will format result
formatResult: function (data, index, max) {
//Display only description as result
return data[1];
}
//Callback which will be called when user chooses value
}).result(function (event, data, formatted) {
//Set territory id as hidden input value
if (data)
$("#territoryId").val(data[0]);
else
$("#territoryId").val('-1')
});
After that change we will receive on more parameter in our action:
public ContentResult Territories(string q, int regionId, int limit, Int64 timestamp)
{
StringBuilder responseContentBuilder = new StringBuilder();
IQueryable<Territory> territories = _repository.GetTerritories(regionId, q, limit);
foreach (Territory territory in territories)
responseContentBuilder.Append(String.Format("{0}|{1}\n", territory.TerritoryID, territory.TerritoryDescription));

return Content(responseContentBuilder.ToString());
}
This way we have finished our simple sample. There are few more interesting options in this plugin (for example you can attach it to textarea and/or allow multiply selection). Our sample application looks like this:


You can download source code from here.

Some time ago I have written about using jQuery TreeView plugin for creating an asynchronous TreeView in ASP.NET MVC. Recently I had to use the same plugin in WebForms application, so I decided to share my experience. First let's reference all JavaScript and CSS needed:
<script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.treeview.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.treeview.async.js" type="text/javascript"></script>
<link href="~/Styles/jquery.treeview.css" rel="stylesheet" type="text/css" />
We should also add the markup for TreeView (an empty list):
<ul id="trFileBrowser"></ul>
Now we should provide data for TreeView, there are generally two approaches in which we can do that.
WebService approach
If we want to use WebService as the data source, we should start by adding an Ajax-enabled WCF Service to the project:
[ServiceContract(Namespace = "AsyncTreeViewExample")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TreeView
{
...
}
and enabling the Web programming model for this service (in web.config):

...
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="AsyncTreeViewExample.TreeViewAspNetAjaxBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="AsyncTreeViewExample.TreeView">
<endpoint address="" behaviorConfiguration="AsyncTreeViewExample.TreeViewAspNetAjaxBehavior" binding="webHttpBinding" contract="AsyncTreeViewExample.TreeView" />
</service>
</services>
</system.serviceModel>
...
In order to write WebService method properly, we need to remember that plugin performs GET request with application/x-www-form-urlencoded content type and expects bare JSON serialized array in response. Knowing all that, the method should look like this (TreeViewNode class was described in previous post about this plugin):

[OperationContract]
//Accept GET request and return bare JSON serialized result
[WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
public List<TreeViewNode> FileBrowserData(string root)
{
DirectoryInfo rootDirectory = null;
if (root == "source")
rootDirectory = new DirectoryInfo(@"E:\");
else
rootDirectory = new DirectoryInfo(root);

var directoryChildren = from child in rootDirectory.GetFileSystemInfos()
orderby child is DirectoryInfo descending
select child;

List<TreeViewNode> nodes = new List<TreeViewNode>();
foreach (FileSystemInfo directoryChild in directoryChildren)
{
bool isDirectory = directoryChild is DirectoryInfo;
nodes.Add(new TreeViewNode()
{
id = directoryChild.FullName,
text = directoryChild.Name,
classes = isDirectory ? "folder" : "file",
hasChildren = isDirectory
});
}

return nodes;
}
Last thing to do in this approach is adding some JavaScript for initialization purposes:
<script type="text/javascript">
$(document).ready(function () {
$('#trFileBrowser').treeview({
url: '<% = ResolveClientUrl("~/TreeView.svc/FileBrowserData") %>'
});
});
</script>
PageMethod approach
Using PageMethod as a source of data is a little bit more tricky. First of all PageMethods requires request to have application/json content type (so all the parameters have to be properly JSON serialized). Also PageMethod always returns response with wrapped body. There are three ways in which we can workaround this.
First way is to modify the plugin in such a way that it will work with PageMethod requirements (I would recommend this way, as others should be considered nasty workarounds). This is modified load funtion from jquery.treeview.async.js (I'm also changing request from GET to POST here):
function load(settings, root, child, container) {
$.ajax({
//Changing request to POST
type: "POST",
url: settings.url,
//Required content type
contentType: 'application/json; charset=utf-8',
//Properly JSON serialized data
data: '{"root": "' + root + '"}',
dataType: 'json',
success: function(response) {
function createNode(parent) {
var current = $("<li/>").attr("id", this.id || "").html("<span>" + this.text + "</span>").appendTo(parent);
if (this.classes) {
current.children("span").addClass(this.classes);
}
if (this.expanded) {
current.addClass("open");
}
if (this.hasChildren || this.children && this.children.length) {
var branch = $("<ul/>").appendTo(current);
if (this.hasChildren) {
current.addClass("hasChildren");
createNode.call({
text:"placeholder",
id:"placeholder",
children:[]
}, branch);
}
if (this.children && this.children.length) {
$.each(this.children, createNode, [branch])
}
}
}
//Accessing wrapped array
$.each(response.d, createNode, [child]);
$(container).treeview({add: child});
}
});
}
After that we just need a regular PageMethod:
[System.Web.Services.WebMethod]
public static List<TreeViewNode> FileBrowserData(string root)
{
//Method body is the same as in WebService
...
}
and initalization JavaScript:
<script type="text/javascript">
$(document).ready(function () {
$('#trFileBrowser').treeview({
url: '<% = ResolveClientUrl("~/PageMethodPlugin.aspx/FileBrowserData") %>'
});
});
</script>
Second way we can call a 'client-side workaround'. The idea is to change some default settings for all Ajax requests (it's nasty and I wouldn't recommend using it):

<script type="text/javascript">
$(document).ready(function () {
//Default callback for creating the XMLHttpRequest object
var xhr = $.ajaxSettings.xhr;

//Changing default values for all Ajax requests
$.ajaxSetup({
//Required content type
contentType: 'application/json; charset=utf-8',
//New callback for creating the XMLHttpRequest object
xhr: function () {
//Wrapping url parameters with '"'
if (this.url.indexOf('=') >= 0) {
this.url = this.url.replace(/=/g, '="') + '"';
}
//Calling default callback
return xhr.call(this);
},
//Sanitize the response (remove wrapping)
dataFilter: function (data, dataType) {
if (dataType == "json") {
var result = $.parseJSON(data);
if (result.d) {
return result.d;
} else {
return data;
}
} else {
return data;
}
}
});
$('#trFileBrowser').treeview({
url: '<% = ResolveClientUrl("~/PageMethodClient.aspx/FileBrowserData") %>'
});
});
</script>
We also have to allow GET verb on PageMethod:

[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod(UseHttpGet = true)]
public static List<TreeViewNode> FileBrowserData(string root)
{
//Method body is the same as in WebService
...
}
Third way is 'server-side workaround'. We will write PageMethod in a little bit different way, so it will work with TreeView:
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod(UseHttpGet = true)]
//Method doesn't take any parameters and return anything
public static void FileBrowserData()
{
//We are getting 'root' directly from request params
string root = HttpContext.Current.Request.Params.Get("root");

//Nodes generation code is the same as in other cases
...

//We are serializing nodes
JavaScriptSerializer serializer = new JavaScriptSerializer();
string nodesSerialized = serializer.Serialize(nodes);

//And writing them directly into response
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = "application/json; charset=utf-8";
HttpContext.Current.Response.AddHeader("Content-Length", nodesSerialized.Length.ToString());
HttpContext.Current.Response.AddHeader("Connection", "Close");
HttpContext.Current.Response.Write(nodesSerialized);
HttpContext.Current.Response.Flush();
}
What is left is few lines of JavaScript:
<script type="text/javascript">
$(document).ready(function () {
//Changing default values for all Ajax requests
$.ajaxSetup({
//Required content type
contentType: 'application/json; charset=utf-8'
});
$('#trFileBrowser').treeview({
url: '<% = ResolveClientUrl("~/PageMethodClient.aspx/FileBrowserData") %>'
});
});
</script>
Now we have all approaches covered, the result of this work looks like this:
You can download source code from repository.

Few days ago I wrote a post in this thread on forums.asp.net. In one of the posts vazavi noticed, that client side validation works only until you reload form using AJAX. I decided to take a deeper look at this. After few hours I have found a solution. The solution depends on which client side validation script you are using (the one which works with jQuery or the one which not). First I will go with the one which doesn't use jQuery. To make it work we need to write one function (I have just added it to MicrosoftMvcValidation.js):
Sys.Mvc.FormContext.OnSuccessEnableClientValidation = function (ajaxContext) {
  //Getting the update target container
  var updateTarget = document.getElementById(ajaxContext.$4.id);
  //Getting all script elements in it (script elements injected with innerHtml are not executed)
  var mvcClientValidationMetadataOldScripts = updateTarget.getElementsByTagName('script');
  var mvcClientValidationMetadataNewScripts = [];
  //For every script element
  while (mvcClientValidationMetadataOldScripts.length > 0) {
    //Create a new one
    var mvcClientValidationMetadataNewScript = document.createElement('script');
    mvcClientValidationMetadataNewScript.type = 'text/javascript';
    mvcClientValidationMetadataNewScript.text = mvcClientValidationMetadataOldScripts[0].text;
    //Add it to collection
    mvcClientValidationMetadataNewScripts.push(mvcClientValidationMetadataNewScript);
    //And remove old one
    updateTarget.removeChild(mvcClientValidationMetadataOldScripts[0]);
  }
  //For every new script element
  while (mvcClientValidationMetadataNewScripts.length > 0) {
    //Append it to update target container, this way they will be executed and generate needed metadata
    updateTarget.appendChild(mvcClientValidationMetadataNewScripts.pop());
  }
  //Calling Microsoft validation initialization for new metadata
  Sys.Mvc.FormContext._Application_Load();
}

Now we can use our new function as OnSuccess handler for Ajax.BeginForm:
<% Html.EnableClientValidation(); %>
<% using (Ajax.BeginForm("...", "...", null, new AjaxOptions() { UpdateTargetId = "...", OnSuccess = "Sys.Mvc.FormContext.OnSuccessEnableClientValidation" }, new { id = "..." })) { %>
  ...
<% } %>

Now it should work. Let's do the same for MicrosoftMvcJQueryValidation.js. We will add the following function inside of it:
function __MVC_OnSuccessEnableClientValidation(ajaxContext) {
  //Getting the update target container
  var updateTarget = document.getElementById(ajaxContext.$4.id);
  //Getting all script elements in it (script elements injected with innerHtml are not executed)
  var mvcClientValidationMetadataOldScripts = updateTarget.getElementsByTagName('script');
  var mvcClientValidationMetadataNewScripts = [];
  //For every script element
  while (mvcClientValidationMetadataOldScripts.length > 0) {
    //Create a new one
    var mvcClientValidationMetadataNewScript = document.createElement('script');
    mvcClientValidationMetadataNewScript.type = 'text/javascript';
    mvcClientValidationMetadataNewScript.text = mvcClientValidationMetadataOldScripts[0].text;
    //Add it to collection
    mvcClientValidationMetadataNewScripts.push(mvcClientValidationMetadataNewScript);
    //And remove old one
    updateTarget.removeChild(mvcClientValidationMetadataOldScripts[0]);
  }
  //For every new script element
  while (mvcClientValidationMetadataNewScripts.length > 0) {
    //Append it to update target container, this way they will be executed and generate needed metadata
    updateTarget.appendChild(mvcClientValidationMetadataNewScripts.pop());
  }
  //Getting new metadata
  var allFormOptions = window.mvcClientValidationMetadata;
  if (allFormOptions) {
    //For every form in metadata
    while (allFormOptions.length > 0) {
      //Enable validation for form based on metadata
      var thisFormOptions = allFormOptions.pop();
      __MVC_EnableClientValidation(thisFormOptions);
    }
  }
}

We can use this function in the same way as previous one:
<% Html.EnableClientValidation(); %>
<% using (Ajax.BeginForm("...", "...", null, new AjaxOptions() { UpdateTargetId = "...", OnSuccess = "__MVC_OnSuccessEnableClientValidation" }, new { id = "..." })) { %>
  ...
<% } %>

I can't say that this solution has been tested in every possible scenario, so if you find any problems with it, just let me know.

Some time ago I have written a post about creating asynchronous form with client-side validation in ASP.NET MVC. Since then few things have been changed. Now we have ASP.NET MVC 2 with built in client-side validation. So it's time to rewrite that sample (but we are still going to use jQuery Validation plugin).
First let's remove all the javascript from our view, and leave only those script references:
<script src="<%= Url.Content("~/Scripts/jquery-1.4.1.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/MicrosoftAjax.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/MicrosoftMvcAjax.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/MicrosoftMvcJQueryValidation.js") %>" type="text/javascript"></script>
Now we will move our form to user control and make some changes in it:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<...>" %>
<% Html.EnableClientValidation(); %>
<% if (Model.UserRegistered) { %>
  User successfully registered.
<% } else { %>
  <% using (Ajax.BeginForm("AsynchronousForm", "Home", null, new AjaxOptions() { UpdateTargetId = "dvAccountInformationFormContainer" }, new { id = "frmRegister" })) { %>
    <%= Html.AntiForgeryToken() %>
    <div>
      <fieldset>
        <legend>Account Information</legend>
        <p>
          <%= Html.LabelFor(value => Model.UserName) %>
          <%= Html.TextBoxFor(value => Model.UserName) %>
          <%= Html.ValidationMessageFor(value => Model.UserName) %>
        </p>
        <p>
          <%= Html.LabelFor(value => Model.Email) %>
          <%= Html.TextBoxFor(value => Model.Email) %>
          <%= Html.ValidationMessageFor(value => Model.Email) %>
        </p>
        <p>
          <%= Html.LabelFor(value => Model.Password) %>
          <%= Html.TextBoxFor(value => Model.Password) %>
          <%= Html.ValidationMessageFor(value => Model.Password) %>
        </p>
        <p>
          <%= Html.LabelFor(value => Model.ConfirmPassword) %>
          <%= Html.TextBoxFor(value => Model.ConfirmPassword) %>
          <%= Html.ValidationMessageFor(value => Model.ConfirmPassword) %>
        </p>
        <p>
          <input type="submit" value="Register" />
        </p>
      </fieldset>
    </div>
  <% } %>
</div>
As you can see I have added Html.EnableClientValidation(); to the markup - this will enable native client-side validation. I have also changed Html.BeginForm into Ajax.BeginForm - we can use it now without any problems. Another important change you should notice is usage of new strongly typed helpers.
We now should adjust the view markup to use our user control:
<div id="dvAccountInformationFormContainer">
  <% Html.RenderPartial("~/Views/Home/AccountInformationForm.ascx", Model); %>
</div>
Next thing is to modify our controller actions:
/// <summary>
///
GET Home/AsynchronousForm
/// </summary>
/// <returns>AsynchronousForm view</returns>
public ViewResult AsynchronousForm()
{
  return View(new AsynchronousFormViewModel());
}

/// <summary>
///
POST Home/AsynchronousForm
/// </summary>
/// <param name="viewModel">The form post data</param>
/// <param name="isAjaxRequest">
Value indicating if it is an AJAX request</param>
/// <returns>
AsynchronousForm view or json result</returns>
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
[IsAjaxRequest("isAjaxRequest")]
public ActionResult AsynchronousForm(AsynchronousFormViewModel viewModel, bool isAjaxRequest)
{
  if (ModelState.IsValid)
  {
    try
    {
      _usersRepository.RegisterUser(viewModel.UserName, viewModel.Email, viewModel.Password);
      viewModel = new AsynchronousFormViewModel() { UserRegistered = true };
    }
    catch (Exception ex)
    {
      ModelState.AddModelError("_FORM", ex.Message);
    }
  }

  if (isAjaxRequest)
    return PartialView("AccountInformationForm", viewModel);
  else
    return View(viewModel);
}

/// <summary>
///
Validates username
/// </summary>
/// <param name="UserName">
The username</param>
/// <returns>
true or false</returns>
public JsonResult ValidateUserName(string UserName)
{
  return Json(_usersRepository.ValidateUserNameUnique(UserName), JsonRequestBehavior.AllowGet);
}

/// <summary>
///
Validates password
/// </summary>
/// <param name="Password">
The password</param>
/// <returns>
true or error message</returns>
public JsonResult ValidatePassword(string Password)
{
  string passwordInvalidMessage = _usersRepository.ValidatePassword(Password);
  if (String.IsNullOrEmpty(passwordInvalidMessage))
    return Json(true, JsonRequestBehavior.AllowGet);
  else
    return Json(passwordInvalidMessage, JsonRequestBehavior.AllowGet);
}
Most of changes are in action responsible for handling Post request - it should now return markup which will be injected in our page using script generated by Ajax.BeginForm. We also had to add JsonRequestBehavior.AllowGet to JsonResult because of changes in ASP.NET MVC 2 (by default JsonResult is allowed only for Post request).
Now it's time for key changes. We will remove IDataErrorInfo interface from ViewModel class and add DataAnnotations. The problem is, that by default we have only four rules: Required, Range, StringLength and RegularExpression. In our sample we can only use Required (I will also use DisplayName attribute for labels) and for the rest of rules we have to write our own attributes.
Let's go step by step through implementing a DataAnnotation attribute for minlength rule and making it work on client-side. First, we need to create an attribute delivered from ValidationAttribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class MinLengthAttribute : ValidationAttribute
{
  #region Properties
  public int MinLength { get; set; }
  #endregion

  #region Constructor
  public MinLengthAttribute(int minLength)
  {
    MinLength = minLength;
  }
  #endregion

  #region Methods
  public override bool IsValid(object value)
  {
    if (value == null)
      return true;

    if ((value as String).Length < MinLength)
      return false;

    return true;
  }
  #endregion
}
The implementation is pretty straightforward and doesn't require any sophisticated comments. This is all we need to make it work on server-side, now let's make it work on client-side. We need to prepare a validator class (in this case it will be class delivered from DataAnnotationsModelValidator<TAttribute>):
public class MinLengthValidator : DataAnnotationsModelValidator<MinLengthAttribute>
{
  #region Fields
  int _minLength;
  string _errorMessage;
  #endregion

  #region Constructor
  public MinLengthValidator(ModelMetadata metadata, ControllerContext context, MinLengthAttribute attribute)
    : base(metadata, context, attribute)
  {
    _minLength = attribute.MinLength;
    _errorMessage = attribute.ErrorMessage;
  }
  #endregion

  #region Methods
  public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
  {
    var validatioRule = new ModelClientValidationRule
    {
      ErrorMessage = _errorMessage,
      ValidationType = "minlength"
    };
    validatioRule.ValidationParameters.Add("length", _minLength);

    return new[] { validatioRule };
  }
  #endregion
}
The most important method here is GetCleintValidationRules, it will be used for generating metadata which will be injected into the client script. Now, we have to register the validator with ASP.NET MVC 2 engine (preferably in Global.asax:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MinLengthAttribute), typeof(MinLengthValidator));
We also have to modify MicrosoftMvcJQueryValidation.js in two places. First, we should add following function:
function __MVC_ApplyValidator_MinLength(object, length) {
  object["minlength"] = length;
}
Then modify __MVC_CreateRulesForField function by extending switch statement inside of it:
switch (thisRule.ValidationType) {
  ...

  case "minlength":
    __MVC_ApplyValidator_MinLength(rulesObj,
      thisRule.ValidationParameters["length"]);
      break;

  ...
}
Following those steps we can implement attributes and validators for all needed rules. You can find here final source code of those used in sample.
You should take special interest in EqualToAttribute. ASP.NET MVC 2 has been built for .Net Framework 3.5, because of that there is no access to context in ValidatorAttribute. This is why cross properties check can't be done on property level, but has to be done on class level. On the other hand, class level attributes doesn't generate client-side validation. This is why this attribute must be placed on class and property to make it work on client and server side (nasty workaround but it is necessary).
When all the DataAnnotations attributes are finished, we can finally change our ViewModel:
[EqualTo("Password", "ConfirmPassword", typeof(AsynchronousFormViewModel), ErrorMessage = "Confirm password does not match password")]
public class AsynchronousFormViewModel
{
  #region Properties
  [DisplayName("Username:")]
  [Required(ErrorMessage = "Please enter username")]
  [MinLength(5, ErrorMessage = "Please enter username with at least 5 characters")]
  [Remote("ValidateUserName", "Home", typeof(DummyUsersRepository), "ValidateUserNameUnique", ErrorMessage = "Username is already in use")]
  public string UserName { get; set; }

  [DisplayName("Email:")]
  [Required(ErrorMessage = "Please enter email address")]
  [Email(ErrorMessage = "Please enter valid email address")]
  public string Email { get; set; }

  [DisplayName("Password:")]
  [Required(ErrorMessage = "Please enter password")]
  [Remote("ValidatePassword", "Home", typeof(DummyUsersRepository), "ValidatePassword", ErrorMessage = null)]
  public string Password { get; set; }

  [DisplayName("Confirm password:")]
  [Required(ErrorMessage = "Please repeat password")]
  [EqualTo("Password", "ConfirmPassword", typeof(AsynchronousFormViewModel), ErrorMessage = "Confirm password does not match password")]
  public string ConfirmPassword { get; set; }

  public bool UserRegistered { get; set; }
  #endregion
}
The sample is finished. We have achieved the same functionality, but the code fallows DRY principle now. You can download complete source code from Codeplex.
Older Posts