クライアント側とサーバー側の処理にカスタム検証属性を使用したい MVC3 プロジェクトがあります。http://thewayofcode.wordpress.com/2012/01/18/custom-unobtrusive-jquery-validation-with-data-annotations-in-mvc-3/にある手順に従いました。これは素晴らしいチュートリアルであり、実際には完璧に機能します。
私が抱えている唯一の問題は、フォームが送信されるまで検証がトリガーされないように見えることです。クライアント側とサーバー側の検証があります。サーバー側の検証は、検証属性とカスタム検証の組み合わせです (たとえば、データベース内の何かに対して入力値をチェックする必要がある場合があります)。(Ajax.BeginForm を使用して) フォームの [保存] ボタンを初めてクリックすると、投稿がサーバーに送信され、入力が無効であるため、サーバー側の検証が開始され、検証メッセージが返されます。フォーム入力をそのままにして、[保存] ボタンをもう一度クリックすると、クライアント側の検証が適切に機能し、投稿が行われなくなります。
フォームが投稿されるまでクライアント側の検証がスキップされる原因は何ですか?
私のカスタム検証属性:
public class RequiredIfContainsAttribute : ValidationAttribute, IClientValidatable
{
private RequiredAttribute _innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public string ComparisonValue { get; set; }
public RequiredIfContainsAttribute(string dependentProperty, string comparisonValue)
{
DependentProperty = dependentProperty;
ComparisonValue = comparisonValue;
}
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);
// this validation only works if the comparison field is a string
if (dependentValue.GetType() != typeof(string))
{
return ValidationResult.Success;
}
var dependentString = (string) dependentValue;
// check whether the string to check contains the comparison value
if (dependentString.Contains(ComparisonValue))
{
// if the string to check contains the comparison value, the attribute becomes required and must now be validated
if (!_innerAttribute.IsValid(value))
{
// validation failed - return an error
return new ValidationResult(ErrorMessage, new[] {validationContext.MemberName});
}
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredifcontains"
};
var depProp = BuildDependentPropertyId(metadata, context as ViewContext);
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("comparisonvalue", ComparisonValue);
yield return rule;
}
private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(DependentProperty);
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
{
// strip it off again
depProp = depProp.Substring(thisField.Length);
}
return depProp;
}
}
私のモデル属性:
[RequiredIfContains("FirstName", "Mickey", ErrorMessage = "The date of birth is required when the first name is Mickey")]
public DateTime DateOfBirth { get; set; }
バリデーターを追加するための私のカスタム js:
$.validator.addMethod('requiredifcontains',
function (value, element, parameters) {
console.log("requiredifcontains starting");
var id = '#' + parameters['dependentproperty'];
// get the target value (as a string,
// as that's what actual value will be)
var comparisonvalue = parameters['comparisonvalue'];
comparisonvalue =
(comparisonvalue == null ? '' : comparisonvalue).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 inputValue = 'empty';
if (control.is('input:text')) {
inputValue = control.text();
} else if (control.is('select')) {
inputValue = $(id + " option:selected").text();
}
// if the input control wasn't found (possibly because the type wasn't checked for) then we can't compare so just return true
if (inputValue == 'empty') {
return true;
}
// if the condition is true, reuse the existing
// required field validator functionality
console.log("requiredifcontains performing underlying validation");
if (inputValue.indexOf(comparisonvalue) > -1)
return $.validator.methods.required.call(
this, value, element, parameters);
console.log("requiredifcontains returning true");
return true;
}
);
$.validator.unobtrusive.adapters.add(
'requiredifcontains',
['dependentproperty', 'comparisonvalue'],
function (options) {
options.rules['requiredifcontains'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['comparisonvalue']
};
options.messages['requiredifcontains'] = options.message;
});
失敗しているものとほぼ同じである不自然なビュー:
@{
var options = new AjaxOptions()
{
HttpMethod = "Post",
UpdateTargetId = "personalInfoDiv",
OnSuccess = "FormSubmitSuccess()"
};
}
<div id="personalInfoDiv">
@using (Ajax.BeginForm("PersonalInformationDetail", "PersonalInformation", null, options, new {@style = "height:100%", @id = "PersonalInformationForm"}))
{
@Html.ValidationSummary(false)
@Html.EditorForModel()
<div style="float:left; position:relative;">
<input type="button" value="Save" style="width:125px;" id="Save" onclick="saveClick(this)" />
</div>
}
</div>
[保存] クリックと成功時のメソッドの JavaScript:
function saveClick(e) {
var firstName = $("#FirstName").val();
var result = true;
if (firstName == '') {
result = confirm("First name is not required but is recommended. Choose OK to save anyway or CANCEL to add a first name.");
}
if (result) {
$("#PersonalInformationForm").submit();
}
}
function FormSubmitSuccess(result) {
// do some stuff after the form submits
}
私はこれをしばらく探していましたが、見つけた解決策のほとんどは、私の問題の反対の解決策です。form.validate() の結果をログに記録しました。最初に [保存] をクリックするとエラーは発生しませんが、2 回目 (投稿後) にはエラーが発生します。
これはおそらく私が見逃している単純なものですが、ここで他に何を試すべきかわかりません。時間がなくなっています。
これは私の最初の投稿です。何か関連するものを含めるのを怠った場合は、お知らせください。質問を更新できます。