如何根据所选国家/地区在 MVC3 中为美国或加拿大邮政编码验证编写自定义验证器?

Posted

技术标签:

【中文标题】如何根据所选国家/地区在 MVC3 中为美国或加拿大邮政编码验证编写自定义验证器?【英文标题】:How do I write a custom validator in MVC3 for US or Canada zip code validation, based on the selected country? 【发布时间】:2012-08-30 14:12:11 【问题描述】:

我需要将自定义验证器应用于帐单地址字段。该视图显示多个地址字段,包括国家下拉列表(带有美国和加拿大选项)和 BillingPostalCode 文本框。最初我将一个正则表达式应用于允许美国或加拿大邮政编码的消息合同,如下所示:

[MessageBodyMember]
[Display(Name = "Billing Postal Code")]
[Required]
[StringLength(10, MinimumLength = 5)]
[RegularExpression("(^\\d5(-\\d4)?$)|(^[ABCEGHJKLMNPRSTVXY]1\\d1[A-Z]1 *\\d1[A-Z]1\\d1$)", ErrorMessage = "Zip code is invalid.")] // US or Canada
public string BillingPostalCode

   get  return _billingPostalCode; 
   set  _billingPostalCode = value; 

以上将允许使用美国或加拿大的邮政编码。但是,只有在 BillingCountry 下拉列表中分别选择了美国或加拿大时,企业主才希望表单允许使用美国或加拿大的邮政编码。在他的测试用例中,他选择了加拿大并输入了美国邮政编码。应该禁止这种情况。

我最初的尝试是将它放在视图中,尽管我对创建 2 个文本框字段并不满意。我应该只需要 1 个字段。

<div style="float: left; width: 35%;">
   Zip Code<br />
   <span id="spanBillingZipUS">
      @html.TextBoxFor(m => m.BillingPostalCode, new  @class = "customer_input", @id = "BillingPostalCode" )
   </span>
   <span id="spanBillingZipCanada">
      @Html.TextBoxFor(m => m.BillingPostalCode, new  @class = "customer_input", @id = "BillingPostalCodeCanada" )
   </span>
   @Html.ValidationMessageFor(m => m.BillingPostalCode)
   <br />
</div>

我的想法是,当 Country 下拉列表被切换时,我将使用 jQuery 来显示或隐藏适当的跨度。这部分很简单。

但我遇到的问题是两个文本框都应用了单个验证器,该验证器映射到上面粘贴的 MessageBodyMember。我知道如何在 jQuery 中编写验证代码,但更希望将验证也应用到服务器端。

我对 MVC 很陌生,来自网络表单。 “老派”网络表单自定义验证很容易实现。我在网上找到的用于 MVC 中的自定义验证的示例非常复杂。起初,这似乎是一个非常基本的要求。代码需要评估一个变量(所选国家/地区)并将该国家/地区的适当正则表达式应用于 BillingPostalCode 字段。

如何使用 MVC3 以简单的方式满足此要求?谢谢。

【问题讨论】:

【参考方案1】:

好吧,我实现了 this guy made,它作为一个带有 Data Annotations 的 chram 工作。您确实需要做一些工作才能更改 check 以获取 下拉值,但这是我发现使用 Data Annotations 实现验证的更优雅的方式不显眼


这里是一个例子:

型号

...
        [RequiredIf("IsUKResident", true, ErrorMessage = "You must specify the City if UK resident")]
        public string City  get; set; 
...

自定义属性

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace Mvc3ConditionalValidation.Validation

    public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
    
        private RequiredAttribute _innerAttribute = new RequiredAttribute();

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

        public RequiredIfAttribute(string dependentProperty, object targetValue)
        
            this.DependentProperty = dependentProperty;
            this.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(this.DependentProperty);

            if (field != null)
            
                // get the value of the dependent property
                var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);

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

            return ValidationResult.Success;
        

        public 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 = (this.TargetValue ?? "").ToString();
            if (this.TargetValue.GetType() == typeof(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(this.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 (our Person), 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;
        
    

客户端

...
<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>

<script>
    $.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 (targetvalue === 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;
        );

</script>
...
    <div class="editor-label">
        @Html.LabelFor(model => model.City)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.City)
        @Html.ValidationMessageFor(model => model.City)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.IsUKResident)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.IsUKResident)
        @Html.ValidationMessageFor(model => model.IsUKResident)
    </div>
...

【讨论】:

【参考方案2】:

这听起来不像是使用 ModelBinding 和验证属性很容易完成的事情。

执行此操作的直接方法可能是保留验证属性,并简单地验证它是美国还是加拿大的邮政编码。然后当它到达服务器时,进行一些手动验证。

例如

[Post]
public ActionResult SaveInfo(MyViewModel viewModel)

    var isValid = true;
    if (ModelState.IsValid)
    
        if (!IsValidPostCode(viewModel))
        
            isValid = false;
            ModelState.AddModelError("BillingPostalCode", "The billing postcode appears to be invalid.");
        

        if (isValid)
        
            return RedirectToAction("success");
        
    

    return View(viewModel);


private static IDictionary<string, string> countryPostCodeRegex = new Dictionary<string, string>
    
         "USA", "USAPostCodeRegex" ,
         "Canada", "CanadianPostCodeRegex" ,
    

private bool IsValidPostCode(MyViewModel viewModel)

    var regexString = countryPostCodeRegex[viewModel.SelectedCountry];
    var regexMatch = Regex.Match(viewModel.BillingPostalCode, regexString, RegexOptions.IgnoreCase);

    return regexMatch.Success;

【讨论】:

【参考方案3】:

我在我的一个项目中使用了MVC Foolproof Validation。您可以按照您所说的隐藏和显示字段,但验证将遵循相同的逻辑。有 2 个邮政编码字段。一个用于加拿大,另一个用于美国。他们每个人都有自己的正则表达式验证,以确保格式正确。

伪代码

[RequiredIf(DependentName = "Country", DependentValue="USA")]
public string PostalCodeUSA  get; set; 

[RequiredIf(DependentName = "Country", DependentValue="Canada")]
public string PostalCodeCanada  get; set; 

【讨论】:

以上是关于如何根据所选国家/地区在 MVC3 中为美国或加拿大邮政编码验证编写自定义验证器?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 google api 根据所选国家/地区自动完成?

我可以在打开的购物车中为不同国家/地区单独收取运费吗?

系统架构培训:矩阵,封装,一个案例教你激发客户潜藏的需求!

如何更改 Apple ID 国家或地区

无法摆脱重复值[重复]

MVC 3 - 基于国家的重定向?