2013-01-11 129 views
14

我有一個很大的模型(大的意思是模型類包含很多字段/屬性,並且每個都至少有一個驗證屬性(例如RequiredMaxLength,MinLength等))。而不是創建一個視圖與大量的輸入爲用戶填充模型與數據我想創建幾個視圖,其中用戶將填充模型字段的一部分,並進入下一步(某種「嚮導」)。在步驟之間重定向時,我將未填滿的模型對象存儲在Session中。類似下面:如何驗證ASP .NET MVC中只有部分模型?

型號:

public class ModelClass 
{ 
    [MaxLength(100)] ... 
    public string Prop1{get;set;} 
    [MaxLength(100)] ... 
    public string Prop2{get;set;} 
    ... 
    [Required][MaxLength(100)] ... 
    public string Prop20{get;set;} 
} 

控制器:爲Prop3Prop4

[HttpPost] 
public ActionResult Step1(ModelClass postedModel) 
{  
    // user posts only for example Prop1 and Prop2 
    // so while submit I have completly emty model object 
    // but with filled Prop1 and Prop2 
    // I pass those two values to Session["model"] 
    var originalModel = Session["model"] as ModelClass ?? new ModelClass(); 
    originalModel.Prop1 = postedModel.Prop1; 
    originalModel.Prop2 = postedModel.Prop2; 
    Session["model"] = originalModel; 

    // and return next step view 
    return View("Step2"); 
} 

[HttpPost] 
public ActionResult Step2(ModelClass postedModel) 
{ 
    // Analogically the same 
    // I have posted only Prop3 and Prop4 

    var originalModel = Session["model"] as ModelClass; 
    if (originalModel!=null) 
    { 
     originalModel.Prop3 = postedModel.Prop3; 
     originalModel.Prop4 = postedModel.Prop4; 
     Session["model"] = originalModel; 

     // return next step view 
     return View("Step3"); 
    } 
    return View("SomeErrorViewIfSessionBrokesSomeHow") 
} 

Step1視圖僅具有用於Prop1Prop2輸入,第二步視圖包含輸入但這是件事

當用戶處於打開狀態時(例如,步驟1),並使用超過100個字符的值填充Prop1時,客戶端驗證的長度可以正常工作。但是,當然,我必須驗證此值,並在控制器的服務器端進行驗證。如果我有充分的模式,我只希望做到以下幾點:

if(!ModelState.IsValid) return View("the same view with the same model object"); 

所以用戶必須再次,正確填寫表格。 但是在步驟1上用戶只填充了2個屬性20,我需要驗證它們。我無法使用ModelState.IsValid,因爲模型狀態將無效。如您所見Prop20標有[Required]屬性,當用戶提交Prop1Prop2時,Prop20爲空,這就是爲什麼ModelState無效。當然,我可以允許用戶進入第2步,填寫所有步驟並僅在最後一步驗證模型狀態,但我不想讓用戶在步驟1填寫不正確的情況下轉到第2步。我想在控制器中進行驗證。 所以問題是: 我怎樣才能驗證模型的一部分?我如何驗證只有一些模型屬性符合其驗證屬性?

+8

您可以爲每個步驟創建不同的視圖模型,這可能是一個單獨的視圖。像ProductStep1ViewModel和ProductStep1View,但我會將它們命名爲比這更好。 –

+0

@NickBray,ModelClass是從原始模型映射的視圖模型類,所以如果我將ModelClass創建爲其他特定步驟類的組合,我將不得不爲映射器添加大約40個映射規則以映射這些模型類,並且它將是一堆的代碼。我已經考慮過了,但感謝您的建議。 – Dmytro

+0

@DmytroTsiniavsky您可以使用類似[Value Injecter](http://valueinjecter.codeplex.com/)的內容來避免必須明確設置映射規則。 – Mun

回答

14

一個可能的解決方案:

  1. 使用的ModelState。IsValidField(string key);

    if (ModelState.IsValidField("Name") && ModelState.IsValidField("Address")) 
    { ... } 
    

那麼,在年底的時候一切都做運用:

if(ModelState.IsValid) { .. } 
+0

如果你填寫一個非感興趣的字符串,它總是會返回,確保你輸入正確。 –

9

我覺得最優雅的方式是那樣做:

List<string> PropertyNames = new List<string>() 
{ 
    "Prop1", 
    "Prop2" 
}; 

if (PropertyNames.Any(p => !ModelState.IsValidField(p))) 
{ 
    // Error 
} 
else 
{ 
    // Everything is okay 
} 

或:

List<string> PropertyNames = new List<string>() 
{ 
    "Prop1", 
    "Prop2" 
}; 

if (PropertyNames.All(p => ModelState.IsValidField(p))) 
{ 
    // Everything is okay 
} 
else 
{ 
    // Error 
} 
2

只是爲了添加到現有的答案。而不是硬編碼的屬性名稱,我會用一個屬性被添加到伴隨着您的驗證的其餘部分的線沿線的屬性:

public class ValidationStageAttribute : Attribute 
{ 
    public StageNumber { get; private set; } 
    public ValidationStageAttribute(int stageNumber) 
    { 
     StageNumber = stageNumber 
    } 
} 

現在,我們可以得到的屬性名稱不模型本身的局部知識驗證可以被拉入一個方法(如果你使用它很多,你的基礎控制器將是一個很好的選擇)。

protected bool ValidateStage(object model, int stageToValidate) 
{ 
    var propertiesForStage = postedModel.GetType() 
     .GetProperties() 
     .Where(prop => prop.GetCustomAttributes(false).OfType<ValidationStageAttribute>().Where(attr => attr.StageNumber == stageToValidate)); 
     .Select(prop => prop.Name); 

    return propertiesForStage.All(p => ModelStage.IsValidField(p)); 
} 

現在,所有你需要在您的文章動作做會調用ValidateStage(postedModel, 1)

2

在MVC核心,這將是等價的:

if (ModelState.GetFieldValidationState("Name") == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid) 
{ 
    // do something 
} 

不過,我想在這種情況下,建議只需創建一個單獨的視圖模型

您的部分視圖模型可以被您的大視圖模型繼承,因此您不必在代碼中重複自己(DRY主體)。

最好避免硬編碼屬性名稱!