Saturday, October 27, 2012

ASP.NET MVC ~ JSON decimal binding problem

With ASP.NET MVC, we will face problem when mapping JSON decimal values in an AJAX call to the model. For example consider the following class and the action method:

public class MyClass {
  
public decimal decValue1 { get; set; }
  
public int intValue1 { get; set; }
  
public decimal decValue2 { get; set; }
}

public ActionResult MyMethod(MyClass mc) {
  
return View();
}

and assume the following jquery ajax call:

$.ajax({
   type
: "POST",
   url
: "/Default/MyMethod",
   contentType
:'application/json',
   data
:JSON.stringify({decValue1:21.00,intValue1:3,decValue2:6.36})
});

On executing the above code, we will notice that the value for decValue1 will not be binded properly and that it will hold a value of 0. The reason is that the method JSON.stringify converts the value as follows before actually posting:

{"decValue1":21,"intValue1":3,"decValue2":6.36}

The default model binding behaviour believes the value of decValue1 to be of type integer (21) and therefore ignores to map this value to the destination type which is decimal. In order to overcome this problem, we will have to create our custom decimal binder and then register that in the application start event. The following is the code provided by Phil Haack to manage the custom binding of decimal types:

using System;
using System.Globalization;
using System.Web.Mvc;

public class DecimalModelBinder : IModelBinder
{
                  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
                  {
                         ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
                         ModelState modelState = new ModelState { Value = valueResult };
                         object actualValue = null;

                         try
                        {
                                             actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
                         }
                        catch (FormatException e)
                        {
                                             modelState.Errors.Add(e);
                         }

                         bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
                         return actualValue;
                  }
}

and then we should register this custom decimal binder class in the application start event in the Global.asax.cs file as follows:

protected void Application_Start() 
{
    AreaRegistration.RegisterAllAreas();
    
    ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
 
    // All that other stuff you usually put in here...
}

Now, if we execute the same code, we should see the values mapped properly.

Thursday, October 25, 2012

Knockoutjs ~ Int16 data mapping fails from JSON to .NET

We design class comprising of different data types based on our requirement. Assume there is a requirement to store the age of a person. In this case, we can specify any numerical datatype for Age like byte, short or int. In most cases we end up choosing int (Int32) datatype. But sometimes developer opts for either byte datatype or short (Int16) datatype.

Let’s assume that we have the following Person class with Age datatype as short (Int16).

public class Person
    {
        public Int32 Id { get; set; }
        public string Name { get; set; }
        public Int16 Age { get; set; }
    }

The corresponding class definition on the cshtml view (javascript side) will be defined as follows:

function Person (person) {
        var self = this;
        self.Id=ko.observable(person.Id);
        self.Name=ko.observable(person.Name);
        self.Age=ko.observable(person.Age);
    }

The following would normally be our method to send the information back to the server:

        self.save = function (person) {               
            $.ajax({
                type: "POST",
                url: "Save",
                data: ko.toJSON(self),
                dataType: 'json',
                contentType: 'application/json;charset=UTF-8',
                success: function (result) {
                    alert("success");
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    alert("save error status: " + xhr.status);
                    alert("save error thrown: " + thrownError);
                }
            });
        };

And the following would be our ASP.NET code to capture the information:

        [HttpPost]
        public JsonResult Save(Person person)
        {
            if (person == null) throw new ArgumentNullException("person");

            var personBusiness = new PersonBusiness();

            var result = person.Id == 0 ? personBusiness.Insert(person) : personBusiness.Update(person);

            return Json(result);
        }

In the above case we would expect that the person object would have the information mapped properly provided by the user but unfortunately Age will hold value of 0 no matter what value is supplied by the user. It seems like the mapping of short datatype is not properly implemented. This becomes evident when we change the datatype of Age from Int16 to Int32. Now on the save action we can see that the Age attribute will has the value as set by the user.

Hope this solves someone facing such issue.

Thursday, October 18, 2012

"Dynamic" deep validation with KnockoutJS model

In this article, Knockout Model and Validating "inner" Models, we have seen how deep validation is done. Here is a snippet of that validation:
 
self.save = function () {
if (self.errors().length != 0) { //verifying the errors attribute using the count of errors
self.errors.showAllMessages(); //displaying the corresponding error messages
return; //returning the control providing user a chance to correct the issues
}
 
In the above method, we are simply invoking the showAllMessages method from the errors member. This will work for on occasion but sometimes this will fail and the validation error messages will not be displayed. In this situation, there is a workaround using which we can achieve this validation functionality working. The workaround is to add errors member to javascript class and then invoike the showAllMessage method for each of the instance of the class as shown below:
 
function Contact(id, phoneType, phone) {
var self = this;
self.ClientId = ko.observable(id);
self.PhoneType = ko.observable(phoneType).extend({ required: true });
self.Phone = ko.observable(phone).extend({ required: true });
self.errors = ko.validation.group(self); //adding the errors object for javascript class making each instance to hold the error individually
};
 
function ClientModel (id, firstName, lastName, email, contacts) {
var self = this;
self.Id = ko.observable(id);
self.FirstName = ko.observable(firstName).extend({ required: true, maxLength: 50 });
self.LastName = ko.observable(lastName).extend({ required: true, maxLength: 50 });
self.Email = ko.observable(email).extend({ maxLength: 50, email: true });
 
self.Contacts = ko.observableArray(ko.utils.arrayMap(contacts, function (aContact) { return aContact; }));
 }
 
Next we will update the save method as follows to perform the validation:
 
self.save = function () {
       var errorCount = 0;
ko.utils.arrayForEach(self.Contacts(), function(contact){
contact.errors.showAllMessages();
errorCount = errorCount + 1;
});
 
if (errorCount != 0) {
return;
       }
 
       //save code block
}
 
Hope this helps.