UPDATE
based on example data
{"$id":"1","message":"The request is invalid.","modelState": {"$id":"2","email":["This Email Already Exists!"]}}
A fragment for highlighting invalid elements will become
var handleError = function (jqXHR, textStatus, errorThrown) { var error = jqXHR.responseJSON; var message = error.message; var modelState = error.modelState; //highlight invalid fields $.each(modelState, function (key, value) { var id = "#" + key; //construct id var input = $(id); //get the element if(input) { //if element exists input.addClass('input-validation-error'); //update class } }); }
Original
The following POC was used to demonstrate the original problem.
Webapi
[HttpGet] [Route("testobject")] public IHttpActionResult TestObject() { ModelState.AddModelError("Email", "This Email Already Exists!"); return BadRequest(ModelState); }
MVC controller
[HttpGet, Route("")] public ActionResult Index() { var model = new TestVM(); return View(model); }
MVC View: Index
@model TestVM @{ ViewBag.Title = "Index"; } <div class="container"> <div class="form-group"> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(model => model.Email, new { data_bind = "value: Email", @class = "form-control" }) @Html.ValidationMessageFor(model => model.Email) </div> <button type="button" data-bind="click: testEmail" class="btn btn-success submit">Test</button> </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval", "~/bundles/knockout") <script type="text/javascript"> //Pay no attention to this. custom strongly typed helper for routes var url = '@(Url.HttpRouteUrl<TestsApiController>(c => c.TestObject()))'; $(function () { /*using knockout for binding*/ function viewModel() { var self = this; //properties self.Email = ko.observable(@(Model.Email)); //methods self.testEmail = function () { $.ajax({ url: url, type: 'Get', contentType: 'application/json', dataType: 'json', success: handleResponse, error: handleError, }); }; var handleError = function (jqXHR, textStatus, errorThrown) { var error = jqXHR.responseJSON; console.log(JSON.stringify(error)); var message = error.Message; var modelState = error.ModelState; //highlight invalid fields $.each(modelState, function (key, value) { var id = "#" + key; $(id).addClass('input-validation-error'); //get validation message var valmsg = $("[data-valmsg-for='" + key + "']"); if (valmsg) { valmsg.text(value.join()); valmsg.removeClass("field-validation-valid"); valmsg.addClass("field-validation-error"); } }); } var handleResponse = function (data) { //No-op }; } var vm = new viewModel(); ko.applyBindings(vm); }); </script> }
Using the above proof of concept, based on the original example in the question, the resulting model is returned like this:
{"Message":"The request is invalid.","ModelState":{"Email":["This Email Already Exists!"]}}
By focusing primarily on processing the error response, I was able to achieve the desired behavior using the following structure.
var handleError = function (jqXHR, textStatus, errorThrown) { var error = jqXHR.responseJSON; console.log(JSON.stringify(error)); //logs {"Message":"The request is invalid.","ModelState":{"Email":["This Email Already Exists!"]}} var message = error.Message; var modelState = error.ModelState; //highlight invalid fields $.each(modelState, function (key, value) { var id = "#" + key; $(id).addClass('input-validation-error'); //get validation message var valmsg = $("[data-valmsg-for='" + key + "']"); if (valmsg) { valmsg.text(value.join()); valmsg.removeClass("field-validation-valid"); valmsg.addClass("field-validation-error"); } }); }
When performing the above result

From the point of view which had the following
<div class="container"> <div class="form-group"> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(model => model.Email, new { data_bind = "value: Email", @class = "form-control" }) @Html.ValidationMessageFor(model => model.Email) </div> <button type="button" data-bind="click: testEmail" class="btn btn-success submit">Test</button> </div>