极其奇怪的 Fluent Validation 行为
Extremely bizarre Fluent Validation behaviour
当前项目:
- 点网 4.7
- MVC 5
- 数据库优先,从一个非常古老的 Web 表单数据库生成(nchar 字段等......不要问为什么)
所以我遇到了 Fluent Validation 中极其奇怪的行为。
也就是说,
Non-Nulled Nullable 字段仅在服务器端验证。取一个应该由下拉菜单的值填充的 int
(而不是 int?
)字段,虽然它会在服务器端验证,但只会在客户端。如果您再次选择不可接受的空值 ("Select a choice"),它不会在客户端重新验证。
此行为似乎仅限于 int
个从下拉列表中填写的字段。
在提交表单之前,所有字符串、日期和任何其他类型的字段都无法在客户端验证(一旦贪婪验证开始),并且根本不会在服务器端验证。此外,所有 .NotEmpty()
和 .NotNull()
声明似乎都被忽略了,即使它们是字符串字段验证中的唯一声明。
我的 Global.asax.cs
配置正确:
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
FluentValidationModelValidatorProvider.Configure();
}
}
我有正确的 JS 文件进入页面:
<link href="/Content/bootstrap.css" rel="stylesheet"/>
<link href="/Content/bootstrap-datepicker3.css" rel="stylesheet"/>
<link href="/Content/fontawesome-all.css" rel="stylesheet"/>
<link href="/Content/style.css" rel="stylesheet"/>
<script src="/Scripts/modernizr-2.8.3.js"></script>
<script src="/Scripts/jquery-3.3.1.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="/Scripts/bootstrap.js"></script>
<script src="/Scripts/bootstrap-datepicker.js"></script>
<script src="/Scripts/popper.js"></script>
<script src="/Scripts/jquery.mask.js"></script>
<script src="/Scripts/script.js"></script>
我的 ViewModel 已正确配置:
namespace Project.Models {
using Controllers;
using FluentValidation.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Validators;
[Validator(typeof(MoreInfoValidator))]
public class MoreInfoViewModel {
[DisplayName(@"First Name")]
public string FirstName { get; set; }
[DisplayName(@"Last Name")]
public string LastName { get; set; }
[DisplayName(@"Phone Number")]
[DataType(DataType.PhoneNumber)]
public string Phone { get; set; }
[DisplayName(@"eMail Address")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DisplayName(@"Date of Birth")]
[DataType(DataType.DateTime)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime Dob { get; set; } = DateTime.Now.AddYears(-16);
[DisplayName(@"Mailing Address")]
public string Address { get; set; }
[DisplayName(@"City")]
public string City { get; set; }
[DisplayName(@"Province or State")]
public string ProvState { get; set; }
[DisplayName(@"Postal Code")]
[DataType(DataType.PostalCode)]
public string Postal { get; set; }
[DisplayName(@"Country")]
public int CountryId { get; set; }
[DisplayName(@"How did you hear about us?")]
public int HowHeardId { get; set; }
[DisplayName(@"Training Site")]
public int TrainingSiteId { get; set; }
[DisplayName(@"Comments")]
public string Comments { get; set; }
public IEnumerable<SelectListItem> HowHeardList = ListController.HowHeardList();
public IEnumerable<SelectListItem> CountryList = ListController.CountryList();
public IEnumerable<SelectListItem> TrainingSiteList = ListController.TrainingSiteList();
}
}
我的验证器配置正确:
namespace Project.Validators {
using FluentValidation;
using Models;
public class MoreInfoValidator : AbstractValidator<MoreInfoViewModel> {
public MoreInfoValidator() {
RuleFor(x => x.FirstName)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("You must provide a first name of some kind.")
.MinimumLength(2).WithMessage(@"A first name must be at least two characters or longer.");
RuleFor(x => x.LastName)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage(@"You must provide a last name of some kind.")
.MinimumLength(2).WithMessage(@"A last name must be at least two characters or longer.");
RuleFor(x => x.Email.Trim())
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage(@"Please provide an eMail address to act as the login username.")
.EmailAddress().WithMessage(@"Please provide a valid eMail address to act as the login username.");
RuleFor(x => x.Phone)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please enter a valid 10-digit phone number.")
.Length(12, 12).WithMessage("Phone number must be in the form of “123-456-7890”")
.Matches(@"^\d{3}-\d{3}-\d{4}$").WithMessage("Phone number must be a valid 10-digit phone number with dashes, in the form of “123-456-7890”");
RuleFor(x => x.Address)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your street address.")
.MinimumLength(6).WithMessage("Addresses should be at least 6 characters long.");
RuleFor(x => x.City)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your city.")
.MinimumLength(2).WithMessage("City names should be at least 2 characters long.");
RuleFor(x => x.ProvState)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your province or state.")
.Length(2).WithMessage("Please provide the 2-character code for your province or state.");
RuleFor(x => x.CountryId)
.NotEmpty().WithMessage("Please choose your country.");
RuleFor(x => x.HowHeardId)
.NotEmpty().WithMessage("How did you hear of us?");
RuleFor(x => x.TrainingSiteId)
.NotEmpty().WithMessage("Please choose a desired training site.");
}
}
}
我的表单构建正确:
@model Project.Models.MoreInfoViewModel
@{
ViewBag.Title = "More Info";
}
<h1>@ViewBag.Title</h1>
<p><span class="requiredcolor">These fields</span> are required.</p>
@using(Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationMessage("", new { @class = "alert" })
<div class="row">
<div class="form-group col-md-6">
@Html.LabelFor(x => x.FirstName, new { @class = "control-label required" })@Html.EditorFor(x => x.FirstName, new { htmlAttributes = new { @class = "form-control required", maxlength = 100 } })
@Html.ValidationMessageFor(x => x.FirstName)
</div>
<div class="form-group col-md-6">
@Html.LabelFor(x => x.LastName, new { @class = "control-label required" })@Html.EditorFor(x => x.LastName, new { htmlAttributes = new { @class = "form-control required", maxlength = 100 } })
@Html.ValidationMessageFor(x => x.LastName)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Phone, new { @class = "control-label required" })@Html.EditorFor(x => x.Phone, new { htmlAttributes = new { @class = "form-control required phone", maxlength = 12 } })
@Html.ValidationMessageFor(x => x.Phone)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Email, new { @class = "control-label required" })@Html.EditorFor(x => x.Email, new { htmlAttributes = new { @class = "form-control required", maxlength = 75 } })
@Html.ValidationMessageFor(x => x.Email)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Dob, new { @class = "control-label required" })@Html.EditorFor(x => x.Dob, new { htmlAttributes = new { @class = "form-control required datepicker" } })
@Html.ValidationMessageFor(x => x.Dob)
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
@Html.LabelFor(x => x.Address, new { @class = "control-label required" })@Html.EditorFor(x => x.Address, new { htmlAttributes = new { @class = "form-control required", maxlength = 150 } })
@Html.ValidationMessageFor(x => x.Address)
</div>
<div class="form-group col-md-6">
@Html.LabelFor(x => x.City, new { @class = "control-label required" })@Html.EditorFor(x => x.City, new { htmlAttributes = new { @class = "form-control required", maxlength = 50 } })
@Html.ValidationMessageFor(x => x.City)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
@Html.LabelFor(x => x.ProvState, new { @class = "control-label required" })@Html.EditorFor(x => x.ProvState, new { htmlAttributes = new { @class = "form-control required", maxlength = 2 } })
@Html.ValidationMessageFor(x => x.ProvState)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.CountryId, new { @class = "control-label required" })@Html.DropDownListFor(x => x.CountryId, Model.CountryList, "« ‹ Select › »", new { @class = "form-control required" })
@Html.ValidationMessageFor(x => x.CountryId)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Postal, new { @class = "control-label" })@Html.EditorFor(x => x.Postal, new { htmlAttributes = new { @class = "form-control postalcode", maxlength = 7 } })
@Html.ValidationMessageFor(x => x.Postal)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
@Html.LabelFor(x => x.HowHeardId, new { @class = "control-label required" })@Html.DropDownListFor(x => x.HowHeardId, Model.HowHeardList, "« ‹ Select › »", new { @class = "form-control required" })
@Html.ValidationMessageFor(x => x.HowHeardId)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.TrainingSiteId, new { @class = "control-label required" })@Html.DropDownListFor(x => x.TrainingSiteId, Model.TrainingSiteList, "« ‹ Select › »", new { @class = "form-control required" })
@Html.ValidationMessageFor(x => x.TrainingSiteId)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Comments, new { @class = "control-label" })@Html.TextAreaFor(x => x.Comments, new { @class = "form-control", rows = 3 })
@Html.ValidationMessageFor(x => x.Comments)
</div>
</div>
<div class="blank-divider clearfix" style="height:30px;"></div>
<div class="row">
<div class="form-group col-md-6"> </div>
<div class="form-group col-md-6"><label class="control-label"> </label><input type="submit" value="Submit Request" alt="Submit Request" title="Submit Request" class="btn btn-default btn-success" /></div>
</div>
}
我完全不明白为什么 Fluent Validation 只在极其有限的情况下才会触发。
请理解,当它确实触发时——post-对所有其他字段提交贪婪验证,对下拉菜单进行服务器端验证——所有消息都完全符合预期。
但是,对于 any 字段,客户端和服务器端都不会触发验证。如果它触发服务器端,则无法触发客户端。如果它触发客户端,它只会在有限的条件下(字符串长度不够等)这样做,然后拒绝触发服务器端。
什么是 not 工作是通常的验证和任何捕获 .NotEmpty()
和 .NotNull()
.
的尝试
编辑:
我看不出这是怎么可能的(没有从数据库中加载,只是插入其中),但是字符串字段验证是否会被数据库字段的格式? nchar
会成为这里的问题吗?
此外,我使用自定义模型收集数据,然后再将其添加到数据模型以插入数据库,因此我们与数据库结构的距离更远。从理论上讲,在将数据从 ViewModel 移动到 DataModel 之前,我看不出这种关系如何可能。
编辑 2:
.Cascade(CascadeMode.StopOnFirstFailure)
没有区别,在验证器 class 中有或没有这些问题都会出现。
编辑 3:
让我更准确地说明贪心验证。在所有情况下,任何 .NotEmpty()
和 .NotNull()
验证仍然无法触发,因此简单地从一个字段导航到下一个字段(点击提交后)将无法触发贪婪验证。只有当你输入一些东西,并且它不足以进行其他验证时(太短,不是有效的电子邮件,不是有效的 phone 号码等)贪婪验证才会触发。这就是为什么我想出我的第一个编辑(上图)的原因,因为也许系统没有将这些字符串字段视为空或空,即使它们是。
编辑 4:
WTF 更离奇。当我将 partial/incomplete 字符串放入不仅仅是长度分析的东西中时——例如只将电子邮件的前半部分放入电子邮件字段——然后点击提交,服务器端验证开始执行所有字符串字段,甚至 NULL/EMPTY 个字段。
喜欢,认真的威士忌。探戈。狐步舞。
编辑 5:
WTF x10:如果选择了三个下拉菜单,编辑 4 只会发生 。如果三个下拉列表中的任何一个仍未选中,服务器端验证将无法触发任何文本字段。
此外,如果选择了所有三个下拉菜单,使用 .NotEmpty()
和 .NotNull()
的完整验证会突然在所有文本字段上成功,包括服务器端 和 客户端贪婪验证。
神圣的玉米粉蒸肉。这越来越奇怪了。
这个问题源于我自从开始使用 Fluent Validation 以来的概念盲点。
自从我开始使用 Fluent Validation 之前,我就使用 GUID
s 作为主键。我在整个数据生命周期中使用了 GUID
s——从 ViewModel 到 View 再回到 Mapper,数据被转储回数据模型以添加 to/updating 数据库。
因此,当我使用下拉 select 菜单并将 GUID
用作主键时,那些 select 菜单旨在填充所需的外键,我能够将 ViewModel 字段设置为不可为 null 的 GUID
并仍然正确触发 Fluent Validation。当我想填充可选外键时,我为该字段使用了可为空的 GUID
,一切仍然有效。
这个项目使用的数据库早于我的参与。因此,它使用 int
作为主键,因此所需的某些外键也是 int
而不是 int?
。正因为如此,我做出了这样的假设——无论多么错误——我可以继续使用 int
来保存 所需外键 [=42] 的下拉菜单 select 的值=] 并能够成功验证其他所有内容,包括文本字段。
天哪,我错了。
Fluent Validation 的 Jeremy Skinner 名声大噪 took his Sunday out to assist me 并向我展示了我需要做什么。
本质上,任何整数驱动的下拉 select 菜单,无论是必需的外键还是可选的外键,都需要 ViewModel 中的 int?
来保存 selected值。如果没有可为 null 的 int?
,整个模型的验证将以意想不到的方式进行,以至于根本不会触发任何其他内容(根据我的经验)。
当我将该字段的值更改为 int?
并向 Mapper 添加空合并运算符时(通过提供任何通过 Fluent Validation 的空值的默认值),一切突然开始按预期工作。
当前项目:
- 点网 4.7
- MVC 5
- 数据库优先,从一个非常古老的 Web 表单数据库生成(nchar 字段等......不要问为什么)
所以我遇到了 Fluent Validation 中极其奇怪的行为。
也就是说,
Non-Nulled Nullable 字段仅在服务器端验证。取一个应该由下拉菜单的值填充的 int
(而不是 int?
)字段,虽然它会在服务器端验证,但只会在客户端。如果您再次选择不可接受的空值 ("Select a choice"),它不会在客户端重新验证。
此行为似乎仅限于 int
个从下拉列表中填写的字段。
在提交表单之前,所有字符串、日期和任何其他类型的字段都无法在客户端验证(一旦贪婪验证开始),并且根本不会在服务器端验证。此外,所有 .NotEmpty()
和 .NotNull()
声明似乎都被忽略了,即使它们是字符串字段验证中的唯一声明。
我的 Global.asax.cs
配置正确:
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
FluentValidationModelValidatorProvider.Configure();
}
}
我有正确的 JS 文件进入页面:
<link href="/Content/bootstrap.css" rel="stylesheet"/>
<link href="/Content/bootstrap-datepicker3.css" rel="stylesheet"/>
<link href="/Content/fontawesome-all.css" rel="stylesheet"/>
<link href="/Content/style.css" rel="stylesheet"/>
<script src="/Scripts/modernizr-2.8.3.js"></script>
<script src="/Scripts/jquery-3.3.1.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="/Scripts/bootstrap.js"></script>
<script src="/Scripts/bootstrap-datepicker.js"></script>
<script src="/Scripts/popper.js"></script>
<script src="/Scripts/jquery.mask.js"></script>
<script src="/Scripts/script.js"></script>
我的 ViewModel 已正确配置:
namespace Project.Models {
using Controllers;
using FluentValidation.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Validators;
[Validator(typeof(MoreInfoValidator))]
public class MoreInfoViewModel {
[DisplayName(@"First Name")]
public string FirstName { get; set; }
[DisplayName(@"Last Name")]
public string LastName { get; set; }
[DisplayName(@"Phone Number")]
[DataType(DataType.PhoneNumber)]
public string Phone { get; set; }
[DisplayName(@"eMail Address")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DisplayName(@"Date of Birth")]
[DataType(DataType.DateTime)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime Dob { get; set; } = DateTime.Now.AddYears(-16);
[DisplayName(@"Mailing Address")]
public string Address { get; set; }
[DisplayName(@"City")]
public string City { get; set; }
[DisplayName(@"Province or State")]
public string ProvState { get; set; }
[DisplayName(@"Postal Code")]
[DataType(DataType.PostalCode)]
public string Postal { get; set; }
[DisplayName(@"Country")]
public int CountryId { get; set; }
[DisplayName(@"How did you hear about us?")]
public int HowHeardId { get; set; }
[DisplayName(@"Training Site")]
public int TrainingSiteId { get; set; }
[DisplayName(@"Comments")]
public string Comments { get; set; }
public IEnumerable<SelectListItem> HowHeardList = ListController.HowHeardList();
public IEnumerable<SelectListItem> CountryList = ListController.CountryList();
public IEnumerable<SelectListItem> TrainingSiteList = ListController.TrainingSiteList();
}
}
我的验证器配置正确:
namespace Project.Validators {
using FluentValidation;
using Models;
public class MoreInfoValidator : AbstractValidator<MoreInfoViewModel> {
public MoreInfoValidator() {
RuleFor(x => x.FirstName)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("You must provide a first name of some kind.")
.MinimumLength(2).WithMessage(@"A first name must be at least two characters or longer.");
RuleFor(x => x.LastName)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage(@"You must provide a last name of some kind.")
.MinimumLength(2).WithMessage(@"A last name must be at least two characters or longer.");
RuleFor(x => x.Email.Trim())
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage(@"Please provide an eMail address to act as the login username.")
.EmailAddress().WithMessage(@"Please provide a valid eMail address to act as the login username.");
RuleFor(x => x.Phone)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please enter a valid 10-digit phone number.")
.Length(12, 12).WithMessage("Phone number must be in the form of “123-456-7890”")
.Matches(@"^\d{3}-\d{3}-\d{4}$").WithMessage("Phone number must be a valid 10-digit phone number with dashes, in the form of “123-456-7890”");
RuleFor(x => x.Address)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your street address.")
.MinimumLength(6).WithMessage("Addresses should be at least 6 characters long.");
RuleFor(x => x.City)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your city.")
.MinimumLength(2).WithMessage("City names should be at least 2 characters long.");
RuleFor(x => x.ProvState)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Please provide your province or state.")
.Length(2).WithMessage("Please provide the 2-character code for your province or state.");
RuleFor(x => x.CountryId)
.NotEmpty().WithMessage("Please choose your country.");
RuleFor(x => x.HowHeardId)
.NotEmpty().WithMessage("How did you hear of us?");
RuleFor(x => x.TrainingSiteId)
.NotEmpty().WithMessage("Please choose a desired training site.");
}
}
}
我的表单构建正确:
@model Project.Models.MoreInfoViewModel
@{
ViewBag.Title = "More Info";
}
<h1>@ViewBag.Title</h1>
<p><span class="requiredcolor">These fields</span> are required.</p>
@using(Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationMessage("", new { @class = "alert" })
<div class="row">
<div class="form-group col-md-6">
@Html.LabelFor(x => x.FirstName, new { @class = "control-label required" })@Html.EditorFor(x => x.FirstName, new { htmlAttributes = new { @class = "form-control required", maxlength = 100 } })
@Html.ValidationMessageFor(x => x.FirstName)
</div>
<div class="form-group col-md-6">
@Html.LabelFor(x => x.LastName, new { @class = "control-label required" })@Html.EditorFor(x => x.LastName, new { htmlAttributes = new { @class = "form-control required", maxlength = 100 } })
@Html.ValidationMessageFor(x => x.LastName)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Phone, new { @class = "control-label required" })@Html.EditorFor(x => x.Phone, new { htmlAttributes = new { @class = "form-control required phone", maxlength = 12 } })
@Html.ValidationMessageFor(x => x.Phone)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Email, new { @class = "control-label required" })@Html.EditorFor(x => x.Email, new { htmlAttributes = new { @class = "form-control required", maxlength = 75 } })
@Html.ValidationMessageFor(x => x.Email)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Dob, new { @class = "control-label required" })@Html.EditorFor(x => x.Dob, new { htmlAttributes = new { @class = "form-control required datepicker" } })
@Html.ValidationMessageFor(x => x.Dob)
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
@Html.LabelFor(x => x.Address, new { @class = "control-label required" })@Html.EditorFor(x => x.Address, new { htmlAttributes = new { @class = "form-control required", maxlength = 150 } })
@Html.ValidationMessageFor(x => x.Address)
</div>
<div class="form-group col-md-6">
@Html.LabelFor(x => x.City, new { @class = "control-label required" })@Html.EditorFor(x => x.City, new { htmlAttributes = new { @class = "form-control required", maxlength = 50 } })
@Html.ValidationMessageFor(x => x.City)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
@Html.LabelFor(x => x.ProvState, new { @class = "control-label required" })@Html.EditorFor(x => x.ProvState, new { htmlAttributes = new { @class = "form-control required", maxlength = 2 } })
@Html.ValidationMessageFor(x => x.ProvState)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.CountryId, new { @class = "control-label required" })@Html.DropDownListFor(x => x.CountryId, Model.CountryList, "« ‹ Select › »", new { @class = "form-control required" })
@Html.ValidationMessageFor(x => x.CountryId)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Postal, new { @class = "control-label" })@Html.EditorFor(x => x.Postal, new { htmlAttributes = new { @class = "form-control postalcode", maxlength = 7 } })
@Html.ValidationMessageFor(x => x.Postal)
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
@Html.LabelFor(x => x.HowHeardId, new { @class = "control-label required" })@Html.DropDownListFor(x => x.HowHeardId, Model.HowHeardList, "« ‹ Select › »", new { @class = "form-control required" })
@Html.ValidationMessageFor(x => x.HowHeardId)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.TrainingSiteId, new { @class = "control-label required" })@Html.DropDownListFor(x => x.TrainingSiteId, Model.TrainingSiteList, "« ‹ Select › »", new { @class = "form-control required" })
@Html.ValidationMessageFor(x => x.TrainingSiteId)
</div>
<div class="form-group col-md-4">
@Html.LabelFor(x => x.Comments, new { @class = "control-label" })@Html.TextAreaFor(x => x.Comments, new { @class = "form-control", rows = 3 })
@Html.ValidationMessageFor(x => x.Comments)
</div>
</div>
<div class="blank-divider clearfix" style="height:30px;"></div>
<div class="row">
<div class="form-group col-md-6"> </div>
<div class="form-group col-md-6"><label class="control-label"> </label><input type="submit" value="Submit Request" alt="Submit Request" title="Submit Request" class="btn btn-default btn-success" /></div>
</div>
}
我完全不明白为什么 Fluent Validation 只在极其有限的情况下才会触发。
请理解,当它确实触发时——post-对所有其他字段提交贪婪验证,对下拉菜单进行服务器端验证——所有消息都完全符合预期。
但是,对于 any 字段,客户端和服务器端都不会触发验证。如果它触发服务器端,则无法触发客户端。如果它触发客户端,它只会在有限的条件下(字符串长度不够等)这样做,然后拒绝触发服务器端。
什么是 not 工作是通常的验证和任何捕获 .NotEmpty()
和 .NotNull()
.
编辑:
我看不出这是怎么可能的(没有从数据库中加载,只是插入其中),但是字符串字段验证是否会被数据库字段的格式? nchar
会成为这里的问题吗?
此外,我使用自定义模型收集数据,然后再将其添加到数据模型以插入数据库,因此我们与数据库结构的距离更远。从理论上讲,在将数据从 ViewModel 移动到 DataModel 之前,我看不出这种关系如何可能。
编辑 2:
.Cascade(CascadeMode.StopOnFirstFailure)
没有区别,在验证器 class 中有或没有这些问题都会出现。
编辑 3:
让我更准确地说明贪心验证。在所有情况下,任何 .NotEmpty()
和 .NotNull()
验证仍然无法触发,因此简单地从一个字段导航到下一个字段(点击提交后)将无法触发贪婪验证。只有当你输入一些东西,并且它不足以进行其他验证时(太短,不是有效的电子邮件,不是有效的 phone 号码等)贪婪验证才会触发。这就是为什么我想出我的第一个编辑(上图)的原因,因为也许系统没有将这些字符串字段视为空或空,即使它们是。
编辑 4:
WTF 更离奇。当我将 partial/incomplete 字符串放入不仅仅是长度分析的东西中时——例如只将电子邮件的前半部分放入电子邮件字段——然后点击提交,服务器端验证开始执行所有字符串字段,甚至 NULL/EMPTY 个字段。
喜欢,认真的威士忌。探戈。狐步舞。
编辑 5:
WTF x10:如果选择了三个下拉菜单,编辑 4 只会发生 。如果三个下拉列表中的任何一个仍未选中,服务器端验证将无法触发任何文本字段。
此外,如果选择了所有三个下拉菜单,使用 .NotEmpty()
和 .NotNull()
的完整验证会突然在所有文本字段上成功,包括服务器端 和 客户端贪婪验证。
神圣的玉米粉蒸肉。这越来越奇怪了。
这个问题源于我自从开始使用 Fluent Validation 以来的概念盲点。
自从我开始使用 Fluent Validation 之前,我就使用 GUID
s 作为主键。我在整个数据生命周期中使用了 GUID
s——从 ViewModel 到 View 再回到 Mapper,数据被转储回数据模型以添加 to/updating 数据库。
因此,当我使用下拉 select 菜单并将 GUID
用作主键时,那些 select 菜单旨在填充所需的外键,我能够将 ViewModel 字段设置为不可为 null 的 GUID
并仍然正确触发 Fluent Validation。当我想填充可选外键时,我为该字段使用了可为空的 GUID
,一切仍然有效。
这个项目使用的数据库早于我的参与。因此,它使用 int
作为主键,因此所需的某些外键也是 int
而不是 int?
。正因为如此,我做出了这样的假设——无论多么错误——我可以继续使用 int
来保存 所需外键 [=42] 的下拉菜单 select 的值=] 并能够成功验证其他所有内容,包括文本字段。
天哪,我错了。
Fluent Validation 的 Jeremy Skinner 名声大噪 took his Sunday out to assist me 并向我展示了我需要做什么。
本质上,任何整数驱动的下拉 select 菜单,无论是必需的外键还是可选的外键,都需要 ViewModel 中的 int?
来保存 selected值。如果没有可为 null 的 int?
,整个模型的验证将以意想不到的方式进行,以至于根本不会触发任何其他内容(根据我的经验)。
当我将该字段的值更改为 int?
并向 Mapper 添加空合并运算符时(通过提供任何通过 Fluent Validation 的空值的默认值),一切突然开始按预期工作。