MVC验证基础(使用实体框架)
情景:
我有一个如下的模型类(通过Entity Framework EF.x DbContext Generator自动生成)。
(目前没有视图模型)。
public partial class Activity
{
public int Id { get; set; }
public byte Progress { get; set; }
public decimal ValueInContractCurrency { get; set; }
public System.DateTime ForecastStart { get; set; }
public System.DateTime ForecastEnd { get; set; }
public int DepartmentId { get; set; }
public int OwnerId { get; set; }
public int StageId { get; set; }
public int StatusId { get; set; }
public virtual Department Department { get; set; }
public virtual Owner Owner { get; set; }
public virtual Stage Stage { get; set; }
public virtual Status Status { get; set; }
}
当我在强类型视图上提交空白表单时,我会收到以下验证消息:
“进度”字段是必需的。
ValueInContractCurrency字段是必需的。
ForecastStart字段是必需的。
需要ForecastEnd字段。
即。 db表中的所有字段。
如果我填写这些内容并再次提交,则会调用控制器。然后,由于IsValid为假,控制器返回到视图页面。
然后使用以下验证消息重新显示屏幕:
StageId字段是必需的。
DepartmentId字段是必需的。
StatusId字段是必需的。
OwnerId字段是必需的。
即。 db表中的所有外键字段(这些也是所有选择框)。
如果我填写这些表格,则表格会成功提交并保存到数据库中。
问题:
鉴于我没有使用任何[必需]属性,验证来自哪里?这与实体框架有关吗?
为什么表单没有立即验证客户端的所有内容,外键(或选择框)的不同之处在于它们仅由IsValid()检查,即使它们是空的,因此显然无效?
如何让所有内容在一个步骤中得到验证(对于空字段),因此用户不必提交表单两次,所有验证消息都会立即显示?您是否必须关闭客户端验证?
(我尝试将[Required]属性添加到外键字段,但这似乎没有任何区别(可能它们只会影响IsValid)。我也尝试调用Html.EnableClientValidation()但是没有任何差异)。
4 ..最后,我见过人们使用[MetadataType [MetadataType(typeof(...)]]进行验证。如果你有一个viewmodel,你为什么要这样做,或者只有你没有?
显然我在这里缺少一些基础知识,所以如果有人知道关于MVC验证过程如何按步骤包括javascript /控制器调用的详细教程,而不仅仅是关于属性的另一篇文章,那么我也可以链接到这个:c)
Mystere Man的更多信息:
解决方案设置如下:
.NET4
MVC3
EF5
EF5.x Db上下文生成器
在edmx设计图面上使用“添加代码生成项”以关联EF.x Db上下文生成器文件(.tt文件)
控制器看起来像这样:
// GET: /Activities/Create
public ActionResult Create()
{
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageId = new SelectList(new List<string>());
ViewBag.StatusId = new SelectList(db.Status.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
return View();
}
// POST: /Activities/Create
[HttpPost]
public ActionResult Create(Activity activity)
{
if (ModelState.IsValid)
{
db.Activities.Add(activity);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageId = new SelectList(db.Stages, "Id", "Number");
ViewBag.StatusId = new SelectList(db.Status, "Id", "Name");
return View(activity);
}
查看是这样的:
<!-- this refers to the EF.x DB Context class shown at the top of this post -->
@model RDMS.Activity
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Activity</legend>
<div class="editor-label">
@Html.LabelFor(model => model.StageId, "Stage")
</div>
<div class="editor-field">
@Html.DropDownList("StageId", String.Empty)
@Html.ValidationMessageFor(model => model.StageId)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Progress)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Progress)
@Html.ValidationMessageFor(model => model.Progress)
</div>
<!-- ETC...-->
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
答案 0 :(得分:1)
鉴于我没有使用任何[必需]属性,验证来自何处?这与实体框架有关吗?
MVC(不是EF)中有一个默认验证提供程序,检查两件事:
提供的值的类型(int属性中的字符串)=&gt; (不确定,但有点像)yyy is not valid for field xxx
值类型的“检查空”属性(如果你让一个空字段对应一个int
属性,它将会抱怨,并且会接受int?
属性的空字段) 。 =&GT; The xxx field is required
可以在global.asax中取消激活第二个行为(属性名称相当清楚):
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
启用客户端验证后,这些验证以及与DataAnnotations(Required
,StringLength
...)相关的验证将在转到Controller之前在客户端引发验证错误。它避免了服务器上的往返,因此它没有用处。但是,当然,您不能仅依赖客户端验证。
嗯,我必须承认我没有得到令人满意的答案......所以我让这一个更有能力。它们在ModelState.IsValid中被视为错误,因为当ClientSide Validation传递时,您将转到ModelBinding(模型绑定查看您的POSTed值,查看相应HttpPost方法的参数(为什么表单没有立即验证客户端的所有内容,外键(或选择框)的不同之处仅在于它们是 由IsValid()检查,即使它们是空的,因此很清楚 无效?
ActionResult Create
为您) ,并尝试将POSTed值与这些参数绑定。在您的情况下,绑定会看到Activity activity
参数。并且他在POSTed字段中没有为StageId
(例如)获取任何内容。 StageId不可为空,他把它作为ModelState字典中的错误=&gt; ModelState不再有效。
但我不知道为什么即使使用Required
属性也没有被客户端验证捕获。
如何让所有内容在一步中得到验证(为空 字段),因此用户不必两次提交表单 验证消息一次显示?你必须关闭客户端吗? 边验证?
嗯,您必须关闭客户端验证,因为您不能仅信任客户端验证。但是,如上所述,客户端验证可以避免无用的往返服务器。
最后,我看到有人在使用 [MetadataType(typeof(...)]]用于验证。为什么选择 如果你有一个视图模型,或者只有你没有视图模型,那么这样做吗?
只有当您没有ViewModel时,才能使用您的Model类。只有在使用Model First或Database First时,它才有用,因为每次edmx更改时都会生成(使用T4)实体类。然后,如果您在类上放置自定义数据注释,则必须在每个类(文件)生成后手动将其放回,这将是愚蠢的。所以[MetadataType(typeof()]]
是一种在类上添加注释的方法,即使重新生成“基类文件”。
希望这有点帮助。
顺便说一句,如果您对验证感兴趣,请查看FluentValidation。 这是一个非常好的...流畅的验证(你猜对了吗?)库。
答案 1 :(得分:1)
您获得必要验证的原因是因为属性是值类型(即它们不能为null)。由于它们不能为null,因此框架要求您为它们填充值(否则它将抛出一些奇怪的异常)。
这个问题以多种方式表现出来。我在Slashdot上一遍又一遍地看到这个。我不确定为什么这么多人陷入这个问题,但这很常见。通常这会导致一个奇怪的异常,指的是没有抛出默认构造函数,但由于某种原因,这里没有发生。
问题源于您使用ViewBag并将ViewBag中的项目命名为与模型属性相同。提交页面时,模型绑定器会被类似命名的项目混淆。
更改这些以在最后添加列表:
ViewBag.DepartmentList = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerList = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractList = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageList = new SelectList(new List<string>());
ViewBag.StatusList = new SelectList(db.Status
.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
并更改您的视图以使用强类型DropDownListFor:
@Html.DropDownList(x => x.StageId, ViewBag.StageList, string.Empty)
... and so on
另外一项注意事项。在上面的例子中,我希望你没有使用某种全局数据上下文,或者更糟糕的是,单身。这将是灾难性的,并可能导致数据损坏。
如果db只是你在构造函数中新建的控制器的成员,那没关系,虽然不理想。更好的方法是在每个操作方法中创建一个新的上下文,由using语句包装(然后立即关闭和销毁连接)或在控制器上实现IDisposable并显式调用Dispose。
更好的方法是在你的控制器中不做任何这样的事情,而是在业务层中,但这可以等到你继续。
答案 2 :(得分:0)