Подтвердить что ты не робот

Лучший подход для комплексной проверки модели/подмодели (MVC)

Проблема

Я знаю, что существует много способов проверки модели в MVC, и есть довольно много документации по этой теме. Однако я не совсем уверен, что лучший подход для проверки свойств Model, которые являются "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.

Проблема здесь, где было бы лучшее место, или что было бы лучшим и самым элегантным способом иметь различные ограничения проверки для Prop1 и Prop2, все еще пользуясь преимуществами TryValidateModel() и не заполняя метод Edit условными операторами и ModelState.AddModelError()

Обычно вы можете иметь атрибуты проверки в классе SomeSubModel, но в этом случае это не сработает, потому что для каждого свойства существуют разные ограничения.

Другой вариант заключается в том, что в классе MainModel может быть атрибут пользовательской проверки, но он также не будет работать в этом случае, потому что объект SomeSubModel передается непосредственно в представление, и когда проверка не имеет ссылки на его MainModel объект.

Единственная левая опция, о которой я могу думать, - это ValidationModel для каждого свойства, но я не совсем понимаю, какой был бы лучший подход для этого.

Решение

Здесь решение я реализовано на основе ответа @MrMindor.

Base ValidationModel class:

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);
}
4b9b3361

Ответ 1

Мы имеем аналогичную ситуацию в одном из наших приложений, где каждый SomeSubModel представляет параметр для задания. Поскольку каждый тип задания имеет различное количество и типы параметров, наша модель задания имеет набор этих параметров, а не только заданные свойства.

У нас есть JobParameter, который подклассифицирован в различные доступные типы (StringParameter, BoolParameter, DoubleParameter,...). Эти подклассы имеют свои собственные атрибуты проверки.
Для передачи параметров в представление используется общий "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 всегда преобразуется обратно в объект своего домена для проверки.
ПараметрМодель:

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);
    }                                
}                                

Для вашей конкретной ситуации вам не нужно усложнять эту сложность (или доверять тому, что специфика нашей системы проверки будет работать для вас).
Редактировать/Сохранить действия для каждого Prop:
Создайте модель проверки для каждой опоры. Prop1ValidationModel, Prop2ValidationModel

[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.

Ответ 2

Если SomeSubModel имеет разные атрибуты проверки, в зависимости от того, применяется ли он в Prop1 или Prop2... означает, что на самом деле два SomeSubModel of prop1 end prop2 - это два разных класса, потому что также, если они имеют одинаковые поля, значение этих полей зависит от того, связаны ли они с prop1 или prop2 (поэтому у них разные атрибуты проверки. Соответственно, лучший подход - это определение двух подклассов SomeSubClass, например SomeSubClass1 и SomeSubClass2, которые наследуются от Common SomeSubClass. После унаследования вы не должны добавлять новые свойства, но только новые правила валидации либо с помощью свободной проверки, либо с помощью MetaDataTypeAttribute, чтобы указать атрибуты проверки из определения класса. есть что-то вроде:

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

и

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