[英]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.