Как проверить только часть модели в ASP.NET MVC? - программирование
Подтвердить что ты не робот

Как проверить только часть модели в ASP.NET MVC?

У меня есть большая модель (большой я имею в виду, что класс модели содержит много полей/свойств, и каждый из них имеет хотя бы один атрибут проверки (например, Required, MaxLength, MinLength и т.д.)). Вместо создания одного представления с большим количеством входов для пользователя, чтобы заполнить модель данными, я хочу создать несколько представлений, где пользователь заполнит часть полей модели и перейдет к следующему шагу (какой-то "волшебник" ). При перенаправлении между этапами я сохраняю не заполненный модельный объект в Session. Что-то вроде ниже:

Модель:

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

Контроллер:

[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 вид имеет входы только для Prop1 и Prop2, представление Step2 содержит входы для Prop3 и Prop4 и т.д.

НО ЗДЕСЬ ВЕЩЬ

Когда пользователь включен, например, шаг 1, и заполняет Prop1 значением более 100 символов. Проверка на стороне клиента работает нормально. Но, конечно, я должен проверить это значение и на стороне сервера в контроллере. Если бы у меня была полная модель, я бы просто сделал следующее:

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

чтобы пользователь снова заполнил форму и исправил ее. НО на шаге 1 пользователь заполнил только 2 свойства по 20, и мне нужно их проверить. Я не могу использовать ModelState.IsValid, потому что состояние модели будет недействительным. Как видите, Prop20 отмечен атрибутом [Required], когда пользователь отправляет Prop1 и Prop2, Prop20 имеет значение null и что ModelState является недопустимым. Конечно, я мог бы позволить пользователю перейти к шагу 2, заполнить все этапы и проверить состояние модели только на последнем шаге, но я не хочу разрешать пользователю перейти к шагу 2, если он наполнил шаг 1 некорректным. И я хочу эту проверку в контроллере. Итак, вопрос: Как я могу проверить только часть модели? Как я могу убедиться, что только некоторые из свойств модели соответствуют их атрибутам проверки?

4b9b3361

Ответ 1

Одно из возможных решений:

  • Использовать ModelState.IsValidField(строковый ключ);

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

Затем в конце, когда все будет сделано, используйте:

if(ModelState.IsValid) { .. }

Ответ 2

Я думаю, что самый элегантный способ - сделать это так:

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
}

Ответ 3

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

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

Теперь все, что вам нужно сделать в вашем post action, - вызвать ValidateStage(postedModel, 1)

Ответ 4

В MVC Core это будет эквивалентно:

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

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

Ваша модель частичного просмотра может быть унаследована вашей более крупной моделью просмотра, поэтому вам не придется повторяться в коде (DRY-руководитель).

Лучше избегать жесткого кодирования имен свойств!