まず、 Compare属性と同様の方法で独自の検証属性を作成する必要があります。
この属性では、他の依存プロパティを指定し、プロパティの表示名を考慮してエラーメッセージをフォーマットします。
属性は次のようになります(名前とデフォルトのエラーメッセージについてはあまり誇りに思っていません!):
public class SelectOneValidationAttribute : ValidationAttribute, IClientValidatable
{
private const string DefaultErrorMessage = "Please enter '{0}' or '{1}'.";
private string _otherFieldName;
public SelectOneValidationAttribute(String otherFieldName)
: base(DefaultErrorMessage)
{
_otherFieldName = otherFieldName;
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _otherFieldName);
}
protected override DataAnnotationsValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(_otherFieldName);
if (otherPropertyInfo == null)
{
return new DataAnnotationsValidationResult("Unknown property " + _otherFieldName);
}
string strValue = value == null ? null : value.ToString();
if(!String.IsNullOrEmpty(strValue))
//validation succeeds as this field has been populated
return null;
object otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
string strOtherPropertyValue = otherPropertyValue == null ? null : otherPropertyValue.ToString();
if (!String.IsNullOrEmpty(strOtherPropertyValue))
//validation succeeds as the other field has been populated
return null;
else
//validation failed, none of the 2 fields were populated
return new DataAnnotationsValidationResult(DefaultErrorMessage);
}
//Create the data attributes for the client to use
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "selectone",
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName())
};
rule.ValidationParameters["otherfield"] = FormatPropertyForClientValidation(_otherFieldName);
yield return rule;
}
public static string FormatPropertyForClientValidation(string property)
{
return "*." + property;
}
}
したがって、次のようにモデルで使用できます。
public class YourModel
{
[SelectOneValidation("Phone")]
public string Mobile { get; set; }
[SelectOneValidation("Mobile")]
public string Phone { get; set; }
}
そのコードで、エラーメッセージ「「携帯電話」または「電話」を入力してください。」および「「電話」または「携帯電話」と入力してください。」サーバー側の検証が失敗すると表示されます。(「1つ入力してください...」のように両方に同じエラーメッセージを設定できます)
クライアント側の検証を追加するには、目立たない検証用のアダプターを作成する必要があります。(目立たない検証でドキュメントを解析する前に、必ずどこかに追加してください。そうしないと、手動で解析する必要があります):
//Add an adaptor for our new jquery validator, that builds the optional parameters
//for our validation code (the other field to look at)
$.validator.unobtrusive.adapters.add("selectone", ["otherfield"], function (options) {
var prefix = getModelPrefix(options.element.name),
other = options.params.otherfield,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(":input[name=" + fullOtherName + "]")[0];
setValidationValues(options, "selectone", element);
});
これは、次のような目立たない検証ライブラリで定義されているユーティリティ関数のいくつかを使用しています。
function setValidationValues(options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
}
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
}
function appendModelPrefix(value, prefix) {
if (value.indexOf("*.") === 0) {
value = value.replace("*.", prefix);
}
return value;
}
最後に、新しい検証ルールを作成しました。これは、必要な検証でフィルタリング式を設定できるため、その式がtrueと評価された場合にのみフィールドに有効のフラグが付けられます。必要な検証を参照してください。onBlur検証には、equaltoルールと同様の修正を適用する必要があります。
検証方法は、equalto検証から上記の修正を適用し、他の関連フィールドの依存セレクターを使用して必要なルールを呼び出すだけなので、フィールドが空白で依存が入力されていない場合にのみ、必要な検証はfalseを返します。
$.validator.addMethod("selectone", function (value, element, param) {
// bind to the blur event of the target in order to revalidate whenever the target field is updated
// TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
var otherField = $(param);
if (this.settings.onfocusout) {
otherField.unbind(".validate-selectone").bind("blur.validate-selectone", function () {
$(element).valid();
});
}
var otherFieldBlankFilter = ":input[name=" + otherField[0].name + "]:blank";
return $.validator.methods.required.call(this, value, element, otherFieldBlankFilter);
});
それを気にしない場合は、フォームが送信されたときにのみ検証するかのように、必要なルールを直接使用するアダプターを作成するだけで済みます。
$.validator.unobtrusive.adapters.add("selectone", ["otherfield"], function (options) {
var prefix = getModelPrefix(options.element.name),
other = options.params.otherfield,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(":input[name=" + fullOtherName + "]")[0];
setValidationValues(options, "required", ":input[name=" + fullOtherName + "]:blank");
});
これらすべてが整っているので、検証は機能するはずです。両方が空で送信しようとすると、両方のフィールドにエラーメッセージが表示されます。次に、それらの1つに何かを入力してタブアウトすると、両方のエラーメッセージが削除されます。
また、検証属性、目立たないアダプター、および検証ルールを変更できるため、これをニーズに適合させることができるはずです。