Я сталкивался с подобной проблемой, которая решит вашу проблему, если вам не нужно , чтобы использовать FormCollection
.
Я не использовал TryUpdateModel
с того дня, как узнал о функции автоматической привязки. Автоматическое связывание, в двух словах, в значительной степени делает работу, которую TryUpdateModel
сделает для вас, то есть установит объект модели в соответствии со значениями, найденными в FormCollection, а также попытается проверить модель. И делает это автоматически. Все, что вам нужно сделать, это поместить параметр в ActionMethod, и его свойства будут автоматически заполнены значениями, найденными в FormCollection. Ваша подпись в действии станет следующей:
public ActionResult Create(Int64 id, SomeModel data)
Теперь вам вообще не нужно звонить TryUpdateModel
. Вам все еще нужно проверить, является ли ModelState действительным, чтобы решить, следует ли перенаправить или вернуть представление.
[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
Это не вызовет исключения в ваших юнит-тестах, так что это одна из решенных проблем. Однако сейчас есть другая проблема. Если вы запустите приложение, используя приведенный выше код, оно будет работать нормально Ваша модель будет проверена при входе в действие, и будет возвращен правильный ActionResult, перенаправление или представление. Однако при попытке выполнить модульное тестирование обоих путей вы обнаружите, что модель всегда будет возвращать перенаправление, даже если модель недействительна.
Проблема в том, что при модульном тестировании модель вообще не проверяется. А поскольку ModelState по умолчанию действителен, ModelState.IsValid
всегда будет возвращать true в ваших модульных тестах и, таким образом, всегда будет возвращать перенаправление, даже если модель недействительна.
Решение: звоните TryValidateModel
вместо ModelState.IsValid
. Это заставит ваш юнит тест для проверки модели. Одна из проблем этого подхода заключается в том, что модель будет проверена один раз в ваших модульных тестах и дважды в вашем приложении. Это означает, что любые обнаруженные ошибки будут дважды записаны в вашем приложении. Это означает, что если вы используете вспомогательный метод ValidationSummary
в своем представлении, вы увидите несколько дублированных сообщений в списке.
Если это слишком много, вы можете очистить ModelState
перед тем, как позвонить TryValidateModel
. Есть некоторые проблемы с этим, потому что вы потеряете некоторые полезные данные, например, значение попытки, если вы очистите ModelState
, чтобы вы могли вместо этого просто очистить ошибки, записанные в ModelState
. Вы можете сделать это, углубившись в ModelState
и очистив каждую ошибку, сохраненную в каждом элементе, следующим образом:
protected void ClearModelStateErrors()
{
foreach (var modelState in ModelState.Values)
modelState.Errors.Clear();
}
Я поместил код в метод, чтобы его можно было использовать во всех действиях. Я также добавил ключевое слово protected
, чтобы намекнуть, что это может быть полезным методом для размещения в BaseController, из которого все ваши контроллеры имеют производные, чтобы они все имели доступ к этому методу.
Окончательное решение:
[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
ClearModelStateErrors();
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
ПРИМЕЧАНИЕ. Я понимаю, что не пролил свет на проблему с корнями. Это потому, что я не совсем понимаю корень проблемы. Если вы заметили сбой модульного теста, он завершается неудачей, потому что ArgumentNullException
был сгенерирован, потому что ControllerContext
равен нулю, и он передается методу, который выдает исключение, если ControllerContext
равен нулю. (Прокляните команду MVC за их проклятое оборонительное программирование).
Если вы попытаетесь высмеять ControllerContext
, вы все равно получите исключение, на этот раз NullReferenceException
. Как ни странно, трассировка стека для исключения показывает, что оба исключения происходят в одном и том же методе, или, я бы сказал, конструкторе, расположенном в System.Web.Mvc.ChildActionValueProvider
. У меня нет удобной копии исходного кода, поэтому я понятия не имею, что является причиной исключения, и я пока не нашел лучшего решения, чем предложенное выше. Мне лично не нравится мое решение, потому что я меняю способ кодирования своего приложения в пользу своих модульных тестов, но лучшей альтернативы, похоже, нет. Могу поспорить, что реальное решение будет включать насмешку над другим объектом, но я просто не знаю, что.
Кроме того, прежде чем кто-либо получит какие-либо умные идеи, насмешка над ValueProvider - это , а не решение.Это остановит исключения, но ваши юнит-тесты не будут проверять вашу модель, и ваш ModelState
всегда будет сообщать, что модель действительна, даже если это не так.