客户端验证不使用重用和嵌套的复杂属性

时间:2018-03-31 15:36:08

标签: jquery asp.net-mvc asp.net-mvc-4 asp.net-mvc-3 unobtrusive-validation

我有一个asp.net MVC 5应用程序,我尝试在.cshtml文件中的不同位置重用嵌套的复杂视图模型类。重用的复杂视图模型名为SchoolPersonViewModel,具有许多属性,PhoneEmail属性的验证类似于"如果未提供电话,则必须提供电子邮件。如果提供了电话,则电子邮件是可选输入"。我写了一个自定义服务器和客户端验证,但它适用于服务器端。但客户端验证无法正常运行。例如,即使填充了关联的Email文本框,也会提示填充Phone文本框。请参阅附件。例如。enter image description here请帮忙。先谢谢你。

我知道问题来自错误:3个电子邮件文本框有3个验证属性,其值与data-val-emailrequired-stringphoneprop="Phone"相同。运行时的Phone值会导致歧义  (jQuery验证机器缺少唯一性)但我不知道如何解决它。请参阅下面的渲染属性。请帮忙。提前谢谢。

有关我的代码的详细信息:

在我的cshtml视图中,我将视图模型复杂类(SchoolPersonViewModel)调用了3次:一个用于Student,一个用于Father,一个用于{{1} }}

C#MVC模型类

Mother

验证属性

public class SchoolPersonViewModel
{
    [DisplayName("Phone")]
    public string Phone { get; set; }
    [DisplayName("Email")]
    [EmailRequired(StringPhonePropertyName = "Phone", ErrorMessage = "Email is required if Phone is not provided")]
    public string Email { get; set; }
    .... // other properties
}

public class StudentEnrollViewModel
{
    public SchoolPersonViewModel Student { get; set; }
    public SchoolPersonViewModel Father { get; set; }
    public SchoolPersonViewModel Mother { get; set; }
}

容器视图模型和jQuery验证代码:

// If Phone is not input then Email is required -- server and client side validation
public class EmailRequiredAttribute : ValidationAttribute, IClientValidatable
{
    public string StringPhonePropertyName { get; set; }

    protected override ValidationResult IsValid(object value, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
    {
        var phone = ValidatorCommon.GetValue<string>(validationContext.ObjectInstance, StringPhonePropertyName);
        var email = (string)value;
        if (!string.IsNullOrWhiteSpace(phone) || (string.IsNullOrWhiteSpace(phone) && !string.IsNullOrWhiteSpace(email)))
        {
            return ValidationResult.Success;
        }
        if (string.IsNullOrWhiteSpace(phone) && string.IsNullOrWhiteSpace(email))
        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }
        return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var modelClientValidationRule = new ModelClientValidationRule
        {
            ValidationType = "emailrequired",
            ErrorMessage = FormatErrorMessage(metadata.DisplayName)
        };

        modelClientValidationRule.ValidationParameters.Add("stringphoneprop", StringPhonePropertyName);
        yield return modelClientValidationRule;
    }
}

上述视图在运行时呈现如下:

@model ExpandoObjectSerializeDeserialize.Web.Models.StudentEnrollViewModel
....
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    ....
    @Html.EditorFor(m => m.Student)
    ....
    @Html.EditorFor(m => m.Father)
    ....
    @Html.EditorFor(m => m.Mother)

    <input type="submit" value="Create" class="btn btn-default" />
}

@section scripts {
    <script type="text/javascript">
        jQuery.validator.addMethod('emailrequired', function(value, element, params) {
            var phoneTextboxId = $(element).attr('data-val-emailrequired-stringphoneprop');
            var phoneTextboxValue = $('#' + phoneTextboxId).val();
            // empty string is evaluated as ‘false’ and non-empty, non-null string is evaluated as ‘true’ in JavaScript
            return phoneTextboxValue || value;
        });

        jQuery.validator.unobtrusive.adapters.add('emailrequired', {}, function(options) {
            options.rules['emailrequired'] = true;
            options.messages['emailrequired'] = options.message;
        });
    </script>
}

1 个答案:

答案 0 :(得分:1)

第一个糟糕的设计决定是你写了一个ValidationAttribute,它对特定场景来说太具体了。您的属性应该是一个简单的RequiredIfAttribute,可以在属性值(特别是您的'Email`属性)依赖于另一个属性值的任何场景中使用。

在您的情况下,该属性将用作

[RequiredIf("Phone", null, ErrorMessage = "...")]
public string Email { get; set; }

您遇到的下一个问题是客户端脚本以及您没有获得依赖属性值的事实。您的

var phoneTextboxId = $(element).attr('data-val-emailrequired-stringphoneprop');

返回"Phone",因此返回以下行

var phoneTextboxValue = $('#' + phoneTextboxId).val();

返回undefined,(您想要的元素包含id="Father_Phone"等),这意味着return phoneTextboxValue || value;始终返回false

foolproof提供了一个包含许多常见条件验证属性的库,包括[RequiredIf][RequiredIfEmpty],这两个属性都适用于您的情况。

但是如果你想自己编写,那么我建议The Complete Guide To Validation In ASP.NET MVC 3 - Part 2作为一个很好的指南。

RequiredIfAttribute的代码为

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{

    #region .Declarations 

    private const string _DefaultErrorMessage = "Please enter the {0}.";
    private readonly string _PropertyName;
    private readonly object _Value;

    public RequiredIfAttribute(string propertyName, object value)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentNullException("propertyName");
        }
        _PropertyName = propertyName;
        _Value = value;
        ErrorMessage = _DefaultErrorMessage;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null)
        {
            var property = validationContext.ObjectInstance.GetType().GetProperty(_PropertyName);
            var propertyValue = property.GetValue(validationContext.ObjectInstance, null);
            if (propertyValue != null && propertyValue.Equals(_Value))
            {
                return new ValidationResult(string.Format(ErrorMessageString, validationContext.DisplayName));
            }
        }
        return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "requiredif",
        };
        rule.ValidationParameters.Add("dependentproperty", _PropertyName);
        rule.ValidationParameters.Add("targetvalue", _Value);
        yield return rule;
    }
}

以及相关的脚本

sandtrapValidation = {
    getDependentElement: function (validationElement, dependentProperty) {
        var dependentElement = $('#' + dependentProperty);
        if (dependentElement.length === 1) {
            return dependentElement;
        }
        var name = validationElement.name;
        var index = name.lastIndexOf(".") + 1;
        var id = (name.substr(0, index) + dependentProperty).replace(/[\.\[\]]/g, "_");
        dependentElement = $('#' + id);
        if (dependentElement.length === 1) {
            return dependentElement;
        }
        // Try using the name attribute
        name = (name.substr(0, index) + dependentProperty);
        dependentElement = $('[name="' + name + '"]');
        if (dependentElement.length > 0) {
            return dependentElement.first();
        }
        return null;
    }
}

$.validator.addMethod("requiredif", function (value, element, params) {
    if ($(element).val() != '') {
        // The element has a value so its OK
        return true;
    }
    if (!params.dependentelement) {
        return true;
    }
    var dependentElement = $(params.dependentelement);
    if (dependentElement.is(':checkbox')) {
        var dependentValue = dependentElement.is(':checked') ? 'True' : 'False';
        return dependentValue != params.targetvalue;
    } else if (dependentElement.is(':radio')) {
        // If its a radio button, we cannot rely on the id attribute
        // So use the name attribute to get the value of the checked radio button
        var dependentName = dependentElement[0].name;
        dependentValue = $('input[name="' + dependentName + '"]:checked').val();
        return dependentValue != params.targetvalue;
    }
    return dependentElement.val() !== params.targetvalue;
});

$.validator.unobtrusive.adapters.add("requiredif", ["dependentproperty", "targetvalue"], function (options) {
    var element = options.element;
    var dependentproperty = options.params.dependentproperty;
    var dependentElement = sandtrapValidation.getDependentElement(element, dependentproperty);
    options.rules['requiredif'] = {
        dependentelement: dependentElement,
        targetvalue: options.params.targetvalue
    };
    options.messages['requiredif'] = options.message;
});