1

MVC驗證基礎(與實體框架)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爲false,控制器然後返回到視圖頁面。

屏幕,然後用這些驗證消息重新顯示:需要

的級ID字段。

DepartmentId字段是必需的。

StatusId字段是必需的。

OwnerId字段是必需的。

即所有的外鍵的數據庫表字段(這些也都選擇框)。

如果我填寫這些表單,然後提交成功,並保存到數據庫。

問題:

  1. 在哪裏驗證從哪裏來的,因爲我沒有使用任何[必需]屬性?這是與實體框架有關嗎?

  2. 爲什麼表單不能立即驗證客戶端的所有內容,即使它們是空的,因此顯然是無效的,它們僅由IsValid()檢查的外鍵(或選擇框)有什麼不同?

  3. 你如何讓所有事情在一個步驟中得到驗證(對於空字段),所以用戶不必一次提交表單兩次,並且所有驗證消息一次顯示?你是否必須關閉客戶端驗證?

(我嘗試添加[必需]屬性外鍵的字段,但是這似乎沒有什麼差別(大概只會影響的IsValid)。我也打過電話Html.EnableClientValidation(),但也沒有任何區別)。

4.最後,我看到有人使用[MetadataType [MetadataType(typeof(...)]]進行驗證。如果您有視圖模型,或者只有當您不使用視圖模型時, t?

顯然我在這裏錯過了一些基礎知識,所以除此之外,如果有人知道關於MVC驗證過程如何工作的詳細教程,包括javascript/controller調用的逐步步驟,而不僅僅是另一篇文章屬性的話,我可以做一個鏈接,太:C)


更多信息的空軍終於男人:

解設置如下:

.NET4

MVC3

EF5

EF5.x Db的上下文產生

「添加代碼生成物品」 上EDMX設計表面以用於關聯EF.x Db上下文生成器文件(.tt文件)

控制器看起來像th是:

// 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

順便說一句,對不起,因爲這裏的所有地方,但有點失去了這... – jimasp

+0

顯然還有其他事情在這裏。你所展示的不應該給出這些結果。你有沒有添加任何其他包到你的項目?您是否使用任何自定義模型活頁夾或價值活頁夾?你的行爲方法是什麼樣子的?獲取和發佈。 –

+0

添加更多信息 – jimasp

回答

1

您之所以需要驗證是因爲屬性是值類型(即它們不能爲空)。由於它們不能爲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。

一個更好的方法是在你的控制器中沒有執行任何操作,而是在業務層中執行,但是可以等到你繼續前進。

+0

謝謝。標記爲答案。在提到你關於數據庫上下文的問題時,這不是全局的,我已經明確地調用了Dispose,但是感謝提及它:c) – jimasp

+0

在MVC3的下拉列表中似乎有一個相關的討論,在MVC4中有一個錯誤修正: http://aspnet.codeplex.com/workitem/7629 – jimasp

+0

@jimasp - 是的,它是相關的。然而,問題在於人們會混淆幾個不同的問題並將它們混合在一起。簡單的經驗法則..永遠不要使用DropDownList(),總是使用DropDownListFor(),並且絕對不要將你的列表命名爲你選擇的ID。 –

0
  1. 如果該字段不爲空,EF認爲這是必需的。
  2. 原因外鍵不可爲空,因此導航屬性也是必需的。
  3. 要一次獲得所有驗證,您需要使用驗證後在控制器中以實體模式轉換的ViewModel。 更多關於MVC中的屬性驗證,你可以閱讀here.
+0

#1和2是正確的,但#3是錯誤的。問題在於他做DropDownLists的方式。 –

1

在哪裏驗證從哪裏來的,因爲我沒有使用任何[必需]屬性?這是與實體框架有關嗎?

有一個在MVC(未EF)默認驗證提供商,檢查兩件事情:

  • 所提供的值的類型(在一個int屬性的字符串)=>(不知道,但類似)yyy is not valid for field xxx

  • 一個「檢查空」值類型的屬性,如果你讓對應的int財產的空場,並會接受一個空場爲int?特性(它會抱怨)。 =>The xxx field is required

這個第二行爲可以在全局中去激活。ASAX(屬性名稱是相當清楚的):

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; 

隨着客戶端驗證啓用,這些驗證,和一個與DataAnnotations(RequiredStringLength ...)將提高對客戶端的驗證錯誤,纔去給控制器。它避免了在服務器上往返,所以它不是無用的。但是,當然,你不能只依賴客戶端驗證。

爲什麼形式沒有驗證一切的時候了客戶端,有何不同之處外鍵(或選擇框),他們只 通過的IsValid(檢查),即使它們是空的,因此明確 無效?

嗯,我必須承認我沒有得到一個令人滿意的答案...所以我讓這個更有能力的一個。它們在ModelState.IsValid中被認爲是錯誤的,因爲當ClientSide Validation通過時,你會去ModelBinding(模型綁定查看你的POST值,查看相應HttpPost方法的參數(爲你提供ActionResult Create),然後嘗試在你的情況下,綁定看到一個Activity activity參數,並且他在你的POSTed字段中沒有得到StageId的任何東西,因爲StageId不是可以爲空的,他把它作爲一個錯誤該ModelState中詞典=> ModelState中不再有效。

但我不知道爲什麼它不是由客戶端驗證逮住,即使有Required屬性。

您如何使所有內容在一個步驟中得到驗證(對於空的 字段),因此用戶不必提交表單兩次,並且所有 驗證消息一次顯示?你必須關閉客戶端 方驗證?

那麼,你必須關閉客戶端驗證,因爲你不能相信客戶端驗證只。但是,如上所述,客戶端驗證可以避免無用的往返服務器。

最後,我見過的人使用 [MetadataType(typeof運算(...)]進行確認。爲什麼你會 做,如果你有一個視圖模型,或者是它唯一的,如果你不?

只有當您沒有獲得ViewModel,但是可以在您的Model類上工作時,只有在使用Model First或Database First時纔有用,因爲每次都會生成實體類(使用T4)然後,如果你把自定義的數據註釋放在你的類上,你將不得不在每個類(文件)生成之後手動將它放回去,這很愚蠢,所以[MetadataType(typeof()]]是一種添加註釋的方法即使重新生成「基類文件」,也是如此。

希望這會有所幫助。

順便說一句,如果您有興趣進行驗證,請參閱FluentValidation。 這是一個非常不錯的...流利的驗證(你會猜到?)庫。

+0

查看我的答案,爲什麼發生奇怪的驗證行爲,FYI。 –

+0

謝謝你,這非常有幫助。 – jimasp