模态 pop-up 中的表单:除了 drop-down 之外的所有内容都经过验证,即使在使用 .IsEmpty() 时也是如此
Form in modal pop-up: Everything is validated *except* for the drop-down, even when using .IsEmpty()
当前项目:
- ASP.NET 4.5.2
- MVC 5
- Entity Framework 6
- 流畅验证
所以我有一堆结构相同 table 的“笔记”,它们应该与至少两页的单个元素配对,并在第三页进行总结。所有需要注释的元素都是单个“循环”的一部分,因此元素都是相同 table 的片段,注释 table 挂在上面。例如,“演示文稿”由完成 (yes/no) 布尔值和周期 table 中的日期组成。演示文稿注释是一个单独的 table,仅针对那两个循环列,它挂在循环 table 之外(注释 table 有一个外键,它是循环的主键)。由于这些笔记仅用于演示,因此整个笔记 table 称为 PresentationNotes。循环中的许多其他元素都有自己的注释 table,并且整个项目中的所有注释 table 在结构上都是相同的。
从这个相同的结构中,我能够抽象出模型和视图,这样我就不必为每个笔记复制不同的 CRUD 模型和 CRUD 视图 table。我在控制器中所要做的就是为每个笔记取模型 table 并将特定条目与通用笔记模型中的通用条目相关联。
例如前面提到的Presentation模型:
namespace CCS.Models {
public class CycleNotesPresentation {
[Key]
public Guid NotesId { get; set; }
[DisplayName("Cycle")]
public Guid CycleId { get; set; }
[DisplayName("Comm. Type")]
public Guid NotesStatusId { get; set; }
[DisplayName("Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime NotesDate { get; set; }
[DisplayName("Notes")]
[DataType(DataType.MultilineText)]
public string Notes { get; set; }
#region Essentials
//Essential DB components for each and every table. Place at end.
[HiddenInput, DefaultValue(true)]
public bool Active { get; set; }
[HiddenInput, Timestamp, ConcurrencyCheck]
public byte[] RowVersion { get; set; }
[HiddenInput]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime Recorded { get; set; }
[HiddenInput]
public DateTime Modified { get; set; }
[HiddenInput]
public string TouchedBy { get; set; }
#endregion
[ForeignKey("CycleId")]
public virtual Cycle Cycle { get; set; }
[ForeignKey("NotesStatusId")]
public virtual NotesStatus NotesStatus { get; set; }
}
}
如您所见,这里有很多不一定需要在抽象模型和视图中。
抽象的 Notes 模型,至少对于 Create,是这样的:
[Validator(typeof(CreateNotesValidator))]
public class CreateNotes {
public string NotesCategory { get; set; }
[DisplayName("Comm. Type")]
public string NotesStatusId { get; set; }
[DisplayName("Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime NotesDate { get; set; }
[DisplayName("Notes")]
public string Notes { get; set; }
}
当然,我还有其他三个模型:查看、编辑和删除,但我们现在只关注这个。如果我可以修复创建,我可以修复编辑,这是唯一一个具有需要 client-side 验证的 drop-down 菜单的其他菜单。
请注意上面的区别 -- NotesStatusId
字段实际上是一个字符串,而不是一个 Guid。好吧,事实证明,如果我一直使用 Guid,我的 client-side 验证选项将非常有限。另外,client-side 验证仍然不适用于 Guid,因此我决定通过使用字符串来简化模型(以及验证)。
因此,当我拉取原始 Presentation 模型时,我会将 Guid 转换为字符串,当我处理 Notes 模型并将其转储回 Presentation 模型时,我会将字符串转换回 Guid。这让我有更多 client-side 验证选项。
整个过程我的控制器是这样的:
// GET: Onboarding/CreateCycleNotesPresentation
[HttpGet]
public ActionResult CreateCycleNotesPresentation() {
var model = new CreateNotes() {
NotesCategory = "Presentation",
NotesDate = DateTime.Now
};
ViewBag.NotesStatusId = new SelectList(db.NotesStatus.Where(x => x.Active == true), "NotesStatusId", "NotesStatusName");
return PartialView("_CreateNotesPartial", model);
}
// POST: Onboarding/CreateCycleNotesPresentation
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> CreateCycleNotesPresentation(CreateNotes model) {
if(ModelState.IsValid) {
var id = new Guid(User.GetClaimValue("CWD-Cycle"));
CycleNotesPresentation cycleNotes = new CycleNotesPresentation();
cycleNotes.NotesId = new Guid();
cycleNotes.CycleId = id;
cycleNotes.NotesStatusId = new Guid(model.NotesStatusId);
cycleNotes.NotesDate = model.NotesDate;
cycleNotes.Notes = model.Notes;
cycleNotes.Active = true;
cycleNotes.Recorded = DateTime.UtcNow;
cycleNotes.Modified = DateTime.UtcNow;
cycleNotes.TouchedBy = User.Identity.GetFullNameLF();
db.CycleNotesPresentation.Add(cycleNotes);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
model.NotesCategory = "Presentation";
ViewBag.NotesStatusId = new SelectList(db.NotesStatus.Where(x => x.Active == true), "NotesStatusId", "NotesStatusName", model.NotesStatusId);
return PartialView("_CreateNotesPartial", model);
}
在这里我们可以看到一些有趣的部分 -- 我添加了一个 NotesCategory
条目,以便视图可以填充要添加注释的元素的标题。这个最后就不处理了
我还将以刷新整个页面来结束 POST。我发现这是最简单的解决方案,因为我无法使 JSON 提交正常工作(实际的 POST 方法从未收到数据,因此提交会挂起)。此外,通过 whole-page 刷新,整个页面效果更好。所以让我们别管它,k?
现在是最重要的事情:抽象的 Notes 模型和视图的验证器:
namespace CCS.Validators {
class NotesValidator {
}
public class CreateNotesValidator : AbstractValidator<CreateNotes> {
public CreateNotesValidator() {
RuleFor(x => x.NotesDate)
.NotEmpty().WithMessage("Please select a date that this communication occurred on.");
RuleFor(x => x.NotesStatusId)
.NotEmpty().NotNull().WithMessage("Please indicate what type of communication occurred.");
RuleFor(x => x.Notes)
.NotEmpty().WithMessage("Please submit notes of some kind.")
.Length(2, 4000).WithMessage("Please provide notes of some substantial length.");
}
}
public class EditNotesValidator : AbstractValidator<EditNotes> {
public EditNotesValidator() {
RuleFor(x => x.NotesDate)
.NotEmpty().WithMessage("Please select a date that this communication occurred on.");
RuleFor(x => x.NotesStatusId)
.NotNull().NotEmpty().NotEqual("00000000-0000-0000-0000-000000000000").Matches("^[{(]?[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$").WithMessage("Please indicate what type of communication occurred.");
RuleFor(x => x.Notes)
.NotEmpty().WithMessage("Please submit notes of some kind.")
.Length(2, 4000).WithMessage("Please provide notes of some substantial length.");
}
}
}
我们现在可以在很大程度上忽略 EditNotesValidator
,因为这不是我们正在做的事情。
该视图是抽象注释的简单部分,表单本身与您所能得到的一样普通:
@model CCS.Models.CreateNotes
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="modal-title">Create Note for “@Model.NotesCategory”</h3>
</div>
@using(Html.BeginForm()) {
@Html.AntiForgeryToken()
<div class="modal-body">
<fieldset>
@Html.LabelFor(m => Model.NotesDate, new { @class = "control-label" })<div class="input-group date">@Html.TextBoxFor(m => m.NotesDate, "{0:yyyy-MM-dd}", new { @class = "form-control date" })<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span></div>
@Html.ValidationMessageFor(m => m.NotesDate)
@Html.LabelFor(m => Model.NotesStatusId, new { @class = "control-label" })@Html.DropDownList("NotesStatusId", null, "« ‹ Select › »", htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.NotesStatusId)
@Html.LabelFor(m => Model.Notes, new { @class = "control-label" })@Html.TextAreaFor(m => m.Notes, new { @class = "form-control required" })
@Html.ValidationMessageFor(m => m.Notes)
</fieldset>
</div>
<div class="modal-footer">
<span id="progress" class="text-center" style="display: none;">
<img src="/images/wait.gif" alt="wait" />
Wait..
</span>
<button type="submit" value="Save" title="Save" class="btn btn-primary glyphicon glyphicon-floppy-disk"></button>
<button class="btn btn-warning" data-dismiss="modal">Close</button>
</div>
}
<script>
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
$(function () {
$.fn.datepicker.defaults.format = "yyyy-mm-dd";
$(".date").datepicker();
});
</script>
所以,是的。日期验证器完全按预期工作。 Notes textarea 得到了很好的验证。然而,drop-down 菜单完全是午餐时间——无论我尝试什么,无论是 .NotEmpty()
还是 .NotNull()
或任何其他被 FluentValidation 明确标记为在客户端起作用的东西, drop-down 菜单无效。原始 HTML 的检查表明我正在正确构建 SelectList:
<select id="NotesStatusId" class="form-control" name="NotesStatusId">
<option value="">« ‹ Select › »</option>
<option value="98e9f033-20df-e511-8265-14feb5fbeae8">Phone Call</option>
<option value="4899dd4d-20df-e511-8265-14feb5fbeae8">eMail</option>
<option value="8c073863-20df-e511-8265-14feb5fbeae8">Voice Mail</option>
<option value="8a13ec76-20df-e511-8265-14feb5fbeae8">Meeting</option>
</select>
而默认 « ‹ Select › »
第一个选项 should 的空值意味着 .NotEmpty()
和 .NotNull()
should 完美运行。但他们不是。如果我删除日期(表单加载时 auto-filled,请参阅上面的控制器)并保持 drop-down 和文本区域不变,只有日期字段和文本区域会被标记——drop-down 根本没有被标记。
建议?
编辑 1: 哎呀,哎呀……添加了错误的控制器……现在修复了。
编辑 2: …Bueller? …布勒?
编辑 3: 我发现很难相信 no-one else 在 drop-down 上进行 client-side 验证时遇到过问题通过 FluentValidation 的菜单。
这是献给所有追随我的可怜人的。具体来说,我的情况涉及:
- 模态对话框中的表单。
- 模态对话框是用通用部分启动的,网站的许多不同部分使用该部分来填充具有相同结构的多个 table。因此,单个通用 Partial/Form 可以在许多地方用于许多不同的 identically-structured tables
- 模态对话框关闭以刷新整个页面,没有任何 JSON。
- 因为这是模态对话框中的一个表单,并且因为模态本身无法刷新(我不知道如何刷新),所以所有验证都必须在客户端进行。这是我遇到的最重要的问题。
- 由于 select 菜单的创建方式,客户端验证未按需要运行。
我解决这个问题的方法部分是偶然发现的,部分是放弃了不明智的方法。具体来说,ViewBag
s。在这里我了解到 ViewBag
s 使 client-side 验证 drop-down selects 不可能,当他们完成填充 drop-down selects 的工作时需要验证。
因此,部分偶然的运气与我使用控制器和模型的方式有关。因为站点的各个不同部分有多个 identically-structured 注释 table,所以我能够“抽象出”整个模型、注释的视图和验证,以便抽象 collection 可以处理多个 Notes table 的全部 CRUD 需求。代码重用,FTW!。如果可能的话,我还将寻求抽象出控制器的一部分,但那是另一天的事情。
所以看看我原来的内容post,我的抽象笔记的创建和编辑部分的模型有一个非常简单的添加:
public IList<SelectListItem> NotesStatus { get; set; }
您看,NotesStatusId 是 NotesStatus table 的外键,它具有基本的通信详细信息 -- phone、电子邮件、会议、语音邮件等。所以我需要告诉模型我要从这个 table.
中列出一个列表
接下来是我的控制器。因为我已经采用特定的 Notes 模型并将它们填充到抽象的 Notes 模型中,所以我能够扩展它以包含 drop-down 菜单的内容,而不是将其填充到 ViewBag
中。将我的控制器上面的内容与下面的内容进行比较:
[HttpGet]
public async Task<ActionResult> CreateProspectingNotes() {
var model = new CreateNotes() { // We just need to set up the abstracted Notes model with a create -- no populating from the db needed.
NotesCategory = "Prospecting",
NotesDate = DateTime.Now,
NotesStatus = await db.NotesStatus.Where(x => x.Active).Select(x => new SelectListItem { Text = x.NotesStatusName, Value = x.NotesStatusId.ToString() }).ToListAsync()
};
return PartialView("_CreateNotesPartial", model);
}
看看我们如何用最终出现在视图中的 SelectList 填充模型的 NotesStatus 部分?
编辑有点复杂,因为我们不仅要调出抽象的笔记,还要用你想编辑的笔记table中的内容填充它:
[HttpGet]
public async Task<ActionResult> EditProspectingNotes(Guid? id) {
ProspectingNotes prospectingNotes = await db.ProspectingNotes.FindAsync(id); // getting the specific ProspectingNotes table that is to be edited.
if(prospectingNotes == null) { return HttpNotFound(); }
EditNotes model = new EditNotes() { // Populating the abstracted Notes model with the specific ProspectingNotes model.
NotesCategory = "Prospecting",
NotesId = prospectingNotes.NotesId,
NotesStatusId = Convert.ToString(prospectingNotes.NotesStatusId),
NotesDate = prospectingNotes.NotesDate,
Notes = prospectingNotes.Notes,
NotesStatus = await db.NotesStatus.Where(x => x.Active).Select(x => new SelectListItem { Text = x.NotesStatusName, Value = x.NotesStatusId.ToString() }).ToListAsync()
};
return PartialView("_EditNotesPartial", model);
}
现在进入视图:
@Html.LabelFor(m => Model.NotesStatusId, new { @class = "control-label" })@Html.DropDownListFor(x => x.NotesStatusId, new SelectList(Model.NotesStatus, "Value", "text"), "« ‹ Select › »", htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.NotesStatusId)
特别是 .DropDownList()
被 .DropDownListFor()
取代,因为我们现在将它牢固地绑定到 x => x.NotesStatusId
而不是 loosely-coupled "NotesStatusId"
调用 ViewBag,我认为它是整个 client-side 工作的关键。使用 ViewBag,您只是用一个已经具有默认值 selected 的列表填充 drop-down,在这里您将默认值绑定到列表,然后直接从 [=62] 填充它=].因为是强耦合的,所以现在有东西要client-side验证来验证。
一旦我完成了所有这些,所要做的就是确保我的验证只有一个 .NotEmpty()
而不是像 .NotEmpty().NotNull()
这样确实抛出异常的双链(double REQUIRED,显然)。
希望对您有所帮助。如果您自己遇到问题,请创建一个引用此问题的 post 并给我发送 PM。我会看看能帮上什么忙。
当前项目:
- ASP.NET 4.5.2
- MVC 5
- Entity Framework 6
- 流畅验证
所以我有一堆结构相同 table 的“笔记”,它们应该与至少两页的单个元素配对,并在第三页进行总结。所有需要注释的元素都是单个“循环”的一部分,因此元素都是相同 table 的片段,注释 table 挂在上面。例如,“演示文稿”由完成 (yes/no) 布尔值和周期 table 中的日期组成。演示文稿注释是一个单独的 table,仅针对那两个循环列,它挂在循环 table 之外(注释 table 有一个外键,它是循环的主键)。由于这些笔记仅用于演示,因此整个笔记 table 称为 PresentationNotes。循环中的许多其他元素都有自己的注释 table,并且整个项目中的所有注释 table 在结构上都是相同的。
从这个相同的结构中,我能够抽象出模型和视图,这样我就不必为每个笔记复制不同的 CRUD 模型和 CRUD 视图 table。我在控制器中所要做的就是为每个笔记取模型 table 并将特定条目与通用笔记模型中的通用条目相关联。
例如前面提到的Presentation模型:
namespace CCS.Models {
public class CycleNotesPresentation {
[Key]
public Guid NotesId { get; set; }
[DisplayName("Cycle")]
public Guid CycleId { get; set; }
[DisplayName("Comm. Type")]
public Guid NotesStatusId { get; set; }
[DisplayName("Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime NotesDate { get; set; }
[DisplayName("Notes")]
[DataType(DataType.MultilineText)]
public string Notes { get; set; }
#region Essentials
//Essential DB components for each and every table. Place at end.
[HiddenInput, DefaultValue(true)]
public bool Active { get; set; }
[HiddenInput, Timestamp, ConcurrencyCheck]
public byte[] RowVersion { get; set; }
[HiddenInput]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime Recorded { get; set; }
[HiddenInput]
public DateTime Modified { get; set; }
[HiddenInput]
public string TouchedBy { get; set; }
#endregion
[ForeignKey("CycleId")]
public virtual Cycle Cycle { get; set; }
[ForeignKey("NotesStatusId")]
public virtual NotesStatus NotesStatus { get; set; }
}
}
如您所见,这里有很多不一定需要在抽象模型和视图中。
抽象的 Notes 模型,至少对于 Create,是这样的:
[Validator(typeof(CreateNotesValidator))]
public class CreateNotes {
public string NotesCategory { get; set; }
[DisplayName("Comm. Type")]
public string NotesStatusId { get; set; }
[DisplayName("Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime NotesDate { get; set; }
[DisplayName("Notes")]
public string Notes { get; set; }
}
当然,我还有其他三个模型:查看、编辑和删除,但我们现在只关注这个。如果我可以修复创建,我可以修复编辑,这是唯一一个具有需要 client-side 验证的 drop-down 菜单的其他菜单。
请注意上面的区别 -- NotesStatusId
字段实际上是一个字符串,而不是一个 Guid。好吧,事实证明,如果我一直使用 Guid,我的 client-side 验证选项将非常有限。另外,client-side 验证仍然不适用于 Guid,因此我决定通过使用字符串来简化模型(以及验证)。
因此,当我拉取原始 Presentation 模型时,我会将 Guid 转换为字符串,当我处理 Notes 模型并将其转储回 Presentation 模型时,我会将字符串转换回 Guid。这让我有更多 client-side 验证选项。
整个过程我的控制器是这样的:
// GET: Onboarding/CreateCycleNotesPresentation
[HttpGet]
public ActionResult CreateCycleNotesPresentation() {
var model = new CreateNotes() {
NotesCategory = "Presentation",
NotesDate = DateTime.Now
};
ViewBag.NotesStatusId = new SelectList(db.NotesStatus.Where(x => x.Active == true), "NotesStatusId", "NotesStatusName");
return PartialView("_CreateNotesPartial", model);
}
// POST: Onboarding/CreateCycleNotesPresentation
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> CreateCycleNotesPresentation(CreateNotes model) {
if(ModelState.IsValid) {
var id = new Guid(User.GetClaimValue("CWD-Cycle"));
CycleNotesPresentation cycleNotes = new CycleNotesPresentation();
cycleNotes.NotesId = new Guid();
cycleNotes.CycleId = id;
cycleNotes.NotesStatusId = new Guid(model.NotesStatusId);
cycleNotes.NotesDate = model.NotesDate;
cycleNotes.Notes = model.Notes;
cycleNotes.Active = true;
cycleNotes.Recorded = DateTime.UtcNow;
cycleNotes.Modified = DateTime.UtcNow;
cycleNotes.TouchedBy = User.Identity.GetFullNameLF();
db.CycleNotesPresentation.Add(cycleNotes);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
model.NotesCategory = "Presentation";
ViewBag.NotesStatusId = new SelectList(db.NotesStatus.Where(x => x.Active == true), "NotesStatusId", "NotesStatusName", model.NotesStatusId);
return PartialView("_CreateNotesPartial", model);
}
在这里我们可以看到一些有趣的部分 -- 我添加了一个 NotesCategory
条目,以便视图可以填充要添加注释的元素的标题。这个最后就不处理了
我还将以刷新整个页面来结束 POST。我发现这是最简单的解决方案,因为我无法使 JSON 提交正常工作(实际的 POST 方法从未收到数据,因此提交会挂起)。此外,通过 whole-page 刷新,整个页面效果更好。所以让我们别管它,k?
现在是最重要的事情:抽象的 Notes 模型和视图的验证器:
namespace CCS.Validators {
class NotesValidator {
}
public class CreateNotesValidator : AbstractValidator<CreateNotes> {
public CreateNotesValidator() {
RuleFor(x => x.NotesDate)
.NotEmpty().WithMessage("Please select a date that this communication occurred on.");
RuleFor(x => x.NotesStatusId)
.NotEmpty().NotNull().WithMessage("Please indicate what type of communication occurred.");
RuleFor(x => x.Notes)
.NotEmpty().WithMessage("Please submit notes of some kind.")
.Length(2, 4000).WithMessage("Please provide notes of some substantial length.");
}
}
public class EditNotesValidator : AbstractValidator<EditNotes> {
public EditNotesValidator() {
RuleFor(x => x.NotesDate)
.NotEmpty().WithMessage("Please select a date that this communication occurred on.");
RuleFor(x => x.NotesStatusId)
.NotNull().NotEmpty().NotEqual("00000000-0000-0000-0000-000000000000").Matches("^[{(]?[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$").WithMessage("Please indicate what type of communication occurred.");
RuleFor(x => x.Notes)
.NotEmpty().WithMessage("Please submit notes of some kind.")
.Length(2, 4000).WithMessage("Please provide notes of some substantial length.");
}
}
}
我们现在可以在很大程度上忽略 EditNotesValidator
,因为这不是我们正在做的事情。
该视图是抽象注释的简单部分,表单本身与您所能得到的一样普通:
@model CCS.Models.CreateNotes
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="modal-title">Create Note for “@Model.NotesCategory”</h3>
</div>
@using(Html.BeginForm()) {
@Html.AntiForgeryToken()
<div class="modal-body">
<fieldset>
@Html.LabelFor(m => Model.NotesDate, new { @class = "control-label" })<div class="input-group date">@Html.TextBoxFor(m => m.NotesDate, "{0:yyyy-MM-dd}", new { @class = "form-control date" })<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span></div>
@Html.ValidationMessageFor(m => m.NotesDate)
@Html.LabelFor(m => Model.NotesStatusId, new { @class = "control-label" })@Html.DropDownList("NotesStatusId", null, "« ‹ Select › »", htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.NotesStatusId)
@Html.LabelFor(m => Model.Notes, new { @class = "control-label" })@Html.TextAreaFor(m => m.Notes, new { @class = "form-control required" })
@Html.ValidationMessageFor(m => m.Notes)
</fieldset>
</div>
<div class="modal-footer">
<span id="progress" class="text-center" style="display: none;">
<img src="/images/wait.gif" alt="wait" />
Wait..
</span>
<button type="submit" value="Save" title="Save" class="btn btn-primary glyphicon glyphicon-floppy-disk"></button>
<button class="btn btn-warning" data-dismiss="modal">Close</button>
</div>
}
<script>
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
$(function () {
$.fn.datepicker.defaults.format = "yyyy-mm-dd";
$(".date").datepicker();
});
</script>
所以,是的。日期验证器完全按预期工作。 Notes textarea 得到了很好的验证。然而,drop-down 菜单完全是午餐时间——无论我尝试什么,无论是 .NotEmpty()
还是 .NotNull()
或任何其他被 FluentValidation 明确标记为在客户端起作用的东西, drop-down 菜单无效。原始 HTML 的检查表明我正在正确构建 SelectList:
<select id="NotesStatusId" class="form-control" name="NotesStatusId">
<option value="">« ‹ Select › »</option>
<option value="98e9f033-20df-e511-8265-14feb5fbeae8">Phone Call</option>
<option value="4899dd4d-20df-e511-8265-14feb5fbeae8">eMail</option>
<option value="8c073863-20df-e511-8265-14feb5fbeae8">Voice Mail</option>
<option value="8a13ec76-20df-e511-8265-14feb5fbeae8">Meeting</option>
</select>
而默认 « ‹ Select › »
第一个选项 should 的空值意味着 .NotEmpty()
和 .NotNull()
should 完美运行。但他们不是。如果我删除日期(表单加载时 auto-filled,请参阅上面的控制器)并保持 drop-down 和文本区域不变,只有日期字段和文本区域会被标记——drop-down 根本没有被标记。
建议?
编辑 1: 哎呀,哎呀……添加了错误的控制器……现在修复了。
编辑 2: …Bueller? …布勒?
编辑 3: 我发现很难相信 no-one else 在 drop-down 上进行 client-side 验证时遇到过问题通过 FluentValidation 的菜单。
这是献给所有追随我的可怜人的。具体来说,我的情况涉及:
- 模态对话框中的表单。
- 模态对话框是用通用部分启动的,网站的许多不同部分使用该部分来填充具有相同结构的多个 table。因此,单个通用 Partial/Form 可以在许多地方用于许多不同的 identically-structured tables
- 模态对话框关闭以刷新整个页面,没有任何 JSON。
- 因为这是模态对话框中的一个表单,并且因为模态本身无法刷新(我不知道如何刷新),所以所有验证都必须在客户端进行。这是我遇到的最重要的问题。
- 由于 select 菜单的创建方式,客户端验证未按需要运行。
我解决这个问题的方法部分是偶然发现的,部分是放弃了不明智的方法。具体来说,ViewBag
s。在这里我了解到 ViewBag
s 使 client-side 验证 drop-down selects 不可能,当他们完成填充 drop-down selects 的工作时需要验证。
因此,部分偶然的运气与我使用控制器和模型的方式有关。因为站点的各个不同部分有多个 identically-structured 注释 table,所以我能够“抽象出”整个模型、注释的视图和验证,以便抽象 collection 可以处理多个 Notes table 的全部 CRUD 需求。代码重用,FTW!。如果可能的话,我还将寻求抽象出控制器的一部分,但那是另一天的事情。
所以看看我原来的内容post,我的抽象笔记的创建和编辑部分的模型有一个非常简单的添加:
public IList<SelectListItem> NotesStatus { get; set; }
您看,NotesStatusId 是 NotesStatus table 的外键,它具有基本的通信详细信息 -- phone、电子邮件、会议、语音邮件等。所以我需要告诉模型我要从这个 table.
中列出一个列表接下来是我的控制器。因为我已经采用特定的 Notes 模型并将它们填充到抽象的 Notes 模型中,所以我能够扩展它以包含 drop-down 菜单的内容,而不是将其填充到 ViewBag
中。将我的控制器上面的内容与下面的内容进行比较:
[HttpGet]
public async Task<ActionResult> CreateProspectingNotes() {
var model = new CreateNotes() { // We just need to set up the abstracted Notes model with a create -- no populating from the db needed.
NotesCategory = "Prospecting",
NotesDate = DateTime.Now,
NotesStatus = await db.NotesStatus.Where(x => x.Active).Select(x => new SelectListItem { Text = x.NotesStatusName, Value = x.NotesStatusId.ToString() }).ToListAsync()
};
return PartialView("_CreateNotesPartial", model);
}
看看我们如何用最终出现在视图中的 SelectList 填充模型的 NotesStatus 部分?
编辑有点复杂,因为我们不仅要调出抽象的笔记,还要用你想编辑的笔记table中的内容填充它:
[HttpGet]
public async Task<ActionResult> EditProspectingNotes(Guid? id) {
ProspectingNotes prospectingNotes = await db.ProspectingNotes.FindAsync(id); // getting the specific ProspectingNotes table that is to be edited.
if(prospectingNotes == null) { return HttpNotFound(); }
EditNotes model = new EditNotes() { // Populating the abstracted Notes model with the specific ProspectingNotes model.
NotesCategory = "Prospecting",
NotesId = prospectingNotes.NotesId,
NotesStatusId = Convert.ToString(prospectingNotes.NotesStatusId),
NotesDate = prospectingNotes.NotesDate,
Notes = prospectingNotes.Notes,
NotesStatus = await db.NotesStatus.Where(x => x.Active).Select(x => new SelectListItem { Text = x.NotesStatusName, Value = x.NotesStatusId.ToString() }).ToListAsync()
};
return PartialView("_EditNotesPartial", model);
}
现在进入视图:
@Html.LabelFor(m => Model.NotesStatusId, new { @class = "control-label" })@Html.DropDownListFor(x => x.NotesStatusId, new SelectList(Model.NotesStatus, "Value", "text"), "« ‹ Select › »", htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.NotesStatusId)
特别是 .DropDownList()
被 .DropDownListFor()
取代,因为我们现在将它牢固地绑定到 x => x.NotesStatusId
而不是 loosely-coupled "NotesStatusId"
调用 ViewBag,我认为它是整个 client-side 工作的关键。使用 ViewBag,您只是用一个已经具有默认值 selected 的列表填充 drop-down,在这里您将默认值绑定到列表,然后直接从 [=62] 填充它=].因为是强耦合的,所以现在有东西要client-side验证来验证。
一旦我完成了所有这些,所要做的就是确保我的验证只有一个 .NotEmpty()
而不是像 .NotEmpty().NotNull()
这样确实抛出异常的双链(double REQUIRED,显然)。
希望对您有所帮助。如果您自己遇到问题,请创建一个引用此问题的 post 并给我发送 PM。我会看看能帮上什么忙。