Так же, как и другие, я ожидал, что ModelState будет использоваться для заполнения Модели, и поскольку мы явно используем Модель в выражениях в представлении, она должна использовать Model, а не ModelState.
Это выбор дизайна, и я понимаю, почему: если проверки не пройдены, входное значение может быть не пригодно для анализа в типе данных в модели, и вы все равно захотите отобразить любое неправильное значение, введенное пользователем, поэтому его легко исправить.
Единственное, что я не понимаю, это: , почему не по замыслу используется Модель, которая явно указана разработчиком, и если произошла ошибка проверки, используется ModelState.
Я видел много людей, использующих обходные пути, такие как
- ModelState.Clear (): очищает все значения ModelState, но в основном отключает использование проверки по умолчанию в MVC
- ModelState.Remove ("SomeKey"): то же самое, что ModelState.Clear (), но требует микроуправления клавишами ModelState, что является слишком большой работой и не работает с функцией автоматической привязки из MVC. Кажется, 20 лет назад, когда мы также управляли ключами Form и QueryString.
- Рендеринг самих HTML: слишком много работы, деталей и отбрасывает методы HTML Helper с дополнительными функциями.
Пример: заменить @ Html.HiddenFor на m.Name) "id =" @ Html.IdFor (m => m.Name) "value =" @ Html.AttributeEncode (Model.Name) ">. Или заменить @Html. DropDownListFor by ...
- Создание пользовательских помощников HTML для замены стандартных помощников HTML MVC во избежание проблем с дизайном. Это более общий подход, чем рендеринг вашего HTML, но все же требуется больше знаний HTML + MVC или декомпиляция System.Web.MVC, чтобы сохранить все остальные функции, но отключить приоритет ModelState над Model.
- Применение шаблона POST-REDIRECT-GET: это легко в некоторых средах, но сложнее в тех, где больше взаимодействия / сложности. У этого шаблона есть свои плюсы и минусы, и вам не следует применять этот шаблон из-за индивидуального выбора ModelState вместо Model.
Выпуск
Таким образом, проблема в том, что Модель заполняется из ModelState и в представлении, которое мы явно настроили для использования Модели. Все ожидают, что будет использовано значение модели (если оно изменилось), если только нет ошибки проверки; тогда можно использовать ModelState.
В настоящее время в расширениях MVC Helper значение ModelState имеет приоритет над значением Model.
Решение
Таким образом, фактическое исправление для этой проблемы должно быть таким: для каждого выражения, получающего значение модели, значение ModelState должно быть удалено, если для этого значения нет ошибки проверки. Если для этого элемента управления вводится ошибка проверки, значение ModelState не следует удалять, и оно будет использоваться как обычно.
Я думаю, что это решает проблему точно, что лучше, чем большинство обходных путей.
Код здесь:
/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model if no validation errors exist.
/// Call this when changing Model values on the server after a postback,
/// to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this HtmlHelper helper,
Expression<Func<TModel, TProperty>> expression)
{
//First get the expected name value. This is equivalent to helper.NameFor(expression)
string name = ExpressionHelper.GetExpressionText(expression);
string fullHtmlFieldName = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
//Now check whether modelstate errors exist for this input control
ModelState modelState;
if (!helper.ViewData.ModelState.TryGetValue(fullHtmlFieldName, out modelState) ||
modelState.Errors.Count == 0)
{
//Only remove ModelState value if no modelstate error exists,
//so the ModelState will not be used over the Model
helper.ViewData.ModelState.Remove(name);
}
}
И затем мы создаем наши собственные расширения HTML Helper, чтобы сделать это перед вызовом расширений MVC:
public static MvcHtmlString TextBoxForModel<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
string format = "",
Dictionary<string, object> htmlAttributes = null)
{
RemoveStateFor(htmlHelper, expression);
return htmlHelper.TextBoxFor(expression, format, htmlAttributes);
}
public static IHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
RemoveStateFor(htmlHelper, expression);
return htmlHelper.HiddenFor(expression);
}
Это решение устраняет проблему, но не требует, чтобы вы декомпилировали, анализировали и перестраивали то, что MVC обычно предлагает вам (не забывайте также управлять изменениями во времени, различиями браузера и т. Д.).
Я думаю, что логика "Значение модели, если только ошибка проверки, то ModelState" должна была быть задуманна. Если бы это было так, это не укусило бы так много людей, но все равно охватывало бы то, что MVC должен был сделать.