模型的部分验证

时间:2014-04-08 21:20:34

标签: asp.net-mvc validation

我有一个包含20个字段的模型。

我的视图分为4个面板,每个面板有5个字段;用户可以一次单独保存数据。

我创建了4个视图,每个面板一个。这些视图都使用相同的模型,因为这些数据都在同一个数据库表中,所以我有一个单独的CRUD API。

我的问题是,只有在特定面板中显示所需字段时,如何打开/关闭数据注释?

如果我在20个字段模型属性中声明它们全部,它们将使模型无效,即使它们没有显示在屏幕上......

4 个答案:

答案 0 :(得分:1)

真的没有简单的方法来完成这项工作。我唯一的建议是一个自定义模型验证器,它考虑了每个局部视图中的前一个字段。例如:

class LabRatModel
{
    public int a { get; set; }
    public int b { get; set; } // Say the partial splits right here
    public int c { get; set; }
    public int d { get; set; }
}

在自定义验证程序中,尝试:

public override bool IsValid(object model)
{
    var labrat = model as LabRatModel;
    return labrat.b > 0 && labrat.c > 0;
}

重点是检查上一个字段并从那里开始。

答案 1 :(得分:1)

最好将数据注释移动到视图模型。您可以为每个视图创建不同的模型,以便仅触发受该视图影响的注释。

话虽如此,有时很难重新设计您的数据模型。在这种情况下,您可以使用自定义的requiredif注释添加触发部分数据验证的属性。有关详细信息,请参阅RequiredIf Conditional Validation Attribute

我自己在第一个MVC项目中遇到了这个问题。以下是我解决问题的方法。请注意,我将它们放在Model \ DataValidations.cs文件中以保持生成的类清洁。

[MetadataType(typeof(Location_Validation))]
public partial class Location
{
    public bool DisableValidation { get; set; }
}

public class Location_Validation : HomeIndex_Validation
{
    [RequiredIf(DependentProperty = "DisableValidation", TargetValue = false, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldRequired")]
    [MinLength(2, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldTooShort")]
    [MaxLength(50, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldTooLong")]
    [Display(ResourceType = typeof(Language), Name = "City")]
    public string City { get; set; }

    [RequiredIf(DependentProperty = "DisableValidation", TargetValue = false, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldRequired")]
    [MinLength(2, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldTooShort")]
    [MaxLength(2, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldTooLong")]
    [ReadOnly]
    [Display(ResourceType = typeof(Language), Name = "State")]
    public string State { get; set; }

    [RequiredIf(DependentProperty = "DisableValidation", TargetValue = false, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldRequired")]
    [MinLength(2, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldTooShort")]
    [MaxLength(50, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldTooLong")]
    [Display(ResourceType = typeof(Language), Name = "County")]
    public string County { get; set; }

    [Required(ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldRequired")]
    [MaxLength(5, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldInvalid")]
    [MinLength(5, ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldInvalid")]
    [RegularExpression(@"^\d+$", ErrorMessageResourceType = typeof(Language), ErrorMessageResourceName = "FieldInvalid")]
    [Display(ResourceType = typeof(Language), Name = "ZipCode")]
    public string ZipCode { get; set; }

}

不是最好的解决方案,但它确实有效,并且随着用户通过网站的进展,数据模型得到了充分验证。当然,如果用户输入值,则会强制执行格式化验证。

答案 2 :(得分:1)

正如B2K Answer指出的那样,使用ViewModel具有优势。我不喜欢一遍又一遍地来回映射字段,所以我喜欢使用MetaData和Validation类来封装这个概念。

public TheMainClass
{
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
    public string Prop3 { get; set; }
    public string Prop4 { get; set; }
} 

如果我只显示并验证每个视图的每个道具,那么我会创建4个专门用于验证的模型:

[Metadata(typeof(Main1Model.IView1Validation))]
public Main1Model : TheMainClass
{
  internal interface IView1Validation
  {
    [Required]
    string Prop1 { get; set; }

    [ScaffoldColumn(false)]
    string Prop2 { get; set; }

    //etc
  }
}

ViewModel2:

[Metadata(typeof(IView2Validation))]
public Main2Model : TheMainClass
{
  internal interface IView1Validation
  {
    [HiddenInput(DisplayValue = false)]
    [Required]
    string Prop1 { get; set; }

    [Required]
    string Prop2 { get; set; }

    //etc
  }
}

等等。如果有人决定使用浏览器工具来捏造隐藏字段,那么视图会继续进行,您仍然可以进行验证。

一个ViewModel将封装所有视图模型:(我总是按照B2K推荐的那样做)

public MainViewModel
{
  public MainModel { get; set; }
}

等。

然后,动作看起来像:

[HttpPost]
public ActionResult View1(MainViewModelmodel)
{
  if (ModelState.IsValid)
  {
    TempData["View1"] = model;
    return this.RedirectToAction("View2");
  }

  return this.View(model);
}

public ActionResult View2()
{
  model = TempData["View"] as Main1ViewModel;
  if (model == null)
  {
    return this.RedirectToAction("View1");
  }

  return this.View(model)
}

[HttpPost]
public ActionResult View2(MainViewModel model)
{
  // and so on (like HttpPost View1
}

然后你可能会问......但是等一下这是怎么回事? ViewModel正在使用基类型,这就是MVC的神奇之处。您会注意到View2.cshtml强类型为MainViewModel,但是视图使用传入的具体类型,而不是传入的已定义类型。

所有视图看起来都像(甚至可能是相同的视图):

@model MainViewModel

  @EditFor(m => m.MainModel.Prop1)
  @EditFor(m => m.MainModel.Prop2)
  @EditFor(m => m.MainModel.Prop3)
  @EditFor(m => m.MainModel.Prop4)

如果使用scaffold fals e传入Model1View,则editfor()不会创建任何html元素。

随着用户的进展并且您已指定[HiddenInput],editfor()会创建隐藏字段。

真棒酱的最后一点是双重的;首先,您不必将模型保留在内存中(因为每个模型都完全传递到视图中),其次后退按钮起作用,因为模型存储在视图输入中(包括隐藏字段)

答案 3 :(得分:0)

与' DRY'保持一致原则,当您的域实体可以整齐地包含所需的所有验证元数据注释时,我认为将所有实体映射到视图模型是有害的。在为这个部分验证问题寻找一个更令人满意的解决方案时,我保持一个步骤"在viewmodel中计算并将我的域实体添加为该视图中的子组件。我发现通过在if(Model.IsValid){之前检查此步骤计数{并删除有问题的条目,我可以在域实体上保留注释。例如

public MainViewModel
{
  public ModelType MainModel { get; set; }//Domain entity containing my validation metadata annotations
  public int StepCount { get; set; }
}

然后我的行动

public ActionResult SubmitProcess(MainViewModel model)
{
  int NextStep = model.StepCount+1;
  if (NextStep <= 4)//I am not ready to perform the below validation at this time.
    ModelState.Remove("MainModel.Prop1");//So remove the key!

  if (!ModelState.IsValid)//Now validate and allow next step only if valid
    NextStep = model.StepCount;//Failed so retract movement to NextStep.

  if (NextStep != model.StepCount)
  {
    ModelState.Remove("StepCount");//Another 'gotcha' - model needs a 'nudge' to refresh hidden fields!
    model.StepCount = NextStep;
  }

  return View(model)//model.StepCount now incremented only if partial validation passed - so do an action based on that.
}