1

MVC 验证基础(使用实体框架)

设想:

我有一个模型类如下(通过实体框架 EF.x DbContext 生成器自动生成)。

(目前没有视图模型)。

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 表中的所有外键字段(这些也是所有选择框)。

如果我填写这些,表单就会成功提交并保存到数据库中。

问题:

  1. 鉴于我没有使用任何 [Required] 属性,验证从何而来?这与实体框架有关吗?

  2. 为什么表单没有立即在客户端验证所有内容,外键(或选择框)有什么不同,它们仅由 IsValid() 检查,即使它们是空的,因此显然无效?

  3. 您如何使所有内容都在一个步骤中得到验证(对于空字段),因此用户不必提交两次表单并且所有验证消息都会立即显示?您是否必须关闭客户端验证?

(我尝试将 [Required] 属性添加到外键字段,但这似乎没有任何区别(可能它们只影响 IsValid)。我也尝试调用 Html.EnableClientValidation() 但这也没有任何区别)。

4..最后,我看到有人使用 [MetadataType[MetadataType(typeof(...)]] 进行验证。如果您有视图模型,为什么要这样做,或者只有在没有视图模型时才这样做?

显然我在这里遗漏了一些基础知识,因此,如果有人知道有关 MVC 验证过程如何逐步工作的详细教程,包括 javascript/controller 调用,而不仅仅是关于属性的另一篇文章,那么我可以做也有一个链接:c)


更多关于神秘人的信息:

解决方案设置如下:

.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>
}
4

3 回答 3

1

您获得所需验证的原因是因为属性是值类型(即它们不能为空)。由于它们不能为空,因此框架要求您为它们填写值(否则将不得不抛出一些奇怪的异常)。

这个问题表现在几个方面。我在 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。

更好的方法是不在您的控制器中执行任何此操作,而是在业务层中执行此操作,但这可以等到您更进一步。

于 2012-10-26T03:18:43.270 回答
1

鉴于我没有使用任何 [Required] 属性,验证从何而来?这与实体框架有关吗?

MVC(不是 EF)中有一个默认的验证提供程序,检查两件事:

  • 提供的值的类型(int 属性中的字符串)=>(不确定,但类似)yyy is not valid for field xxx

  • 值类型的“检查空”属性(如果您让一个空字段对应于一个int属性,它会抱怨,并且会接受一个int?属性的空字段)。=>The xxx field is required

可以在 global.asax 中停用第二种行为(属性名称相当清楚):

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

启用客户端验证后,这些验证以及与 DataAnnotations ( Required, StringLength...) 相关的验证将在客户端引发验证错误,然后再转到控制器。它避免了服务器上的往返,所以它不是没用的。但当然,您不能只依赖客户端验证。

为什么表单没有立即在客户端验证所有内容,外键(或选择框)有什么不同,它们仅由 IsValid() 检查,即使它们是空的,因此显然无效?

嗯,我必须承认我没有得到令人满意的答案......所以我让这个更胜任​​的一个。它们在 ModelState.IsValid 中被视为错误,因为当 ClientSide Validation 通过时,您然后转到 ModelBinding(模型绑定查看您的 POST 值,查看相应 HttpPost 方法的参数(ActionResult Create为您),并尝试绑定带有这些参数的 POST 值。在您的情况下,绑定看到一个Activity activity参数。并且他在您的 POSTed 字段中没有得到任何东西StageId(例如)。由于 StageId 不可为空,他将其作为 ModelState 字典中的错误=> ModelState 不再有效。

但我不知道为什么它没有被客户端验证捕获,即使有一个Required属性。

您如何使所有内容都在一个步骤中得到验证(对于空字段),因此用户不必提交两次表单并且所有验证消息都会立即显示?您是否必须关闭客户端验证?

好吧,您必须关闭客户端验证,因为您不能只信任客户端验证。但是,如上所述,客户端验证可以避免到服务器的无用往返。

最后,我看到有人使用 [MetadataType(typeof(...)]] 进行验证。如果您有视图模型,为什么要这样做,或者只有在没有视图模型时才这样做?

只有当你没有 ViewModel,而是在你的 Model 类上工作。它仅在使用 Model First 或 Database First 时才有用,因为每次 edmx 更改时都会生成实体类(使用 T4)。然后,如果您在类上放置自定义数据注释,则必须在每个类(文件)生成后手动将其放回去,这将是愚蠢的。所以这[MetadataType(typeof()]]是一种在类上添加注释的方法,即使“基类文件”被重新生成。

希望这个对你有帮助。

顺便说一句,如果您对验证感兴趣,请查看FluentValidation。这是一个非常好的...流畅的验证(你猜到了吗?)库。

于 2012-10-25T19:20:35.620 回答
0
  1. 如果该字段不可为空,EF 认为它是必需的。
  2. 因为外键不可为空,所以导航属性也是必需的。
  3. 要一次获得所有验证,您需要使用在验证后在控制器中以实体模式转换的 ViewModel。有关 mvc 中属性验证的更多信息,您可以在此处阅读。
于 2012-10-25T18:55:23.850 回答