我有一个包含20个字段的模型。
我的视图分为4个面板,每个面板有5个字段;用户可以一次单独保存数据。
我创建了4个视图,每个面板一个。这些视图都使用相同的模型,因为这些数据都在同一个数据库表中,所以我有一个单独的CRUD API。
我的问题是,只有在特定面板中显示所需字段时,如何打开/关闭数据注释?
如果我在20个字段模型属性中声明它们全部,它们将使模型无效,即使它们没有显示在屏幕上......
答案 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.
}