0

I have a simple form with 2 buttons (Cancel and Submit) and a TextArea. The user types a list of email addresses and presses submit. I am trying to figure out why my custom message is not being shown when I submit my form. I know the validation logic works as it triggers my [Required] rule and I can see the error message for that:

enter image description here

However, when I type in data such as "test@" and then submit, the logic in the Validate gets triggered but I can't see my error message "Please make sure that all of the emails are valid". What am I doing wrong?

That is my Model:

public class ShareModel : IValidatableObject
{
    [HiddenInput] public string Title { get; set; }

    [Required]
    public string Emails { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        // custom validation logic here
        yield return new ValidationResult($"Please make sure that all of the emails are valid", new[] { "Emails" });
    }
}

That is my view:

<div class="modal fade" id="shareFormModal" role="dialog">
<div class="modal-dialog modal-md">
    <!-- Modal content-->
    <div class="modal-content">
        <div class="modal-header">
            <h4 class="modal-title">Share Workbook - @Model.Title</h4>

        </div>

        @using (Html.BeginForm("ShareWorkbook", "Home", FormMethod.Post, new {@id = "partialform"}))
        {
            <div class="modal-body">

                <label>@BaseLanguage.Share_workbook_Instruction_text</label>
                <div class="form-group">
                    <textarea class="form-control" asp-for="Emails" rows="4" cols="50" placeholder="@BaseLanguage.ShareDialogPlaceholder"></textarea>
                    <span asp-validation-for="Emails" class="text-danger"></span>
                </div>

                <input asp-for="Title"/>
            </div>
            <div class="modal-footer">
                <button type="submit" class="btn btn-primary">Share</button>
                <button id="btnCancelDialog" type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
            </div>
        }

    </div>
</div>
halfer
  • 19,824
  • 17
  • 99
  • 186
Georgi Koemdzhiev
  • 11,421
  • 18
  • 62
  • 126

2 Answers2

1

According to this post IValidatableObject is not considered on client-side. In order to display custom error message from custom validation you need to implement custom ValidationAttribute that also implements IClientModelValidator interface as described here.

Alexander
  • 9,104
  • 1
  • 17
  • 41
  • Thank you for your suggestion. However, it still does not work - I can't see my custom error message. I created a gist with what I have here if you can have a look, perhaps I missed something: https://gist.github.com/georgikoemdzhiev/a8cf6877667a502b38e31219814ca398 – Georgi Koemdzhiev Apr 12 '19 at 08:11
  • 1
    @GeorgiKoemdzhiev You are missing `options.rules.isEmail = {};` line. Insert it before `options.messages["isEmail"] = options.message;` – Alexander Apr 12 '19 at 12:02
  • Thank you very much! That worked! Can I ask why I had to add the above `options.rules.isEmail = {};` ? – Georgi Koemdzhiev Apr 12 '19 at 13:34
  • 1
    @GeorgiKoemdzhiev It seems like jQuery searches if there is such `rule` to handle validation. When you did not add it, validation didn't find suitable `rule` to process validation. – Alexander Apr 12 '19 at 16:55
  • Thank you for clarifying, now it does make more sense why it is working. Much appreciated! :) – Georgi Koemdzhiev Apr 15 '19 at 07:12
0

For future reference, as Alexander explained above I had to use both ValidationAttribute, IClientModelValidator like that:

ShareModel:

public class ShareModel
{
    [HiddenInput] public string Title { get; set; }

    [Required]
    [IsEmailAttribute(ErrorMessage = "Check all of the emails you have typed")]
    public string Emails { get; set; }
}

public class IsEmailAttribute : ValidationAttribute, IClientModelValidator
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
         return new ValidationResult("Check emails!");
    }

    public void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val", "true");
        var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
        MergeAttribute(context.Attributes, "data-val-isEmail", errorMessage);
    }

    private bool MergeAttribute(
        IDictionary<string, string> attributes,
        string key,
        string value)
    {
        if (attributes.ContainsKey(key))
        {
            return false;
        }
        attributes.Add(key, value);
        return true;
    }

}

_ShareView.cshtml:

@using DNAAnalysisCore.Resources
@model DNAAnalysisCore.Models.ShareModel

<!-- Modal -->
<div class="modal fade" id="shareFormModal" role="dialog">
    <div class="modal-dialog modal-md">
        <!-- Modal content-->
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">Share Workbook - @Model.Title</h4>

            </div>

            @using (Html.BeginForm("ShareWorkbook", "Home", FormMethod.Post, new {@id = "partialform"}))
            {
                <div class="modal-body">

                    <label>@BaseLanguage.Share_workbook_Instruction_text</label>
                    <div class="form-group">
                        <textarea class="form-control" asp-for="Emails" rows="4" cols="50" placeholder="@BaseLanguage.ShareDialogPlaceholder"></textarea>
                        <span asp-validation-for="Emails" class="text-danger"></span>
                    </div>

                    <input asp-for="Title"/>
                </div>
                <div class="modal-footer">
                    <button type="submit" class="btn btn-primary">Share</button>
                    <button id="btnCancelDialog" type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                </div>
            }

        </div>
    </div>
</div>

index.cshtnl:

@using DNAAnalysisCore.Resources
@model DNAAnalysisCore.Models.WorkBookModel
@{
}

@section BodyFill
{
    <div id="shareFormContainer">
        <!--PLACEHOLDER FOR SHARE DIALOG -->
        @{
            @Html.Partial("_ShareView", new ShareModel())
        }

    </div>

    <div class="workbook-container">
        <table class="table">
            <tbody>
                @foreach (var workbook in Model.Workbooks)
                {
                    <tr>
                        <td>@Html.ActionLink(workbook.Name, "Open", "OpenAnalytics", new { id = Model.Id, workbook = workbook.Name })</td>
                        <td>
                            <button title="Share" class="share-button" onclick='showSharingView("@workbook.Name")'>&nbsp;</button>
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
}

@section Scripts
{
    <!--Load JQuery 'unobtrusive' validation -->
    @await Html.PartialAsync("_ValidationScriptsPartial")
    <script type="text/javascript">

        function showSharingView(title) {
            var url = "@Url.Action("ShowShareDialog", "Home")" + "?workbookTitle=" + encodeURI(title);
            $('#shareFormContainer').load(url,
                function() {
                    $('#shareFormModal').modal("show");
//                    // We need to manually register the form for validation as the dialog is
//                    // not included in the page when it initially loads
                    $.validator.unobtrusive.parse("#partialform");

                    // email validation
                    $.validator.addMethod("isEmail",
                        function (value, element, parameters) {
                            // TODO CLIENT SIDE VALIDATETION LOGIC HERE
                            return false;
                        });
                    $.validator.unobtrusive.adapters.add("isEmail",
                        [],
                        function(options) {
                            options.rules.isEmail = {};
                            options.messages["isEmail"] = options.message;
                        });
                });
        }

    </script>
}
Georgi Koemdzhiev
  • 11,421
  • 18
  • 62
  • 126