Server side validation using .NET WebAPI

For arguments, let's say I'm in Create View. If I left all the text fields empty and click submit, I would get the same form returned, but with validation messages under each text field that was needed, and this was done using client-side validation. Now that this happens, each text field is decorated with a class name called input-validation-error , and if I style, then I can make the window red so that it stands out more for the user.

But now let's say that one of the text fields 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 this exception message:

 .error(function (jqXHR, textStatus, errorThrown) { var status = capitalizeFirstLetter(textStatus); var error = $.parseJSON(jqXHR.responseText); toastr.error(status + " - " + error.exceptionMessage); }); 

Displays an exception message in the toastr notification. But this does not give the email text field the class name input-validation-error , and I would like it to display the text field in red.

Is there a way in the methods of the WebApi controller to return something that adds this class to this text box? I know that in regular .Net controllers I could do

 ModelState.AddModelError("Email", "This email already exists!") return View(testObject); 

This will return a view with this text field having the css class name.

Any help is appreciated.

Based on Nkosi's answer below:

When I console.log(JSON.stringify(error));

The answer is:

 {"$id":"1","message":"The request is invalid.","modelState": {"$id":"2","email":["This Email Already Exists!"]}} 

Ok, so I changed the formatting according to the JSON answer, and I also changed the var id line to var id = "#" + key.replace('$', '');

Now I get the error valmsg.text(value.join()); saying Object doesn't support property or method 'join' .. so I comforted the value and this 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"); } 
+7
c # css validation asp.net-web-api asp.net-mvc-5
source share
6 answers

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

image

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> 
+2
source share

You tried

return BadRequest("This Email Already Exists!");

another version of BadRequest instead of throwing an exception?

0
source share

When your view / html calls the Web API method, the web API basically has no idea that your pages or input fields even exist. It purely receives some input and returns some result. In this case, since you selected the exception in the Web API, the HTTP response then represents the 500 error code for the โ€œinternal server errorโ€. This will start the xhr error handler. This is not ideal, because something else has ever gone wrong (database failure, dropped connection with the client, etc.), you will see an email verification error. A better idea would be to return a more informative answer giving the results of the test in each field, but then you need a type of response with a little more in it than TestObject, something like a result in which the result has some fields for validation errors.

As a quick workaround, you probably want to use some front end interface library to manually add this class to the field. An example in jQuery, immediately before / after the toastr line:

$('#emailfield').addClass('input-validation-error')

0
source share

You need to add the input-validation-error class to your .error function in the desired text field or control.

register after the script to verify

 $(".selector").validate({ highlight: function(element, errorClass) { // Override the default behavior here } }); 
0
source share

If you return to the controller:

 ModelState.AddModelError("Email", "This Email Already Exists!"); return BadRequest(ModelState); 

Then the returned json should be:

 {"Email":["This Email Already Exists!"]} 

In the HTML output on your view, you should have an input for which the name attribute will be set to the email address:

 <input name="Email" type="text" /> 

Similarly, all other keys in the JSON error will have corresponding form elements with name attributes corresponding to these keys.

So, in your error function, you can scroll through the keys and apply the appropriate CSS class:

 .error(function (jqXHR, textStatus, errorThrown) { var error = $.parseJSON(jqXHR.responseText); $(error).each(function(i,e) { $('[name="' + e + '"]').addClass('input-validation-error'); }); }); 
0
source share

I would recommend you use FluentValidation .

Check out this great article.

Thus, using this approach, you can move the verification logic from the controller and make error responses following the pattern.

But to add client-side css classes, you have to replace public List<string> Errors { get; set; } public List<string> Errors { get; set; } public List<string> Errors { get; set; } on ResponsePackeage on public Dictionary<string, string> Errors { get; set; } public Dictionary<string, string> Errors { get; set; } public Dictionary<string, string> Errors { get; set; } , where the key will be the name of the property and this value will be associated with the error message.

Good luck

0
source share

All Articles