2012-06-04 175 views
3

我有一个简单的问题。MVC 3字段必填,如果其他字段填充

我有例如映射在模型ex上的两个fileds:textbox_1和textbox_2。

我想问问是否存在一种方式(ex强制装饰),强制textbox_2强制只有当我填写textbox_1。如果我没有填写textbox_1文本框2是可选的。

有没有一个优雅的方式来做到这一点?

+1

可能的解决方法在这里过的属性:http://stackoverflow.com/questions/3713281 /属性依赖于另一个字段 –

回答

15

ASP.NET MVC中没有开箱即用的解决方案。这是我为解决它而创建的属性。有该属性3级可用的用法:

  • nulltargetValue构造器:只有当 依赖字段为空必需的。
  • 将任何值作为tagetValue传递:只有当依赖字段等于任何值时才需要 。
  • 通过"*"作为 tagetValue:仅当填充相关字段时才需要。

你的情况,你需要传递"*"targetValue的构造,这意味着相关的属性可以是任何非空值。

注意:它包含服务器端和客户端(+不显眼)验证。

服务器端属性类:

public class RequiredIfAttribute : ValidationAttribute, IClientValidatable 
{ 
    protected RequiredAttribute _innerAttribute; 

    public string DependentProperty { get; set; } 
    public object TargetValue { get; set; } 

    public bool AllowEmptyStrings 
    { 
     get 
     { 
      return _innerAttribute.AllowEmptyStrings; 
     } 
     set 
     { 
      _innerAttribute.AllowEmptyStrings = value; 
     } 
    } 

    public RequiredIfAttribute(string dependentProperty, object targetValue) 
    { 
     _innerAttribute = new RequiredAttribute(); 
     DependentProperty = dependentProperty; 
     TargetValue = targetValue; 
    } 

    protected override ValidationResult IsValid(object value, ValidationContext validationContext) 
    { 
     // get a reference to the property this validation depends upon 
     var containerType = validationContext.ObjectInstance.GetType(); 
     var field = containerType.GetProperty(DependentProperty); 

     if (field != null) 
     { 
      // get the value of the dependent property 
      var dependentValue = field.GetValue(validationContext.ObjectInstance, null); 
      // trim spaces of dependent value 
      if (dependentValue != null && dependentValue is string) 
      { 
       dependentValue = (dependentValue as string).Trim(); 

       if (!AllowEmptyStrings && (dependentValue as string).Length == 0) 
       { 
        dependentValue = null; 
       } 
      } 

      // compare the value against the target value 
      if ((dependentValue == null && TargetValue == null) || 
       (dependentValue != null && (TargetValue == "*" || dependentValue.Equals(TargetValue)))) 
      { 
       // match => means we should try validating this field 
       if (!_innerAttribute.IsValid(value)) 
        // validation failed - return an error 
        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName }); 
      } 
     } 

     return ValidationResult.Success; 
    } 

    public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) 
    { 
     var rule = new ModelClientValidationRule 
     { 
      ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()), 
      ValidationType = "requiredif", 
     }; 

     string depProp = BuildDependentPropertyId(metadata, context as ViewContext); 

     // find the value on the control we depend on; 
     // if it's a bool, format it javascript style 
     // (the default is True or False!) 
     string targetValue = (TargetValue ?? "").ToString(); 
     if (TargetValue is bool) 
      targetValue = targetValue.ToLower(); 

     rule.ValidationParameters.Add("dependentproperty", depProp); 
     rule.ValidationParameters.Add("targetvalue", targetValue); 

     yield return rule; 
    } 

    private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext) 
    { 
     // build the ID of the property 
     string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(DependentProperty); 
     // unfortunately this will have the name of the current field appended to the beginning, 
     // because the TemplateInfo's context has had this fieldname appended to it. Instead, we 
     // want to get the context as though it was one level higher (i.e. outside the current property, 
     // which is the containing object, and hence the same level as the dependent property. 
     var thisField = metadata.PropertyName + "_"; 
     if (depProp.StartsWith(thisField)) 
      // strip it off again 
      depProp = depProp.Substring(thisField.Length); 
     return depProp; 
    } 
} 

客户端(包括不引人注目的验证):

$.validator.addMethod('requiredif', 
    function (value, element, parameters) { 
     var id = '#' + parameters['dependentproperty']; 

     // get the target value (as a string, 
     // as that's what actual value will be) 
     var targetvalue = parameters['targetvalue']; 
     targetvalue = (targetvalue == null ? '' : targetvalue).toString(); 

     // get the actual value of the target control 
     // note - this probably needs to cater for more 
     // control types, e.g. radios 
     var control = $(id); 
     var controltype = control.attr('type'); 
     var actualvalue = 
      controltype === 'checkbox' ? 
      control.attr('checked').toString() : 
      control.val(); 

     // if the condition is true, reuse the existing 
     // required field validator functionality 
     if ($.trim(targetvalue) === $.trim(actualvalue) || ($.trim(targetvalue) === '*' && $.trim(actualvalue) !== '')) 
      return $.validator.methods.required.call(
       this, value, element, parameters); 

     return true; 
    }); 

$.validator.unobtrusive.adapters.add(
    'requiredif', 
    ['dependentproperty', 'targetvalue'], 
    function (options) { 
     options.rules['requiredif'] = { 
      dependentproperty: options.params['dependentproperty'], 
      targetvalue: options.params['targetvalue'] 
     }; 
     options.messages['requiredif'] = options.message; 
    }); 
使用自定义的验证
+1

伟大的解决方案,对我来说有几个问题。 TargetValue ==“*”需要更改为TargetValue.Equals(“*”)和 – superwalnut

+1

,并且我还为js验证提供单选按钮。 var actualvalue = (controltype ==='checkbox'|| controltype ==='radio')? ('checked')。toString(): control.val(); – superwalnut

+0

如果该属性位于本身为更高级视图模型中属性的类中,则BuildDependentPropertyId例程不起作用。 (在这种情况下,在属性ID字符串的开始处还有另一个标识符。)本文文章[http://stackoverflow.com/a/21018963/1637105]包含与该作品类似的代码版本 –