ASP.NET MVC бета 1:DefaultModelBinder ошибочно сохраняет параметр и состояние проверки между несвязанными запросами
-
04-07-2019 - |
Вопрос
Когда я использую привязку модели по умолчанию для привязки параметров формы к сложному объекту, который является параметром действия, платформа запоминает значения, переданные в первый запрос, а это означает, что любой последующий запрос к этому действию получает те же данные, что и первый.Значения параметров и состояние проверки сохраняются между несвязанными веб-запросами.
Вот мой код контроллера (service
представляет доступ к серверной части приложения):
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create()
{
return View(RunTime.Default);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(RunTime newRunTime)
{
if (ModelState.IsValid)
{
service.CreateNewRun(newRunTime);
TempData["Message"] = "New run created";
return RedirectToAction("index");
}
return View(newRunTime);
}
Мое представление .aspx (строго типизированное как ViewPage<RunTime
>) содержит такие директивы, как:
<%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %>
При этом используется DefaultModelBinder
класс, который означало автоматическую привязку свойств моей модели.
Я захожу на страницу, ввожу действительные данные (например.время = 1).Приложение правильно сохраняет новый объект со временем = 1.Затем я нажимаю еще раз, ввожу другие действительные данные (например,время = 2).Однако данные, которые сохраняются, являются оригинальными (например,время = 1).Это также влияет на проверку: если мои исходные данные были недействительны, то все данные, которые я введу в будущем, будут считаться недействительными.Перезапуск IIS или пересборка моего кода сбрасывает сохраненное состояние.
Я могу решить эту проблему, написав собственную жестко закодированную связку модели, простой пример которой показан ниже.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([ModelBinder(typeof (RunTimeBinder))] RunTime newRunTime)
{
if (ModelState.IsValid)
{
service.CreateNewRun(newRunTime);
TempData["Message"] = "New run created";
return RedirectToAction("index");
}
return View(newRunTime);
}
internal class RunTimeBinder : DefaultModelBinder
{
public override ModelBinderResult BindModel(ModelBindingContext bindingContext)
{
// Without this line, failed validation state persists between requests
bindingContext.ModelState.Clear();
double time = 0;
try
{
time = Convert.ToDouble(bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"]);
}
catch (FormatException)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".Time", bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"] + "is not a valid number");
}
var model = new RunTime(time);
return new ModelBinderResult(model);
}
}
Я что-то пропустил?Я не думаю, что это проблема сеанса браузера, поскольку я могу воспроизвести проблему, если первые данные вводятся в одном браузере, а вторые — в другом.
Решение
Оказывается, проблема была в том, что мои контроллеры использовались повторно между вызовами. Одна из деталей, которые я решил опустить в своем первоначальном посте, заключается в том, что я использую контейнер Castle.Windsor для создания своих контроллеров. Мне не удалось пометить мой контроллер временным образом жизни, поэтому я получал один и тот же экземпляр при каждом запросе. Таким образом, контекст, используемый связывателем, использовался повторно и, конечно, он содержал устаревшие данные. Р>
Я обнаружил проблему, тщательно анализируя разницу между кодом Эйлона и моим, исключая все другие возможности. Как сказано в документации по замку , это & Цитата ; ужасная ошибка " ;! Пусть это будет предупреждением для других!
Спасибо за ваш ответ, Эйлон. Извините, что потратил ваше время.
Другие советы
Я пытался воспроизвести эту проблему, но такого поведения не наблюдаю.Я создал почти точно такой же контроллер и представления, которые есть у вас (с некоторыми предположениями), и каждый раз, когда я создавал новый «RunTime», я помещал его значение в TempData и отправлял его через перенаправление.Затем на целевой странице я получал значение, и оно всегда было тем значением, которое я вводил в этом запросе, а не устаревшим значением.
Вот мой контроллер:
общедоступный класс HomeController:Controller {public actionResult index () {viewData ["title"] = "Home Page";строковое сообщение = "Добро пожаловать:" + TempData["Сообщение"];if (tempdata.containskey ("value")) {int thevalue = (int) tempdata ["value"];сообщение += " " + theValue.ToString();} ViewData ["Сообщение"] = сообщение;вернуть просмотр();}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create() {
return View(RunTime.Default);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(RunTime newRunTime) {
if (ModelState.IsValid) {
//service.CreateNewRun(newRunTime);
TempData["Message"] = "New run created";
TempData["value"] = newRunTime.TheValue;
return RedirectToAction("index");
}
return View(newRunTime);
}
}
И вот мое представление (Create.aspx):
<% using (Html.BeginForm()) { %>
<%= Html.TextBox("newRunTime.TheValue", ViewData.Model.TheValue) %>
<input type="submit" value="Save" />
<% } %>
Кроме того, я не был уверен, как выглядит тип «RunTime», поэтому сделал вот этот:
public class RunTime {
public static readonly RunTime Default = new RunTime(-1);
public RunTime() {
}
public RunTime(int theValue) {
TheValue = theValue;
}
public int TheValue {
get;
set;
}
}
Возможно ли, что ваша реализация RunTime включает в себя какие-то статические значения или что-то в этом роде?
Спасибо,
Эйлон
Я не уверен, связано это или нет, но ваш звонок
<% = Html.TextBox (" newRunTime.Time " ;, ViewData.Model.Time)% >
может на самом деле выбрать неправильную перегрузку (так как Time является целым числом, он выберет перегрузку object htmlAttributes
, а не string value
.
Проверка отображенного HTML-кода позволит вам узнать, происходит ли это. изменение int на ViewData.Model.Time.ToString()
приведет к корректной перегрузке.
Похоже, ваша проблема - это нечто иное, но я заметил это и был сожжен в прошлом.
Себ, я не уверен, что ты имеешь в виду под примером. Я ничего не знаю о конфигурации Unity. Я объясню ситуацию с Castle.Windsor и, возможно, это поможет вам правильно настроить Unity. Р>
По умолчанию Castle.Windsor возвращает один и тот же объект каждый раз, когда вы запрашиваете данный тип. Это синглтон образ жизни. Хорошее объяснение различных вариантов образа жизни можно найти в Castle.Windsor документации а>. Р>
В ASP.NET MVC каждый экземпляр класса контроллера связан с контекстом веб-запроса, который он был создан для обслуживания. Поэтому, если ваш контейнер IoC каждый раз возвращает один и тот же экземпляр вашего класса контроллера, вы всегда будете привязывать контроллер к контексту первого веб-запроса, который использовал этот класс контроллера. В частности, Поэтому вам нужен ваш IoC для возврата нового экземпляра каждый раз, когда MVC запрашивает экземпляр вашего класса контроллера. В Castle.Windsor это называется переходным образом жизни. Для его настройки у вас есть два варианта: Я полагаю, что Unity предлагает похожую концепцию образа жизни в Castle.Windsor, поэтому вам нужно настроить Unity для использования его эквивалента переходного образа жизни для ваших контроллеров.
В MvcContrib есть несколько Поддержка Unity - возможно, вы могли бы посмотреть там. Р>
Надеюсь, это поможет. ModelState
и другие объекты, используемые DefaultModelBinder
, будут использованы повторно, поэтому объект вашей связанной модели и сообщения проверки в <=> будут устаревшими.
Столкнувшись с похожими проблемами при попытке использовать контейнер IoC Windsor в приложении ASP.NET MVC, мне пришлось пройти тот же путь обнаружения, чтобы заставить его работать. Вот некоторые детали, которые могут помочь кому-то еще.
Использование этого - начальная настройка в Global.asax:
if (_container == null)
{
_container = new WindsorContainer("config/castle.config");
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container));
}
И с помощью WindsorControllerFactory, который при запросе экземпляра контроллера делает:
return (IController)_container.Resolve(controllerType);
Хотя Виндзор правильно связывал все контроллеры, по какой-то причине параметры не передавались из формы в соответствующее действие контроллера. Вместо этого все они были нулевыми, хотя это вызывало правильное действие.
По умолчанию контейнер передает обратные синглтоны, что явно плохо для контроллеров и является причиной проблемы:
http://www.castleproject.org/monorail/documentation /trunk/integration/windsor.html р>
Однако в документации указано, что образ жизни контроллеров может быть изменен на переходный, хотя на самом деле не говорится, как это сделать, если вы используете файл конфигурации. Оказывается, это достаточно просто:
<component
id="home.controller"
type="DoYourStuff.Controllers.HomeController, DoYourStuff"
lifestyle="transient" />
И без каких-либо изменений кода он теперь должен работать как положено (то есть уникальные контроллеры каждый раз, предоставляемые одним экземпляром контейнера). Затем вы можете выполнить всю конфигурацию IoC в файле конфигурации, а не код, такой как хороший парень / девушка, которого я знаю.