ASP.NET MVC бета 1:DefaultModelBinder ошибочно сохраняет параметр и состояние проверки между несвязанными запросами

StackOverflow https://stackoverflow.com/questions/238460

Вопрос

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

Вот мой код контроллера (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 каждый раз возвращает один и тот же экземпляр вашего класса контроллера, вы всегда будете привязывать контроллер к контексту первого веб-запроса, который использовал этот класс контроллера. В частности, ModelState и другие объекты, используемые DefaultModelBinder, будут использованы повторно, поэтому объект вашей связанной модели и сообщения проверки в <=> будут устаревшими.

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

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

<Ол>
  • Конфигурация XML: вы добавляете lifestlye = " transient " к каждому элементу в вашем файле конфигурации, который представляет контроллер.
  • Конфигурация в коде: вы можете указать контейнеру использовать переходный образ жизни во время регистрации контроллера. Это то, что помощник MvcContrib, о котором упоминал Бен, делает автоматически для вас - взгляните на метод RegisterControllers в Исходный код MvcContrib .
  • Я полагаю, что Unity предлагает похожую концепцию образа жизни в Castle.Windsor, поэтому вам нужно настроить Unity для использования его эквивалента переходного образа жизни для ваших контроллеров. В MvcContrib есть несколько Поддержка Unity - возможно, вы могли бы посмотреть там.

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top