ASP.NET MVC中使用属性进行条件验证

时间:2014-07-31 16:32:55

标签: c# jquery asp.net-mvc jquery-validate unobtrusive-validation

我正在尝试创建在ASP.NET中有条件地验证模型的能力。我让它用于服务器端验证,但我无法弄清楚如何使它与客户端验证一起工作。

例如,

public class Student
{
    [Required]
    public string Name { get; set; }
    public bool RequiresAddress { get; set; }
    [RequiredIf("RequiresAddress", true)]
    public string Address { get; set; }
}

这是我的RequiredIf属性(创建以下this post):

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
    public string DependentName;
    public object DependentValue;

    public RequiredIfAttribute(string PropertyName, object PropertyValue, string ErrorMessage)
    {
        this.DependentName = PropertyName;
        this.DependentValue = PropertyValue;
        this.ErrorMessage = ErrorMessage;
    }

    public override bool IsValid(object value)
    {
        if (value == null) { return false; }

        string ValueString = value as string;
        if (ValueString != null) { return (ValueString.Trim().Length != 0); }
        return true;
    }
}

然后我有了这个验证器(我使用DataAnnotationsModelValidatorProvider在Global.asax.cs中注册):

public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
    public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
        : base(metadata, context, attribute)
    {
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        var Rule = new ModelClientValidationRule
        {
            ErrorMessage = ErrorMessage,
            ValidationType = "requiredif",
        };

        var viewContext = (ControllerContext as ViewContext);
        string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentName);
        Rule.ValidationParameters.Add("dependentname", depProp);
        Rule.ValidationParameters.Add("dependentvalue", Attribute.DependentValue);

        yield return Rule;
    }

    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        var Field = Metadata.ContainerType.GetProperty(Attribute.DependentName);
        if (Field != null)
        {
            var Value = Field.GetValue(container, null);
            if ((Value == null && Attribute.DependentValue == null) || (Value != null && Value.Equals(Attribute.DependentValue)))
            {
                if (!Attribute.IsValid(Metadata.Model))
                {
                    yield return new ModelValidationResult { Message = ErrorMessage };
                }
            }
        }
    }
}

最后,在我看来,我尝试用jQuery注册客户端验证器:

if (typeof (jQuery) !== "undefined" && typeof (jQuery.validator) !== "undefined") {
    (function ($) {
        $.validator.addMethod('requiredif', function (value, element, parameters) {
            var id = '#' + parameters['dependentname'];
            var dependentvalue = parameters['dependentvalue'];
            dependentvalue = (dependentvalue == null ? '' : dependentvalue).toString();
            var actualvalue = $(id).val();
            if (dependentvalue === actualvalue) {
                return $.validator.methods.required.call(this, value, element, parameters);
            }
            else {
                return true;
            }
        });

    })(jQuery);
}

就像我说的那样,服务器端验证工作完美无缺,但客户端似乎根本没有工作。我是一个使用JavaScript的n00b,所以我不知道如何解决这个问题 - 我在这里缺少什么?

更新

我摆脱了Validator类,并在自定义属性上实现了IClientValidatable。这是我的新RequiredIfAttribute。同样,服务器端的所有内容似乎都按预期工作,但客户端无法正常工作。

从我的观点可以看出,我正在使用DevExpress来构建表单。

P.S。 The zipped solution can be found here.

public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
    public string DependentName { get; set; }
    public object DependentValue { get; set; }

    public RequiredIfAttribute(string DependentName, object DependentValue)
    {
        this.DependentName  = DependentName;
        this.DependentValue = DependentValue;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata Metadata, ControllerContext Context)
    {   
        var rule = new ModelClientValidationRule();
        rule.ErrorMessage = FormatErrorMessage(Metadata.GetDisplayName());
        rule.ValidationParameters.Add("dependentname", DependentName);
        rule.ValidationParameters.Add("dependentvalue", DependentValue);
        rule.ValidationType = "requiredif";
        yield return rule;
    }

    public override bool IsValid(object value)
    {
        if (value == null) { return false; }

        string ValueString = value as string;
        if (ValueString != null) { return (ValueString.Trim().Length != 0); }
        return true;
    }

    protected override ValidationResult IsValid(object Value, ValidationContext Context)
    {
        var ContainerType = Context.ObjectInstance.GetType();
        var Field = ContainerType.GetProperty(this.DependentName);

        if (Field != null)
        {
            var DependentValue = Field.GetValue(Context.ObjectInstance, null);
            if ((DependentValue == null && DependentValue == null) ||
                (DependentValue != null && DependentValue.Equals(this.DependentValue)))
            {
                if (!IsValid(Value))
                {
                    //return new ValidationResult(ErrorMessage, new[] { Context.MemberName });
                    return new ValidationResult(FormatErrorMessage(Context.DisplayName));
                }
            }
        }
        return ValidationResult.Success;
    }

    public string GetPropertyID(ModelMetadata Metadata, ViewContext Context)
    {
        string DependentID = Context.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentName);
        var Fie1ldID = Metadata.PropertyName + "_";

        return (DependentID.StartsWith(FieldID))
            ? DependentID.Substring(FieldID.Length)
            : DependentID;
    }
}

然后这是我的View类以及相关的JavaScript:

<script type="text/javascript">
function OnValueChanged(s, e) {
    var Result = ASPxClientCheckBox.Cast(s).GetValue();
    StudentForm.GetItemByName("Address").SetVisible(!Result);
}
</script>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(false)
    @Html.DevExpress().FormLayout(Form =>
    {
        Form.Name = "StudentForm";
        Form.Items.Add(x => x.Name, Item =>
        {
            (Item.NestedExtensionSettings as TextBoxSettings).ShowModelErrors = true;
            (Item.NestedExtensionSettings as TextBoxSettings).Properties.ValidationSettings.ErrorDisplayMode = ErrorDisplayMode.ImageWithTooltip;
            (Item.NestedExtensionSettings as TextBoxSettings).Properties.ValidationSettings.Display = Display.Dynamic;
        });

        Form.Items.Add(x => x.Address, Item =>
        {
            Item.Name = "Address";
            (Item.NestedExtensionSettings as TextBoxSettings).ShowModelErrors = true;
            (Item.NestedExtensionSettings as TextBoxSettings).Properties.ValidationSettings.ErrorDisplayMode = ErrorDisplayMode.ImageWithTooltip;
            (Item.NestedExtensionSettings as TextBoxSettings).Properties.ValidationSettings.Display = Display.Dynamic;
        });

        Form.Items.Add(x => x.AddressHidden, Item =>
        {
            Item.Name = "AddressHidden";
            (Item.NestedExtensionSettings as CheckBoxSettings).Properties.ClientSideEvents.Init = "OnValueChanged";
            (Item.NestedExtensionSettings as CheckBoxSettings).Properties.ClientSideEvents.ValueChanged = "OnValueChanged";
        });

        Form.Items.Add(x => x.AddressRequired);

        Form.Items.Add(x =>
        {
            x.NestedExtensionType = FormLayoutNestedExtensionItemType.Button;
            ButtonSettings Settings = (ButtonSettings)x.NestedExtensionSettings;
            Settings.UseSubmitBehavior = true;
            Settings.Text = "Submit";
            Settings.Name = "SubmitButton";
        });
    }).Bind(Model).GetHtml()
}

<script type="text/javascript">
    jQuery.validator.unobtrusive.adapters.add('requiredif', ['dependentname', 'dependentvalue'], function (options) {
    options.rules["requiredif"] = true;
    options.messages["requiredif"] = options.message;
    });

    $.validator.addMethod('requiredif', function (value, element, params) {
        var id = '#' + $(element).attr("data-val-requiredif-dependentname");
        var dependentvalue = $(element).attr("data-val-requiredif-dependentvalue");
        var actualvalue = $(id).is(":checked");
        if (dependentvalue == "True" && actualvalue && value.length <= 0) {
            return false;
        }
        return true;
    });
</script>

1 个答案:

答案 0 :(得分:1)

如果您想通过附加属性来查看您的RequiredIfAttribute必须实现IClientValidatable界面。可以通过添加此方法来完成:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules( ModelMetadata metadata, ControllerContext context)
{
    var rule = new ModelClientValidationRule();
    rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
    rule.ValidationParameters.Add("dependentname", DependentName);
    rule.ValidationParameters.Add("dependentvalue", DependentValue);
    rule.ValidationType = "requiredif";
    yield return rule;
}

你的js代码需要进行一些修改。我不是一个不显眼的大师,但它对我有用:

if ($.validator && $.validator.unobtrusive) {
    $.validator.unobtrusive.adapters.add('requiredif', ['dependentname', 'dependentvalue'], function(options) {
        options.rules["requiredif"] = options.params;
        options.messages["requiredif"] = options.message;
    });

    $.validator.addMethod('requiredif', function(value, element, params) {
        var dependentElement = $("input[name=" + params["dependentname"] + "]");
        var dependentvalue = params["dependentvalue"];
        var actualvalue = dependentElement.parents(".dxWeb_edtCheckBoxChecked_DevEx:first").length > 0; //is checkbox is checked
        if (dependentvalue == "True" && actualvalue && (value == null || value.length <= 0)) {
            return false;
        }
        return true;
    });
}

此代码仅适用于此示例。它假设RequiresAddress propery是一个bool并且它呈现为chckbox。它需要更多的编码才能适用于所有类型。我知道,为另一种类型编写另一个验证器会更好。

这对你来说是个好方法吗?

顺便说一句。 RequiredIfAttribute构造函数有三个参数,在Student类的例子中有两个传递;)