簡體   English   中英

如何根據所選國家/地區在MVC3中編寫用於美國或加拿大郵政編碼驗證的自定義驗證器?

[英]How do I write a custom validator in MVC3 for US or Canada zip code validation, based on the selected country?

我需要將自定義驗證器應用於帳單郵寄地址字段。 該視圖顯示幾個地址字段,包括“國家/地區”下拉列表(帶有美國和加拿大選項)以及BillingPostalCode文本框。 最初,我將正則表達式應用於郵件合同,該合同允許美國或加拿大的郵政編碼,如下所示:

[MessageBodyMember]
[Display(Name = "Billing Postal Code")]
[Required]
[StringLength(10, MinimumLength = 5)]
[RegularExpression("(^\\d{5}(-\\d{4})?$)|(^[ABCEGHJKLMNPRSTVXY]{1}\\d{1}[A-Z]{1} *\\d{1}[A-Z]{1}\\d{1}$)", 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>

我的想法是,在切換國家/地區下拉列表時,我將使用jQuery顯示或隱藏適當的跨度。 那一塊很容易。

但是我對兩個文本框都應用了單個驗證器的問題感到困惑,該驗證器映射到上面粘貼的MessageBodyMember。 我知道如何在jQuery中編寫驗證代碼,但也希望將驗證也應用於服務器端。

我對MVC相當陌生,因為它來自Web表單。 “老派” Web表單自定義驗證很容易實現。 我在網上發現用於MVC中的自定義驗證的示例非常復雜。 起初,這似乎是一個非常基本的要求。 該代碼需要評估一個變量(選定的國家/地區),並將該國家/地區的適當正則表達式應用於BillingPostalCode字段。

如何使用MVC3以簡單的方式滿足此要求? 謝謝。

好吧,我實現了這個家伙的所作所為,並且將其與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>
...

聽起來並不難,使用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;
}

我在我的一個項目中使用了MVC Foolproof Validation 您將按照隱藏隱藏字段和顯示字段的方式進行操作,但是驗證將遵循相同的邏輯。 有2個郵政編碼字段。 一個用於加拿大,另一個用於美國。 他們每個人都有自己的正則表達式驗證以確保格式正確。

偽碼

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM