简体   繁体   中英

Custom validation for a EditorTemplate ASP MVC 5

I have the following models

public class A
{
    public string Ecuation { get; set; }
    ...
    public B B { get; set; }
}
public class B // validation attributes omitted
{
    public int Number1 { get; set; }
    public int Number2 { get; set; }
    public int Number3 { get; set; }
}

and an EditorTemplate for B (its reused in other views)

@model B
<div id="EcuationGenerator">
    @Html.EditorFor(x => x.Number1)
    @Html.ValidationMessageForBootstrap(x => x.Number1)
    .... // ditto for other properties
    <button id="sendEcuation">Fill Ecuation</button>
</div>

<script>
    $('#sentEcuation').on('click', function (e) {
        e.preventDefault();
        $.ajax({
            cache: false,
            type: "POST",
            url: '@Url.Action("ValidateB")',
            data: $('#EcuationGenerator :input').serialize(),
            dataType: "json"
        })
        .done(function (data) {
            if (data.Valid) {
                // working here about how can i get the 'Ecuation' to fill the input
                return;
            }
            $.each(data.Errors, function (key, value) {
                // The following is not working to update the error messages
                if (value != null) {
                    key = key.replace('.', '\\.');
                    $(":input[name='" + key + "']").addClass("is-invalid");
                    key = key.replace('[', '\\['); key = key.replace(']', '\\]');
                    $("#Err_" + key).text(value[value.length - 1].ErrorMessage);
                }
            });
        })
        .fail(function (xhr) {
            alert(xhr.responseText);
            alert("Critical Error!. Failed to call the server.");
        });
    });
</script>

Where the ValidationMessageForBootstrap() extension method is

public static MvcHtmlString ValidationMessageForBootstrap<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, object htmlAttributes = null) 
{ 
    if (expression == null) { throw new ArgumentNullException("Campo vacío"); } 
    string result = string.Empty; 
    try 
    { 
        Func<TModel, TValue> deleg = expression.Compile(); 
        result = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression)); 
    }
    catch { } 
    var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); 
    attributes["class"] += " invalid-feedback"; 
    attributes["id"] += "Err_" + result; 
    TagBuilder generador = new TagBuilder("div"); 
    generador.MergeAttributes(new RouteValueDictionary(attributes)); 
    return new MvcHtmlString(generador.ToString()); 
}

and the main view is

@model A
....
@Html.EditorFor(x => x.Ecuation, new {htmlAttributes = new { @readonly = "readonly" }})
<button class="btn btn-outline-dark rounded-right" data-keyboard="false" data-toggle="modal" data-target="#modal" id="abrirConexionado" type="button">Fill Ecuation!</i></button>

<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-body">
                @Html.EditorFor(m => m.B })
            </div>
        </div>
    </div>
</div>

When I click the button in the template, I make an ajax call to post the value of the inputs in the template to a controller method that perform some server side calculations. If the value of the inputs are not valid I get the ModelState errors and return then in a JsonResult , which I then want to use to update the message in the element generated by my ValidationMessageForBootstrap() methods.

The controller method is

public JsonResult ValidateB(B b)
{
    var valid = TryUpdateModel(b)
    if(valid)
    {
        error = false;
        //More Logic
    }
    return Json(new 
    {
        EcuationResult = //logic,
        Valid = valid,
        Errors = getErrorsForModel()
    });
}

The getErrorsForModel() method returns a IDictionary<string, object> with the ModelState errors.

My problem is that the names of the properties returned in the JsonResult are just Number1 etc and do not include the full name (ie B.Number1 ) which is generated in the view, and my code in the ajax .done() function is not updataing the messages.

How can I find the correct elements in the view in order to update the error messages?

The first thing you need to do is delete that ValidationMessageForBootstrap() extension method and use the inbuilt @Html.ValidationMessageFor() which correctly generates the placeholder for client and server side validation messages. If you inspect the <span> that it generates, it has a data-valmsg-for attribute whose value is the full name of the property which you can use to find the the message placeholder associated with a property.

Next delete the scripts from your template - scripts go in the main view or its layout, not in partial views.

Your EditorTemplate should be

@model B
<div id="EcuationGenerator">
    @Html.EditorFor(x => x.Number1)
    @Html.ValidationMessageFor(m => m.x.Number1) // add
    ... // ditto for other properties
    <button type="button" id="sendEcuation">Fill Ecuation</button> // add type="button"
</div>

Then change the code in the server method to get a collection of the validation messages using

var errors = ModelState.Keys.Where(k => ModelState[k].Errors.Count > 0)
    .Select(k => new { propertyName = k, errorMessage = ModelState[k].Errors[0].ErrorMessage });

and assign that to the Errors property of your JsonResult .

You can then determine the 'prefix' of the property name using the javascript .split() , pop() and .join() methods, and append the propertyName returned in the errors collection to find the associated input and its message placeholder, then update the message text and class names of those elements

And then the script (in the main view) will be

$('#sentEcuation').click(function() {
    // Determine the prefix of the property names
    var fullName = $('#EcuationGenerator').find('input').first().attr('name'); // returns "B.Number1"
    var parts = fullName.split('.'); // returns [ "B", "Number1" ]
    parts.pop(); // returns [ "B" ]
    var prefix = parts.join('.') + '.'; returns "B."
    $.ajax({
        ....
    })
    .done(function (data) {
        if (data.Valid) {
            // Update input with the result
            $('#Ecuation').val(data.EcuationResult);
            return;
        }
        $.each(data.Errors, function (index, item) {
            var fullName = prefix + item.propertyName;
            // Get the validation message placeholder
            var element = $('[data-valmsg-for="' + fullName + '"]');
            // Update message
            element.append($('<span></span>').text(item.errorMessage));
            // Update class names
            element.removeClass('field-validation-valid').addClass('field-validation-error');
            $('[name="' + fullName + '"]').removeClass('valid').addClass('input-validation-error');
        });
    })
    .fail(function (xhr) {
        ....
    });
});

To avoid unnecessary ajax calls, you should also validate the inputs in your EditorTemplate and if not valid, then cancel the ajax call. For an example of validating just the form controls in your template, refer MVC Force jQuery validation on group of elements

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM