Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Server Side Validation with .NET WebAPI

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");
            }
like image 995
Grizzly Avatar asked Jun 26 '17 18:06

Grizzly


People also ask

What is server side validation in C#?

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.

Is validation handled differently in Web API than in MVC?

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.


1 Answers

UPDATE

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
        }            
    });
}

Original

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

image

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>
like image 177
Nkosi Avatar answered Sep 27 '22 16:09

Nkosi