6

当我使用默认模型绑定将表单参数绑定到作为操作参数的复杂对象时,框架会记住传递给第一个请求的值,这意味着对该操作的任何后续请求都会获得与第一个请求相同的数据。参数值和验证状态在不相关的 Web 请求之间保持不变。

这是我的控制器代码(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该类,该类旨在自动绑定我的模型的 properties

我点击页面,输入有效数据(例如时间 = 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);
    }
}

我错过了什么吗?我不认为这是浏览器会话问题,因为如果在一个浏览器中输入第一个数据而在另一个浏览器中输入第二个数据,我可以重现该问题。

4

5 回答 5

5

事实证明,问题在于我的控制器在调用之间被重用。我选择从原始帖子中省略的细节之一是我正在使用 Castle.Windsor 容器来创建我的控制器。我没有用 Transient 生活方式标记我的控制器,所以我在每个请求上都得到了相同的实例。因此,绑定器使用的上下文被重新使用,当然它包含陈旧的数据。

我在仔细分析 Eilon 的代码和我的代码之间的差异时发现了这个问题,排除了所有其他可能性。正如城堡文档所说,这是一个“可怕的错误”!让这成为对其他人的警告!

感谢您的回复 Eilon - 很抱歉占用您的时间。

于 2008-10-27T20:20:24.637 回答
2

我试图重现这个问题,但我没有看到同样的行为。我创建了几乎完全相同的控制器和视图(有一些假设),每次我创建一个新的“运行时”时,我都会将它的值放入 TempData 并通过重定向发送它。然后在目标页面上,我抓取了该值,它始终是我在该请求中输入的值——绝不是过时的值。

这是我的控制器:

公共类 HomeController : Controller { public ActionResult Index() { ViewData["Title"] = "Home Page"; string message = "欢迎:" + TempData["Message"]; 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 实现是否可能包含一些静态值或其他内容?

谢谢,

艾隆

于 2008-10-27T17:53:19.970 回答
2

我不确定这是否相关,但是您对 <%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %> 的调用实际上可能选择了错误的重载(因为 Time 是一个整数,它将选择object htmlAttributes重载,而不是string value.

检查呈现的 HTML 会让您知道这是否正在发生。将 int 更改为ViewData.Model.Time.ToString()将强制正确的过载。

听起来您的问题有所不同,但我注意到了这一点,并且过去曾被烧毁。

于 2008-10-27T17:59:09.163 回答
0

Seb,我不确定你的例子是什么意思。我对 Unity 配置一无所知。我将解释 Castle.Windsor 的情况,也许这会帮助您正确配置 Unity。

默认情况下,每次请求给定类型时,Castle.Windsor 都会返回相同的对象。这就是单身的生活方式。Castle.Windsor 文档中对各种生活方式选项进行了很好的解释。

在 ASP.NET MVC 中,控制器类的每个实例都绑定到创建它以提供服务的 Web 请求的上下文。因此,如果您的 IoC 容器每次都返回您的控制器类的相同实例,您将始终获得一个绑定到使用该控制器类的第一个 Web 请求的上下文的控制器。特别是,theModelState和其他使用的对象DefaultModelBinder将被重用,因此您绑定的模型对象和 中的验证消息ModelState将是陈旧的。

因此,每次 MVC 请求控制器类的实例时,您都需要 IoC 返回一个新实例。

在 Castle.Windsor 中,这被称为短暂的生活方式。要配置它,您有两个选择:

  1. XML 配置:将 lifestlye="transient" 添加到配置文件中代表控制器的每个元素。
  2. 代码内配置:您可以在注册控制器时告诉容器使用瞬态生活方式。这就是 Ben 提到的 MvcContrib 助手会自动为您做的事情 - 看看MvcContrib 源代码中的 RegisterControllers 方法。

我想 Unity 提供了与 Castle.Windsor 中的生活方式类似的概念,因此您需要配置 Unity 以将其等效于短暂的生活方式用于您的控制器。MvcContrib 似乎有一些Unity 支持——也许你可以看看那里。

希望这可以帮助。

于 2008-11-02T12:05:24.097 回答
0

在尝试在 ASP.NET MVC 应用程序中使用 Windsor IoC 容器时遇到了类似的问题,我必须经历同样的发现之旅才能使其正常工作。以下是一些可能对其他人有所帮助的细节。

使用这是 Global.asax 中的初始设置:

  if (_container == null) 
  {
    _container = new WindsorContainer("config/castle.config");
    ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container)); 
  }

并使用 WindsorControllerFactory ,当被要求提供控制器实例时:

  return (IController)_container.Resolve(controllerType);

虽然 Windsor 正确连接了所有控制器,但由于某种原因,参数没有从表单传递到相关的控制器操作。相反,它们都是空的,尽管它正在调用正确的操作。

默认是让容器传回单例,这对控制器来说显然是一件坏事以及问题的原因:

http://www.castleproject.org/monorail/documentation/trunk/integration/windsor.html

但是文档确实指出控制器的生活方式可以更改为瞬态,尽管如果您使用配置文件,它实际上并没有告诉您如何做到这一点。事实证明这很容易:

<component 
  id="home.controller" 
  type="DoYourStuff.Controllers.HomeController, DoYourStuff" 
  lifestyle="transient" />

并且无需任何代码更改,它现在应该可以按预期工作(即,每次都由容器的一个实例提供唯一的控制器)。然后,您可以在配置文件中进行所有 IoC 配置,而不是像我认识的好男孩/女孩那样的代码。

于 2008-11-03T09:14:19.997 回答