Click here to Skip to main content
15,885,366 members
Articles / Web Development / HTML
Tip/Trick

Custom MVC Validations – A Quick Walkthrough

Rate me:
Please Sign up or sign in to vote.
4.60/5 (6 votes)
27 Sep 2015CPOL7 min read 26.8K   416   15  
A quick walkthrough for implementing client and server side validations in a variety of ways in ASP.NET MVC projects.

Introduction

This tip is aimed at helping developers to quickly setup client and server side validations in ASP.NET MVC based projects. Tips for validations using Jquery Validation Plugin and MVC DataAnnotation have been covered. Easy to edit code snippets have been included.

Setting up your project for client side validations

Include client validation library scripts

To get started with client side validation quickly, we can make use of jquery based validation libraries. If you do not have jquery.validate.min.js and jquery.validate.unobtrusive.min.js in your Project's Scripts folder, then go ahead and use the Nuget Manager from the Project menu or the Package Manager Console from the Tools menu to install packages for these two libraries. In the Nudget Manager you can search and install “jquery.Validation” and “jquery.Validation.Unobtrusive” packages.

Add Reference to script files

To use the libraries we installed using the Nuget Package Manager, include jquery.validate.min.js and jquery.validate.unobtrusive.min.js script files using the <script> as given below or better yet, use bundling! Add the following tags right before the </body> tag in your _Layout.cshtml page or wherever you want to implement the client side validation. As these are dependent on JQuery, you need to include JQuery script file before including these two script files.

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"></script>

Enable client validation and unobtrusive JavaScript

In your web.config, include the following app settings key/value pairs if they are not already present. Make sure that the value attribute is set to “true” else things may not work the way we want it.

<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />

Develop client side and server side validation logic

Client Side Validations (by implementing logic within plugin's addMethod())

Create the Validation Rule

Create a JQuery validation rule called, “NonNumeric”. This validation rule will ensure that the text field to which it is applied does not contain numeric data. Write the following JavaScript code within the <script> tag, or in a separate “.js” file and include the script file in your page.

jQuery.validator.addMethod("nonnumeric",
    function (value, element, params) {
        $.validator.messages.nonnumeric = 
            jQuery.validator.format("This field cannot contain numeric characters.")
        return value.match(/\d+/g) == null;
    },
    $.validator.messages.nonnumeric
);

Implement the Validation Rule

After the custom JavaScript validation code is ready, add it as a rule to your fields which need it. In the example here, we are applying the NonNumeric validation rule to field, “txtPlanetName”. You can write this code in the document ready function of your page.  

$("#txtPlanetName").rules("add",
{
    nonnumeric: true,
    messages: {
        nonnumeric: "My Error Message: Planet Name cannot contain numeric characters."
    }
});

Client Side Validations (by implementing logic within a general Javascript method)

Create a Jquery validation rule called, “TriggerError”. Unlike our previous example, this validation rule does not contain its own validation logic. Adding this rule will automatically trigger the validation error along with an existing list of validation failures. Hence, this validation rule needs to be applied to a field which has invalid data. This technique may be helpful in situations where an existing validations logic needs to be used and integrated with other JQuery plugin based validations.

Create the Validation Rule

Create a Jquery validation rule called, “TriggerError”. Whenever this validation rule is added to a field, the field is considered to have an invalid data. In the code below, you can see that this rule always returns, false. This means that when the JQuery validator tries to execute this validation rule, it will fail each and every time. This is pretty useful in places where you want to use the existing validation logic. You can then add the validation errors along with the general set of validation errors which appear using the @Html.ValidationSummary() or @Html.ValidationFor() HTML helpers. Write the following JavaScript code within the <script> tag, or in a separate “.js” file and include the script file in your page.

jQuery.validator.addMethod("triggererror",
    function (value, element, params) {
        $.validator.messages.triggererror = 
            jQuery.validator.format("This field contains invalid data.");
        return false;
    },
    $.validator.messages.triggererror
);

Implement the Validation Rule

Once the custom JavaScript validation code is ready, you can add it as a rule to your fields which need it. In the example here, we are applying the TriggerError validation rule to field, “txtNumberOfMoons”. You can write this code in the document ready function of your page. 

if (parseInt($("#txtNumberOfMoons").val()) < 0) {
    $("#txtNumberOfMoons").rules("add",
    {
        triggererror: true,
        messages: {
            triggererror: "My Error Message: Numbers of Moons " + 
                "field should have a value greater than or equal to 0."
        }
    });
}

Server Side Validations

We will create a ValidationAttribute called, “RequiredIfChecked”. This validation ensures that the text field contains data when a related check box is checked. For example, you may want the user to provide an anniversary date incase you are married.

Create the Validation Attribute

Create a C# validation attribute rule called, “RequiredIfChecked”. Whenever this custom validation attribute is applied to a class property, the associate validation logic is executed. To do this, we need to create a class which inherits from ValidationAttribute class and implements a method IsValid(). This is the method where you would be writing your validation logic. You can write the following code in a new class file, RequiredIfChecked.cs. Do note that, the Utility class is a custom class which can be reused to quickly get values using commonly used lines of code. The Utility class source code follows at the end and has also been provided in the source code download available with this write up.

public class RequiredIfChecked : ValidationAttribute
{
    private const string _DefaultErrorMessage = "{0} field is required when {1} field is selected.";

    private string _DependentPropertyName;

    public RequiredIfChecked(string dependentPropertyName)
    {
        _DependentPropertyName = dependentPropertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        bool isChecked = false;
        //Get details about the dependent property
        PropertyInfo dependentProperty = 
            Utility.GetPropertyInfo(validationContext, _DependentPropertyName);
        string dependentPropertyValue = 
            Utility.GetPropertyValueAsString(validationContext, _DependentPropertyName);

        if (bool.TryParse(Convert.ToString(dependentPropertyValue), out isChecked))
        {
            //Validation should fail if the checkbox is selected but the main field is empty
            if (isChecked
                    && string.IsNullOrEmpty(Convert.ToString(value)))
            {
                return new ValidationResult(Utility.GetValidationErrorMessage(this,
                                                        _DefaultErrorMessage,
                                                        Utility.GetDisplayName(validationContext),
                                                        Utility.GetDisplayName(dependentProperty)));
            }
        }
        return ValidationResult.Success;
    }
}

Implement the Validation Attribute

Implementing the custom validation attribute is straight forward. As you can see in the example code below, we have applied the RequiredIfChecked validation attribute to our field, AtmosphereNotes. This validation will ensure that if the checkbox, “IsHabitable” is checked (having a value, true) then the field AtmoshpereNotes cannot be left blank. In MVC, you can apply the validation attributes to your Model or your ViewModel class properties as shown in the code shown below. 

[Display(Name = "Is Habitable")]
public bool IsHabitable { get; set; }

[RequiredIfChecked("IsHabitable",ErrorMessage = "My Error Message: {0} field is required when {1} field is selected.")]
[DataType(DataType.MultilineText)]
[Display(Name = "Atmosphere Notes")]
public string AtmosphereNotes { get; set; }

Client and Server Side Validations

We will create a Validation Attribute called, “RequiredIfNotEmpty”. This validation ensures that the text field contains data when a related text box has data. For example, you may want the user to provide a Postal Code when an Address has been provided.

Create the Server Side Validation Attribute

We will create a C# validation attribute rule called, “RequiredIfNotEmpty”. Whenever this custom validation attribute is applied to a class property, the associate validation logic  is executed. To do this, we need to create a class which inherits from ValidationAttribute class and implements a method IsValid(). This is the method where you would be writing your validation logic. You can write the following code in a new class file, RequiredIfNotEmpty.cs. Like in our previous example, do note that, the Utility class is a custom class which can be reused to quickly get values using commonly used lines of code. The Utility class source code follows at the end and has also been provided in the source code download available with this write up.

public class RequiredIfNotEmpty : ValidationAttribute
{
    private const string _DefaultErrorMessage = "{0} field is required when {1} field is not empty.";

    private string _DependentPropertyName;

    public RequiredIfNotEmpty(string dependentPropertyName)
    {
        _DependentPropertyName = dependentPropertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (string.IsNullOrEmpty(Convert.ToString(value)))
        {
            //Get details about the dependent property
            PropertyInfo dependentProperty = 
                Utility.GetPropertyInfo(validationContext, _DependentPropertyName);
            string dependentPropertyValue = 
                Utility.GetPropertyValueAsString(validationContext, _DependentPropertyName);

            //Validation should fail if dependent field has data but the main field is empty
            if (!string.IsNullOrEmpty(dependentPropertyValue)
                    && string.IsNullOrEmpty(Convert.ToString(value)))
            {
              return new ValidationResult(Utility.GetValidationErrorMessage(this,
                                                        _DefaultErrorMessage,
                                                        Utility.GetDisplayName(validationContext),
                                                        Utility.GetDisplayName(dependentProperty)));
            }
        }
        return ValidationResult.Success;
    }
}

Enable and link client side validation attribute

We need to do a bit extra to enable and link client side validation with the server side validation. It is important to understand that, both these codes are independent, and you need to ensure that the logic written using JavaScript is similar to the one written in C# for server side validation. To do this, start by implementing IClientValidatable interface in the class, RequiredIfNotEmpty. Next, include the following code within your class, RequiredIfNotEmpty.

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
    ModelClientValidationRule rule = new ModelClientValidationRule();
    rule.ValidationType = "requiredifnotempty";
    rule.ErrorMessage = Utility.GetValidationErrorMessage(this,
                       _DefaultErrorMessage,
                       metadata.GetDisplayName(),
                       Utility.GetDisplayName(metadata, context,_DependentPropertyName)); 
    rule.ValidationParameters.Add("param", _DependentPropertyName);
    yield return rule;
}

Create the Client Side Validation Rule 

Now write the following JavaScript code. The adapters provides additional data attributes to your HTML controls. You can use these attributes within your JavaScript validation logic whenever required. In our case, the param attribute value represents the name of the related field to identify whether it is empty.

jQuery.validator.unobtrusive.adapters.add("requiredifnotempty", ["param"],
    function (options) {
        options.rules["requiredifnotempty"] = options.params.param;
        options.messages["requiredifnotempty"] = options.message;
    }
);

jQuery.validator.addMethod("requiredifnotempty",
    function (value, element, param) {
        var isValid = true;
        if ($("[name='" + param + "']").val().trim().length != 0
            && $.trim(value).length == 0) {
            isValid = false;
        }
        return isValid;
    }
);

Implement the Validation Attribute

In the example code below, we have applied the RequiredIfNotEmpty validation attribute to our field, OrbitingStarName and StarAge properties. This ensures that a value for both the fields are required if either one of them has a value. In MVC, you can apply the validation attributes to your Model or your ViewModel class properties as shown in the code shown below. 

[RequiredIfNotEmpty("StarAge", 
    ErrorMessage = "My Error Message: {0} field is required when {1} field is not empty.")]
[Display(Name = "Orbiting Star Name")]
public string OrbitingStarName { get; set; }

[RequiredIfNotEmpty("OrbitingStarName", 
    ErrorMessage = "My Error Message: {0} field is required when {1} field is not empty.")]
[Display(Name = "Star Age")]
public string StarAge { get; set; }

Check for validation errors on the server

When the user posts a form by clicking on the submit button, the program control flows to the controller action. In your action method you can check whether or not the Model has valid data. To do this we can make use of ModelState object as shown below.

[HttpPost]
public ActionResult TestValidations(Planet viewModel)
{
    if(ModelState.IsValid)
    {
        //Save the provided data
    }
    else
    {
        //Tell the user to provide correct data
    }
    return View(viewModel);
}

Check for validation errors on the client

When the user clicks on the Submit button within a form tag, the JQuery validations are automatically triggered. Incase you have included a custom validation rule such as TriggerError as shown in one of our examples above, you would need to trigger the validation errors before the form can be submitted. To do this, in your view (.cshtml page) change the HTML button type to “button” from “submit” and use the code shown below.

function btnSubmit_Click() {
    $('#frmAddPlanet').validate();
           
    if (parseInt($("#txtNumberOfMoons").val()) < 0) {
        $("#txtNumberOfMoons").rules("add",
        {
            triggererror: true,
            messages: {
                triggererror: "My Error Message: Numbers of Moons field " + "
                    should have a value greater than or equal to 0."
            }
        });
    }

    if ($("#frmAddPlanet").valid()) {
        $("#frmAddPlanet").submit();
    }
}

Utility Class Source Code

public class Utility
{
    public static PropertyInfo GetPropertyInfo(ValidationContext validationContext, string propertyName)
    {
        return validationContext.ObjectInstance.GetType().GetProperty(propertyName);
    }

    public static string GetPropertyValueAsString(ValidationContext validationContext, 
        string propertyName)
    {
        return Convert.ToString(GetPropertyInfo(validationContext, propertyName).GetValue(validationContext.ObjectInstance, null));
    }

    public static string GetDisplayName(PropertyInfo propertyInfo)
    {
        string fieldDisplayName = propertyInfo.Name;

        if (propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true).Count() > 0)
        {
            fieldDisplayName = propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true).Cast<DisplayAttribute>().Single().Name;
        }

        return fieldDisplayName;
    }

    public static string GetDisplayName(ValidationContext validationContext)
    {
        return validationContext.DisplayName;
    }

    public static string GetDisplayName(ModelMetadata modelMetadata)
    {
        return modelMetadata.GetDisplayName();
    }

    public static string GetDisplayName(ModelMetadata modelMetadata, 
        ControllerContext context, string propertyName)
    {
        var parentMetaData = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model, modelMetadata.ContainerType);
        return parentMetaData.Single(x => x.PropertyName == propertyName).GetDisplayName();
    }

    public static string GetValidationErrorMessage(ValidationAttribute validationAttribute, 
        string defaultErrMsg, params object[] args)
    {
        string valErrMsg = string.Empty;
        if (!string.IsNullOrEmpty(validationAttribute.ErrorMessage))
            valErrMsg = validationAttribute.ErrorMessage;
        else
            valErrMsg = defaultErrMsg;

        valErrMsg = string.Format(valErrMsg, args);

        return valErrMsg;
    }
}

 

History

  • 27th September, 2015: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --