For arguments sake let's say that I am on the Create View. If I left all of the textboxes empty and hit submit, I would get returned the same form but with validation messages under each textbox that was required, and that was done by client side validation. Now, when that happens, each textbox is decorated with a class name called input-validation-error
, and if I style that I can make the box turn red to make it stand out more to the user.
But now, let's say that one of the textboxes requires an email address. Email addresses are unique so in my webapi controller I have this:
// POST: api/ControllerName
[ResponseType(typeof(TestObject))]
public IHttpActionResult PostTestObject(TestObject testObject)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (
db.TestObjects.Any(
x =>
x.Email.Equals(testObject.Email, StringComparison.CurrentCultureIgnoreCase) &&
x.ID != testObject.ID))
{
ModelState.AddModelError("Email", "This Email Already Exists!");
return BadRequest(ModelState);
}
db.TestObjects.Add(testObject);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = testObject.ID }, testObject);
}
In my Create View I have this to display that exception message:
.error(function (jqXHR, textStatus, errorThrown) {
var status = capitalizeFirstLetter(textStatus);
var error = $.parseJSON(jqXHR.responseText);
toastr.error(status + " - " + error.exceptionMessage);
});
This displays the exception message in a toastr notification. But it doesn't give the email textbox a class name of input-validation-error
, and I would like it to so that it will display the textbox in red.
Is there a way in WebApi controller methods to return something that will add that class to that textbox? I know in regular .Net controllers I could do
ModelState.AddModelError("Email", "This email already exists!")
return View(testObject);
That would return the view with that textbox having the css class name.
Any help is appreciated.
Based on Nkosi's answer below:
When I console.log(JSON.stringify(error));
The response is this:
{"$id":"1","message":"The request is invalid.","modelState":
{"$id":"2","email":["This Email Already Exists!"]}}
Okay, so I have changed the formatting to fit the JSON response, and I have also changed the var id
line to var id = "#" + key.replace('$', '');
Now I am receiving an error on valmsg.text(value.join());
saying Object doesn't support property or method 'join'
.. so I consoled the value and it is 2
.. not "This Email Already Exists!"
UPDATE
.error(function (jqXHR, textStatus, errorThrown) {
var error = jqXHR.responseJSON;
console.log(JSON.stringify(error));
var message = error.message;
var modelState = error.modelState;
$.each(modelState,
function (key, value) {
var id = "#" + key.replace('$', '');
var input = $(id);
console.log(id); // result is #id
if (input) { // if element exists
input.addClass('input-validation-error');
}
//get validation message
var valmsg = $("[data-valmsg-for='" + key + "']");
if (valmsg) {
valmsg.text(value.join()); // Object doesn't support property or method 'join'
valmsg.removeClass("field-validation-valid");
valmsg.addClass("field-validation-error");
}
Server-side validation helps prevent users from bypassing validation by disabling or changing the client script. Security Note: By default, ASP.NET Web pages automatically validate that malicious users are not attempting to send script or HTML elements to your application.
We can perform model validation in MVC in an easy way. In API, when sending the request by the client or user to web API, then before doing the next process, it checks the empty validation and handles the validation error using data annotation in WEB API.
based on this sample data
{"$id":"1","message":"The request is invalid.","modelState":
{"$id":"2","email":["This Email Already Exists!"]}}
The snippet to highlight the invalid elements would 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
}
});
}
The following POC was used to demonstrate the original issue
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 returned looked like this.
{"Message":"The request is invalid.","ModelState":{"Email":["This Email Already Exists!"]}}
Focusing primarily on handling the error response returned 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");
}
});
}
The above when executed resulted in
From a view that 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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With