MVC验证基础(包含实体框架)

时间:2012-10-25 17:11:41

标签: asp.net-mvc asp.net-mvc-3 entity-framework-5 asp.net-mvc-validation

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

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

问题:

  1. 鉴于我没有使用任何[必需]属性,验证来自哪里?这与实体框架有关吗?

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

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

  4. (我尝试将[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>
    }
    

3 个答案:

答案 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(RequiredStringLength ...)相关的验证将在转到Controller之前在客户端引发验证错误。它避免了服务器上的往返,因此它没有用处。但是,当然,您不能仅依赖客户端验证。

  

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

嗯,我必须承认我没有得到令人满意的答案......所以我让这一个更有能力。它们在ModelState.IsValid中被视为错误,因为当ClientSide Validation传递时,您将转到ModelBinding(模型绑定查看您的POSTed值,查看相应HttpPost方法的参数(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)

  1. 如果该字段不可为空,则EF认为该字段是必需的。
  2. 原因是外键不可为空,因此也需要导航属性。
  3. 要立即获得所有验证,您需要使用在验证后在控制器中以实体模式转换的ViewModel。 有关mvc中属性验证的更多信息,请参阅here.