ASP.NET MVC Beta 1: DefaultModelBinder неправильно сохраняет параметр и состояние проверки между несвязанными запросами - PullRequest
6 голосов
/ 26 октября 2008

Когда я использую привязку модели по умолчанию для привязки параметров формы к сложному объекту, который является параметром для действия, каркас запоминает значения, переданные первому запросу, то есть любой последующий запрос к этому действию получает те же данные первый. Значения параметров и состояние проверки сохраняются между несвязанными веб-запросами.

Вот мой код контроллера (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);
    }
}

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

Ответы [ 5 ]

5 голосов
/ 27 октября 2008

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

Я обнаружил проблему, тщательно анализируя разницу между кодом Эйлона и моим, исключая все другие возможности. Как сказано в документации Castle , это "ужасная ошибка"! Пусть это будет предупреждением для других!

Спасибо за ваш ответ, Эйлон, извините, что занял ваше время.

2 голосов
/ 27 октября 2008

Я не уверен, связано это или нет, но ваш звонок <% = Html.TextBox ("newRunTime.Time", ViewData.Model.Time)%> может на самом деле выбрать неправильную перегрузку (поскольку Time является целым числом, он выберет перегрузку object htmlAttributes вместо string value.

Проверка визуализированного HTML позволит вам узнать, происходит ли это. изменение int на ViewData.Model.Time.ToString() приведет к корректной перегрузке.

Похоже, что ваша проблема - это нечто иное, но я заметил это и был сожжен в прошлом.

2 голосов
/ 27 октября 2008

Я пытался воспроизвести эту проблему, но я не вижу того же поведения. Я создал почти точно такой же контроллер и представления, которые у вас есть (с некоторыми допущениями), и каждый раз, когда я создавал новый «RunTime», я помещал его значение в TempData и отправлял его через Redirect. Затем на целевой странице я взял значение, и это всегда было значение, которое я вводил в этом запросе, а не устаревшее значение.

Вот мой контроллер:

открытый класс HomeController: Controller { public ActionResult Index () { ViewData ["Title"] = "Домашняя страница"; string message = "Welcome:" + TempData ["Message"]; if (TempData.ContainsKey ("value")) { int theValue = (int) TempData ["value"]; message + = "" + theValue.ToString (); } ViewData ["Message"] = сообщение; возврат View (); }

[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 содержит некоторые статические значения или что-то в этом роде?

Спасибо

Eilon

0 голосов
/ 03 ноября 2008

Столкнувшись с подобными проблемами при попытке использовать контейнер Windsor IoC в приложении 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 в файле конфигурации, а не код, такой как хороший парень / девушка, которого я знаю.

0 голосов
/ 02 ноября 2008

Себ, я не уверен, что вы подразумеваете под примером. Я ничего не знаю о конфигурации Unity. Я объясню ситуацию с Castle.Windsor и, возможно, это поможет вам правильно настроить Unity.

По умолчанию Castle.Windsor возвращает один и тот же объект каждый раз, когда вы запрашиваете данный тип. Это синглтон образ жизни. В документации Castle.Windsor есть хорошее объяснение различных вариантов образа жизни.

В ASP.NET MVC каждый экземпляр класса контроллера связан с контекстом веб-запроса, который он был создан для обслуживания. Поэтому, если ваш контейнер IoC каждый раз возвращает один и тот же экземпляр вашего класса контроллера, вы всегда будете привязывать контроллер к контексту первого веб-запроса, который использовал этот класс контроллера. В частности, ModelState и другие объекты, используемые DefaultModelBinder, будут использованы повторно, поэтому объект вашей связанной модели и сообщения проверки в ModelState будут устаревшими.

Следовательно, ваш IoC должен возвращать новый экземпляр каждый раз, когда MVC запрашивает экземпляр вашего класса контроллера.

В Castle.Windsor это называется переходным образом жизни. Для его настройки у вас есть два варианта:

  1. Конфигурация XML: вы добавляете lifestlye = "transient" к каждому элементу в файле конфигурации, который представляет контроллер.
  2. Конфигурация в коде: вы можете указать контейнеру использовать переходный образ жизни во время регистрации контроллера. Это то, что помощник MvcContrib, о котором упоминал Бен, делает автоматически для вас - взгляните на метод RegisterControllers в исходном коде MvcContrib .

Я бы предположил, что Unity предлагает похожую концепцию образа жизни в Castle.Windsor, поэтому вам нужно настроить Unity, чтобы использовать его эквивалент переходного образа жизни для ваших контроллеров. MvcContrib, кажется, имеет некоторую поддержку Unity - возможно, вы можете посмотреть там.

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...