6

當我使用默認模型綁定將表單參數綁定到作爲動作參數的複雜對象時,框架會記住傳遞給第一個請求,意味着對該操作的任何後續請求都會獲得與第一個相同的數據。參數值和驗證狀態在不相關的Web請求之間持續存在。ASP.NET MVC Beta 1:DefaultModelBinder在無關請求之間錯誤地持久參數和驗證狀態

這裏是我的控制器代碼(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類,即meant to autobind my model's properties

我點擊頁面,輸入有效的數據(例如time = 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

事實證明,問題是我的控制器正在通話之間重複使用。我選擇從原始文章中省略的細節之一是我使用Castle.Windsor容器來創建我的控制器。我沒有用Transient的生活方式來標記我的控制器,所以我在每次請求時都得到相同的實例。因此綁定器正在使用的上下文被重新使用,當然它包含陳舊的數據。

我仔細分析了Eilon的代碼和我的代碼之間的差異,發現了這個問題,消除了所有其他的可能性。由於Castle documentation says,這是一個「可怕的錯誤」!讓這是對他人的警告!

感謝您的回覆Eilon - 抱歉佔用您的時間。

+0

這件事發生在我身上。我花了很長時間才弄明白。 將來,讓MvcContrib使用他們的WindsorContainer擴展方法註冊您的控制器。 – 2008-10-28 14:26:43

2

我試圖重現這個問題,但我沒有看到同樣的行爲。我創建了幾乎完全相同的控制器和視圖(有一些假設),每次創建一個新的「RunTime」時,我都將它的值放入TempData中,並通過重定向發送它。然後,在目標頁面上,我抓取了該值,並且始終是我在該請求中輸入的值 - 從來沒有陳舊的值。

這裏是我的控制器:

公共類HomeController的:控制{ 公衆的ActionResult指數(){ 計算機[ 「標題」] = 「主頁」; string message =「Welcome:」+ TempData [「Message」]; (TempData.ContainsKey(「value」))int theValue =(int)TempData [「value」]; message + =「」+ theValue.ToString(); } ViewData [「Message」] = message; return 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" /> 
<% } %> 

而且,我不知道 「運行」 型是什麼樣子,所以我做了這一個:

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

2

我不知道這是相關或不相關,但您的來電 <%= Html.TextBox( 「newRunTime.Time」,ViewData.Model.Time )%> 實際上可能挑錯過載(因爲時間是一個整數,它會挑object htmlAttributes過載,而不是string value

檢查呈現的HTML將讓你知道,如果這正在發生。改變的int ViewData.Model.Time.ToString()會強迫正確的過載。

這聽起來像你的問題是不同的,但我注意到,並在過去被燒燬。

+0

感謝您的建議本。這次不是問題,但這聽起來像是我需要關注未來的事情,所以感謝您提醒我注意。 – 2008-10-27 20:08:53

0

seb,我不確定你是什麼意思的例子。我對Unity配置一無所知。我會用Castle.Windsor來解釋這種情況,也許這會幫助你正確地配置Unity。

默認情況下,每次請求給定類型時,Castle.Windsor返回相同的對象。這是單身生活方式。對Castle.Windsor documentation中的各種生活方式選項有很好的解釋。

在ASP.NET MVC中,控制器類的每個實例都綁定到它創建要提供的Web請求的上下文。所以如果你的IoC容器每次都返回你的控制器類的同一個實例,你總會得到一個控制器綁定到使用該控制器類的第一個Web請求的上下文。尤其是,ModelStateDefaultModelBinder所使用的其他對象將被重用,因此您綁定的模型對象和ModelState中的驗證消息將陳舊。

因此,您需要您的IoC在每次MVC請求您的控制器類的實例時返回一個新實例。

在Castle.Windsor中,這被稱爲瞬態生活方式。要配置它,您有兩種選擇:

  1. XML配置:您將lifestlye =「transient」添加到配置文件中代表控制器的每個元素。
  2. 在代碼配置:您可以告訴容器在註冊控制器時使用瞬態生活方式。這是Ben提到的MvcContrib幫助程序自動爲您執行的操作 - 查看MvcContrib source code中的RegisterControllers方法。

我會想象Unity爲Castle.Windsor的生活方式提供了一個類似的概念,因此您需要配置Unity以使用其相當於短暫生活方式的控制器。 MvcContrib似乎有一些Unity support - 也許你可以看看那裏。

希望這會有所幫助。

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); 

雖然溫莎正確連接了所有的控制器,由於某些原因參數沒有從表格傳遞到相關的控制器動作。相反,他們都是空的,儘管它正在調用正確的行動。

默認是容器傳回單身,顯然是控制器壞事,問題的原因:

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

但是文件並指出,該控制器的生活方式可以被改爲瞬態的,儘管它實際上並沒有告訴你如何使用配置文件。原來,這是很容易:

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

而無需更改任何代碼,現在應該(通過容器的一個實例提供的每一次即獨特的控制器)按預期工作。然後,您可以在配置文件中完成所有IoC配置,而不是像我認識的那樣的好孩子/女孩。

相關問題