ASP.NET MVC: Enhancing The WebGrid - Validating Inline Editing

June 18th, 2015

In the last post, we discussed how to perform inline editing in a WebGrid. Today, we go over how to validate that entered data using SignalR.

After my last article about performing an inline edit on a WebGrid, I had a reader ask me about how to elegantly handle validation of a record. Since we are using SignalR, we might as well continue on with the example.

Here is how I envision the validation occurring in the WebGrid.

After we finish editing the record, we click the save button on the row. On the save button, we create the serialized version of the row in a record and we send that to the server for validation.

Whether it's successful or not, we send back a ValidationMessage that contains an error message if there is an error, whether it was successful or not, and the member that was affected.

Validation of the Data

But how do we validate the object?

The validation would occur on the server since we have a SignalR direct connection already. Usually, we want to place the business rules inside the business object. We can't have one without the other.

In our User class, we'll attach the IValidateableObject.

public class UserIValidatableObject

The IValidatableObject is located in the System.ComponentModel.DataAnnotations namespace.

Since we are using the IValidateableObject, we automatically get a definition for a Validate method that returns an IEnumerable of ValidationResults.

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
 
}

For this example, let's go with a simple validation of username length. Here is our updated User class.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
 
namespace WebGridExample.Models
{
    [Serializable]
    public class UserIValidatableObject
    {
        public int Id { getset; } // Id (Primary key)
        public string UserName { getset; } // UserName
        public DateTime LastLogin { getset; } // LastLogin
        public string FirstName { getset; } // FirstName
        public string LastName { getset; } // LastName
 
        public User()
        {
            LastLogin = System.DateTime.Now;
        }
 
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var list = new List<ValidationResult>();
 
            if (UserName.Length > 10)
            {
                var item = new ValidationResult("You cannot have a UserName with more than 10 characters",
                    new[] {"UserName"});
                list.Add(item);
            }
 
            return list;
        }
    }
}

We can now validate a User object successfully. Let's move to the SignalR Hub.

Creating the Validations

In our SignalR WebGridHub, we need a way to notify the client as to whether everything was saved or if we have an issue with the data entered.

Our message back to the client must be a simple POCO so it can be serialized as JSON on the client side. I try to keep these objects as simple as possible. No funny or complex types.

public class ValidationMessage
{
    public string Message { getset; }
    public string MemberName { getset; }
    public bool Success { getset; }
}

In my example, I just want one message returned.

When we validate the User object, our SignalR method will receive the object, run a validation on it, and return whether it was successful or not. Below is our GetValidation method.

public ValidationMessage GetValidation(User user)
{
    var message = new ValidationMessage();
    
    var validations = user.Validate(new ValidationContext(user));
    if (validations.Any())
    {
        message.Message = validations.First().ErrorMessage;
        message.MemberName = validations.First().MemberNames.First();
    }
    message.Success = !validations.Any();
 
    return message;                    
}

Notice the return statement at the end. We don't have to return Task from a SignalR method, but we need to make sure we use a promise on the client-side since we are making the call asynchronously.

Let's move to the client side with the save button click event.

The Client-Side Save-Button Click

We need to tweak this click event a little bit. Here is the finished JavaScript event.

$(".save-button").on("click"function () {
    var row = $(this).closest("tr");
 
    var record = getRecord(row);
    webGridHubClient.server.getValidation(record).done(function(result) {
        if (result.Success) {
            $("[id^=" + result.MemberName + "]", row)
                .css("border""")
                .removeAttr("title");
 
            webGridHubClient.server.saveRecord(record);
 
            $("td", row).each(function () {
                var cell = $(this, row);
                var inputValue = $("input", cell);
                $(".cell-value", cell).text(inputValue.val());
            });
 
            $(".record-toolbar li", row).toggleClass("hide");
            $("input, select, textarea", row).toggleClass("hide");
            $(".cell-value", row).toggleClass("hide");
        } else {
            $("[id^=" + result.MemberName + "]", row)
                .css("border""1px solid red")
                .attr("title", result.Message);
        }
    });
});

After we get the record by extracting it from the row, we need to validate it by calling the getValidation, but notice the promise at the end (the done method). This allows the function to complete successfully and when "done," it will pass the validationMessage through (as result) into the anonymous method.

If the validation was successful, we save the record by passing the object to SignalR once again and then hide the editing fields.

Notice the "else" at the bottom? When we validate the object, we need to visibly show the editbox requires some editing to fix the errors. We are using the MemberName to highlight the edit box with a red color and using the title to let the user know that they have an error in that box when they hover over the editbox.

The line right after the conditional test for result.Success at the top is to turn off the red border and remove the title attribute if the validation was successful.

One Last Thing...

If we click the Edit button on the row, we need to put the border and title back to the default settings.

On the edit button JavaScript click event, let's reset the CSS border and remove the title attribute (changes in bold).

$(".edit-button").on("click"function () {
    var row = $(this).closest("tr");
    $(".record-toolbar li", row).toggleClass("hide");
    // Copy value into the edit box.
    $("input, select, textarea", row).toggleClass("hide");
    $("td", row).each(function () {
        var cell = $(this, row);
        var cellValue = $(".cell-value", cell);
        if (cellValue.length > 0) {
            $("input", cell)
                .val(cellValue.text())
                .css("border""")
                .removeAttr("title");
        }
    });
    $(".cell-value", row).toggleClass("hide");
});

Conclusion

In today's post, we covered how to add validation to your business objects, used SignalR to perform the validations from the client-side, and provide feedback to the user on what to fix before saving their data.

I want to thank Phil Stanton for the comment and idea for today's post.

If you have another feature you would like to see in the WebGrid series, please post a comment below.

Series: Enhancing the WebGrid