Есть ли какой-либо способ принудительно выполнить повторную проверку модели в контроллере MVC3?

#asp.net-mvc-3 #validation #controller

#asp.net-mvc-3 #проверка #контроллер

Вопрос:

У меня есть модель, которая содержит адрес и пользователя дважды, один раз для контакта «main» и один раз для контакта «invoice», и логическое значение с именем InvoiceContactSameAsMain — неуклюжее имя, но описательное. Средство получения свойства проверяет, совпадают ли объекты Address и Contact для «main» и «invoice», и возвращает true, если это так. Установщик проверяет, является ли значение true, и если да, копирует основного пользователя поверх пользователя счета-фактуры и основной адрес поверх адреса счета-фактуры.

На мой взгляд, логическое значение представлено флажком (как и следовало ожидать). К этому прилагается небольшая функция JS, которая, если установлен флажок, скрывает поля счета и «отключает» проверку на стороне клиента, устанавливая HTML-атрибуту data-val значение false и принудительно повторно анализируя ненавязчивые атрибуты проверки в форме. Снятие флажка естественным образом отображает поля и снова включает проверку.

Все это работает нормально, пока я не доберусь до своего контроллера.

Несмотря на то, что модель является «допустимой» и содержит правильные поля (благодаря моему установщику InvoiceContactSameAsMain), ModelState.Значение isValid остается решительно ложным, и, похоже, я не могу найти никакого способа повторной проверки модели. Если я очищу ModelState, все ошибки исчезнут. Я бы предпочел не копаться в полях ModelState по имени, поскольку объекты Person и Address используются на протяжении всего проекта и могут нуждаться в изменении или расширении в какой-то момент.

Есть ли что-то очевидное, что я здесь пропустил, что позволит мне повторно проверить ModelState? Я пробовал TryUpdateModel и TryValidateModel, но они оба, похоже, используют кэшированные значения ModelState. Я даже попытался рекурсивно вызвать свое действие еще раз, передав «фиксированную» модель. Я почти благодарен, что это не сработало.

Пожалуйста, дайте мне знать, если вам помогут какие-либо дополнительные подробности или примеры.

Редактировать: Очевидно, что если это совершенно неправильный подход к проблеме, просто дайте мне знать.

Правка 2: Добавлены образцы кода в соответствии с предложением Рона Сийма.

Модель выглядит следующим образом: общедоступные сведения о классе { public int? userId { получить; установить; }

     public Company Company { get; set; }

    public Address CompanyAddress { get; set; }
    public Person MainPerson { get; set; }

    public Address InvoiceAddress { get; set; }
    public Person InvoiceContact { get; set; }

    [Display(Name = "Promotional code")]
    [StringLength(20, ErrorMessage = "Promotional code should not exceed 20 characters")]
    public string PromotionalCode { get; set; }

    [Display(Name = "Invoice contact same as main")]
    public bool InvoiceContactSameasMain
    {
        get { return InvoiceContact.Equals(MainPerson); }
        set
        {
            if (value)
            {
                InvoiceContact = MainPerson.Copy();
                InvoiceAddress = CompanyAddress.Copy();
            }
        }
    }

    [_Common.MustAccept]
    [Display(Name = "I agree with the Privacy Policy")]
    public bool PrivacyFlag { get; set; }

    [Display(Name = "Please subscribe to Sodexo News Letter")]
    public bool MarketingOption { get; set; }

    [Display(Name = "Contract number")]
    public int? ContractNumber { get; set; }

    public Details()
    {
        Company = new Company();
        CompanyAddress = new Address();
        MainPerson = new Person();
        InvoiceAddress = new Address();
        InvoiceContact = new Person();
    }
}
  

Это обернуто в ViewModel, поскольку на странице задействовано несколько списков выбора:

 public class DetailsViewModel
{
    public Details    Details              { get; set; }
    public SelectList MainContactTitles    { get; set; }
    public SelectList InvoiceContactTitles { get; set; }
    public SelectList SICCodes             { get; set; }
    public SelectList TypesOfBusiness      { get; set; }
    public SelectList NumbersOfEmployees    { get; set; }

    public DetailsViewModel()
    {
    }
}
  

Два соответствующих действия контроллера заключаются в следующем:

 public class DetailsController : _ClientController
{
    [Authorize]
    public ActionResult Index()
    {
        DetailsViewModel viewModel = new DetailsViewModel();
        if (Client == null)
        {
            viewModel.Details = DetailsFunctions.GetClient((int)UserId, null);
        }
        else
        {
             viewModel.Details = DetailsFunctions.GetClient((int)UserId, Client.ContractNumber);
        }
        viewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.MainPerson.title);
        viewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.InvoiceContact.title);
        viewModel.SICCodes = DetailsFunctions.GetSICCodes(viewModel.Details.Company.sic_code);
        viewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(viewModel.Details.Company.number_of_employees);
        viewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(viewModel.Details.Company.public_private);
        return View(viewModel);
    }

    [Authorize]
    [HttpPost]
    public ActionResult Index(DetailsViewModel ViewModel)
    {
        if (ModelState.IsValid)
        {
            //go to main page for now
            DetailsFunctions.SetClient((int)UserId, ViewModel.Details);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            ViewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.MainPerson.title);
            ViewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.InvoiceContact.title);
            ViewModel.SICCodes = DetailsFunctions.GetSICCodes(ViewModel.Details.Company.sic_code);
            ViewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(ViewModel.Details.Company.number_of_employees);
            ViewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(ViewModel.Details.Company.public_private);
            return View(ViewModel);
        }
    }
}
  

При необходимости я могу предоставить view и JS, но поскольку привязка модели работает просто отлично, я не уверен, насколько это поможет.

Комментарии:

1. Я думаю, что пример кода, который у вас есть, помог бы

2. Вы можете понять, почему ModelState.IsValid значение false при отладке. Если вы развернете некоторые узлы, вы увидите, какие правила завершились ошибкой.

3. @Darcy: Правила, которые не выполняются, указаны в скопированном адресе счета и персонаже. Средство проверки по-прежнему считает их пустыми, хотя я вижу, что они правильно заполнены в модели.

4. Я только что попытался перевернуть свою логику «Контакт счета-фактуры совпадает с основным» с ног на голову: теперь это простое логическое значение, и средства получения для контакта счета-фактуры и адреса счета-фактуры проверяют его и возвращают основной контакт (или адрес), если логическое значение равно true. Это никак не повлияло на проблему, и ModelState по-прежнему считает эти поля пустыми, хотя Модель довольно четко показывает их заполненными.

Ответ №1:

Это довольно дерьмовый взлом, но в итоге я просто очистил ошибки ModelState для соответствующих полей в контроллере перед проверкой ModelState.isValid:

 if(ViewModel.Details.InvoiceContactSameasMain)
        {
            //iterate all ModelState values, grabbing the keys we want to clear errors from
            foreach (string Key in ModelState.Keys)
            {
                if (Key.StartsWith("Details.InvoiceContact") || Key.Startwith("Details.InvoiceAddress"))
                {
                    ModelState[Key].Errors.Clear();
                }
            }
        }
  

Единственный плюс в том, что если объекты Person или Address изменятся, этот код не нужно будет изменять.

Комментарии:

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