Search code examples
asp.net-mvcclient-sideunobtrusive-validationcustom-attributes

Client-Side unobtrusive validation not working as expected


I am implementing Custom Unobtrusive client-side JavaScript for an MVC Application. The custom validation method ([EnforceTrue] Attribute) does fire when I press the submit button, but it does not have any effect. In the submit event, The Form.valid() method returns true, in spite of the custom method returning a false value. (I hardcoded it to false).

I have read up on Stackoverflow on the topic of Unobtrusive validation and found a myriad of designs , options, ideas, suggestions etc that did not exactly help. I tried referencing code libraries through nuget packages. I moved my code to the top of my View instead of having it at the bottom. I tried this with more complex validation logic but it still behaves the same. Eventually I scaled down my app to a few lines of code, to reproduce the behaviour.

This is my model: It contains a required Name field and a checkbox that needs to be checked, before submission. The checkbox requires the custom validation.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Reflection; // AtLeastOneProperty
using System.Web.Mvc; // Client side Validation STuff (26/08/19)

namespace WebApplication7_POC.Models
{
    public class Consultant
    {

        [Required, Display(Name = "First name:"), MaxLength(80)]
        public string FIRSTNAME { get; set; } // FIRSTNAME

        [Display(Name = "I give my concent.")]
        [EnforceTrue]  // TEST 123
        public bool Concent { get; set; } // AccessAdditional

    }


    public class EnforceTrueAttribute : ValidationAttribute, IClientValidatable
    {
        public override bool IsValid(object value)
        {
            if (value == null) return false;
            if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
            return (bool)value == true;
        }

        public override string FormatErrorMessage(string name)
        {
            return "The '" + name + "' field must be checked in order to continue.";
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            yield return new ModelClientValidationRule
            {
                ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.DisplayName) : ErrorMessage,
                ValidationType = "enforcetrue"
            };
        }
    }
}

Below is the View. It implements the JQuery for the Custom validation at the top. On the botom, it catches the submit event and then checks if the form has validated. valid().

@model WebApplication7_POC.Models.Consultant

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

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

<script type="text/javascript">

    (function ($) {

        // TEST 123

        $.validator.addMethod("enforcetrue", function (value, element, param) {

            alert('client validation trigerred!');
            console.log(element);
            console.log(element.checked);

            return false;  // element.checked;
        });

        $.validator.unobtrusive.adapters.addBool("enforcetrue");


    }(jQuery));

</script>



@{
    ViewBag.Title = "Consultant";
}

<h2>Consultant</h2>


@using (Html.BeginForm("Consultant","Home",FormMethod.Post,new { id = "HelloForm" }))
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Consultant</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

        <div class="form-group">
            @Html.LabelFor(model => model.FIRSTNAME, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.FIRSTNAME, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.FIRSTNAME, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Concent, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <div class="checkbox">
                    @Html.EditorFor(model => model.Concent)
                    @Html.ValidationMessageFor(model => model.Concent, "", new { @class = "text-danger" })
                </div>
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Submit it!" class="btn btn-default" />
            </div>
        </div>

    </div>
}



@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}


<script type="text/javascript">


    $(document).ready(function () {

        $("#HelloForm").submit(function () {

            alert('HelloForm submit trigerred');

            // https://stackoverflow.com/questions/53934575/asp-net-core-unobtrusive-client-side-validation-form-onsubmit-fires-even-on-vali

            if ($("#HelloForm").valid()) {
                // Show the Spinner

                alert('Valid!');


                // Sure this works, but it does not validate the custon validation rules.


                // $("#LoaderSpinner").show('hidden');
                // $(".overlay").show();

            } else {

                alert('Not Valid');

            }

        });


    });

</script>

There is a controller that handles the Postback, but my issue is doing the client side validation.

When the submit button is pressed I expect the valid() method to return false when the checkbox is unchecked and show the error message on the screen (like the default [Required] attribute is doing).

Maybe there is just an oversight on my side somewhere?


Solution

  • Removing the

    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }
    

    snippet, that included jquery a second time, resolved the issue eventually.