复杂模型/子模型验证(MVC)的最佳方法

时间:2012-10-17 16:07:57

标签: c# asp.net-mvc model-view-controller model-validation validationattribute

问题

我知道在MVC中有很多方法可以进行模型验证,并且有很多关于这个主题的文档。但是,我不太确定验证模型属性的最佳方法是什么,模型属于相同类型“Sub Model”

请记住以下

  • 我仍然想利用TryUpdateModel/TryValidateModel种方法
  • 这些“子模型”中的每一个都具有强类型视图
  • MainModel类有一个强类型视图,用于呈现整体显示视图

这可能听起来有点令人困惑,但我会抛出一些代码来澄清。以下面的类为例:

MainModel:

class MainModel{
    public SomeSubModel Prop1 { get; set; }
    public SomeSubModel Prop2 { get; set; }
}

SomeSubModel:

class SomeSubModel{
      public string Name { get; set; }
      public string Foo { get; set; }
      public int Number { get; set; }
}

MainModelController:

class MainModelController{

    public ActionResult MainDisplay(){
         var main = db.retrieveMainModel();
         return View(main); 
    }

    [HttpGet]
    public ActionResult EditProp1(){
         //hypothetical retrieve method to get MainModel from somewhere
         var main = db.retrieveMainModel();

         //return "submodel" to the strictly typed edit view for Prop1
         return View(main.Prop1);
    }

    [HttpPost]
    public ActionResult EditProp1(SomeSubModel model){

         if(TryValidateModel(model)){
              //hypothetical retrieve method to get MainModel from somewhere
              var main = db.retrieveMainModel();
              main.Prop1 = model;
              db.Save();

              //when succesfully saved return to main display page 
              return RedirectToAction("MainDisplay");
         }
         return View(main.Prop1);
    }

    //[...] similar thing for Prop2 
    //Prop1 and Prop2 could perhaps share same view as its strongly 
    //typed to the same class
}

我相信这段代码直到现在才有意义(如果不是这样,请纠正我),因为TryValidateModel()正在验证没有ValidationAttribute的模型。

问题出在这里,哪里是最好的地方,或者是Prop1Prop2不同验证约束的最佳和最优雅方式仍然利用TryValidateModel()并且不使用条件语句和ModelState.AddModelError()

填充Edit方法

通常,您可以在SomeSubModel类中使用验证属性,但在这种情况下它不起作用,因为每个属性都有不同的约束。

其他选项是MainModel类中可能存在自定义验证属性,但在这种情况下它也不起作用,因为SomeSubModel对象直接传递给视图并且在验证时有没有引用它的MainModel对象。

我能想到的唯一左侧选项是每个属性的ValidationModel,但我不是最好的方法。

解决方案

这是我实施的解决方案,基于@MrMindor的回答。

Base ValidationModel类:

public class ValidationModel<T> where T : new()
{
    protected ValidationModel() {
        this.Model = new T();
    }
    protected ValidationModel(T obj) { 
        this.Model = obj; 
    }

    public T Model { get; set; }
}

Prop1的验证模型

public class Prop1ValidationModel:ValidationModel<SomeSubModel>
{
    [StringLength(15)]
    public string Name { get{ return base.Model.Name; } set { base.Model.Name = value; } }

    public Prop1ValidationModel(SomeSubModel ssm)
        : base(ssm) { }
}

Prop2的验证模型

public class Prop2ValidationModel:ValidationModel<SomeSubModel>
{
    [StringLength(70)]
    public string Name { get{ return base.Model.Name; } set { base.Model.Name = value; } }

    public Prop2ValidationModel(SomeSubModel ssm)
        : base(ssm) { }
}

动作

[HttpPost]
public ActionResult EditProp1(SomeSubModel model){

     Prop1ValidationModel vModel = new Prop1ValidationModel(model);
     if(TryValidateModel(vModel)){

          //[...] persist data

          //when succesfully saved return to main display page 
          return RedirectToAction("MainDisplay");
     }
     return View(model);
}

2 个答案:

答案 0 :(得分:4)

我们的某个应用程序中存在类似的情况,其中每个SomeSubModel代表作业的参数设置。由于每种类型的作业都有不同数量和类型的参数,我们的作业模型包含这些参数的集合,而不仅仅是设置属性。

我们将JobParameter子类化为可用的不同类型(StringParameterBoolParameterDoubleParameter,...)。这些子类具有各自的验证属性集 共享的“JobParameterModel”用于将参数传递给视图 对于验证,返回的模型将转换为其特定的JobParameter 的 ParameterTypes:

public enum ParameterType
{
    Empty = 0,
    Boolean = 1,
    Integer = 2,
    String = 3,
    DateTime = 4,
    ...
}

<强> JobParameter:

class JobParameter
{ 
  [AValidationAttributeForAllParamters] 
  public string Name { get; set; }  
  public virtual string Foo { get; set; }  
  public int Number { get; set; }
  public ParameterType Type {get;set;}

  private static readonly IDictionary<ParameterType, Func<object>> ParameterTypeDictionary =
  new Dictionary<ParameterType, Func<object>>{
                {ParameterType.Empty, () => new EmptyParameter() },
                {ParameterType.String, ()=>new StringParameter()},
                {ParameterType.Password, ()=>new PasswordParameter()},
                ...
              };
    public static ScriptParameter Factory(ParameterType type)
    {
        return (ScriptParameter)ParameterTypeDictionary[type]();
    }
}  

<强> BoolParameter:

[ABoolClassLevelValidationAttribute]
class BoolParameter:JobParameter
{
    [AValidationAttribute]
    public override string Foo {get;set;}
}

....

在我们的验证框架(我被告知与MS的模型非常接近)中,ViewModel总是被转换回其域对象以进行验证。
ParameterModel:

class ParameterModel: JobParameter
{
    public JobParameter ToDomain()
    {
        var domainObject = JobParameter.Factory(Type);
        Mapper.Map(this, domainObject);
        return domainObject;
    }
    public bool Validate()
    {
        var dom = ToDomain();
        return TryValidate(dom);
    }

}

<强>控制器:

class Controller(){

    [HttpPost]                                
    public ActionResult SaveParameter(JobParameter model){                                

         if(TryValidateModel(model)){                                

              //persist stuff to db.

          //when succesfully saved return to main display page                                 
              return RedirectToAction("MainDisplay");                                
         }                                
         return View(main.Prop1);
    }                                
}                                

为了您的具体情况,您不需要这么复杂(或者相信我们的验证框架的细节对您有用)。
为每个支柱编辑/保存操作:
为每个道具创建验证模型。 Prop1ValidationModelProp2ValidationModel

[HttpGet]
public ActionResult EditProp1()
{
    var main = db.retrieveMainModel();
    db.Prop1.SubmitUrl = Url.Action("SaveProp1","Controller");
    return View(main.Prop1);
}
[HttpPost]                                
public ActionResult SaveProp1(SomeSubModel model){                                
     var validationModel = new Prop1ValidationModel{
     ///copy properties                                   
         };
     if(TryValidateModel(validationModel)){                                

          var main = db.retrieveMainModel();                                
          main.Prop1 = model;                                
          db.Save();                                

          //when succesfully saved return to main display page                                 
          return RedirectToAction("MainDisplay");                                
     }                                
     return View(main.Prop1);
} 

使用此功能,您可以对Prop1和Prop2使用相同的强类型视图。

答案 1 :(得分:2)

如果SomeSubModel具有不同的验证属性,具体取决于它是否在Prop1或Prop2中应用...意味着实际上prop1 end prop2的两个SomeSubModel是两个不同的类,因为如果它们具有相同的字段,则该字段的含义取决于它们是否附加到prop1或prop2(这就是为什么它们具有不同的验证属性。因此最好的方法是定义SomeSubClass的两个子类,比如SomeSubClass1和SomeSubClass2继承自Common SomeSubClass。一旦继承,你不能添加新的属性,但只是通过使用流畅的验证或使用MetaDataTypeAttribute从类定义中指定验证属性的新验证规则。所以你将有类似的东西:

[MetaDataType(typeof(ValidationClass1)]
public class SomeSubClass1: SomeSubclass{}

[MetaDataType(typeof(ValidationClass2)]
public class SomeSubClass2: SomeSubclass{}