6

Khi tôi sử dụng mô hình mặc định ràng buộc để ràng buộc tham số biểu mẫu cho đối tượng phức tạp là tham số cho một hành động, khuôn khổ ghi nhớ các giá trị được chuyển đến yêu cầu đầu tiên, có nghĩa là mọi yêu cầu tiếp theo đối với hành động đó sẽ nhận được dữ liệu giống như lần đầu tiên. Các giá trị tham số và trạng thái xác thực được duy trì giữa các yêu cầu web không liên quan.ASP.NET MVC Beta 1: DefaultModelBinder giữ nguyên trạng thái tham số và xác thực giữa các yêu cầu không liên quan

Đây là mã điều khiển của tôi (service đại diện cho quyền truy cập vào các back-end của ứng dụng):

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

xem aspx của tôi (gõ mạnh như ViewPage<RunTime>) chứa chỉ thị như:

<%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %> 

Điều này sử dụng lớp DefaultModelBinder, là meant to autobind my model's properties.

Tôi nhấn trang, nhập dữ liệu hợp lệ (ví dụ: thời gian = 1). Ứng dụng lưu chính xác đối tượng mới với thời gian = 1. Sau đó, tôi nhấn lại lần nữa, nhập dữ liệu hợp lệ khác (ví dụ: thời gian = 2). Tuy nhiên, dữ liệu được lưu là dữ liệu gốc (ví dụ: thời gian = 1). Điều này cũng ảnh hưởng đến việc xác thực, vì vậy nếu dữ liệu gốc của tôi không hợp lệ, thì tất cả dữ liệu tôi nhập trong tương lai được coi là không hợp lệ. Khởi động lại IIS hoặc xây dựng lại mã của tôi sẽ xóa trạng thái tồn tại.

Tôi có thể khắc phục sự cố bằng cách viết trình kết nối mô hình mã hóa cứng của riêng tôi, một ví dụ ngây thơ cơ bản được hiển thị bên dưới.

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

Tôi có thiếu gì đó không? Tôi không nghĩ rằng đó là vấn đề phiên trình duyệt vì tôi có thể tái tạo vấn đề nếu dữ liệu đầu tiên được nhập vào một trình duyệt và dữ liệu thứ hai trong một trình duyệt khác.

Trả lời

5

Nó chỉ ra rằng vấn đề là bộ điều khiển của tôi đã được tái sử dụng giữa các cuộc gọi. Một trong những chi tiết tôi đã chọn để bỏ qua từ bài viết ban đầu của tôi là tôi đang sử dụng container Castle.Windsor để tạo bộ điều khiển của tôi. Tôi đã thất bại trong việc đánh dấu bộ điều khiển của mình với lối sống thoáng qua, vì vậy tôi đã nhận được cùng một ví dụ về mỗi yêu cầu. Vì vậy, bối cảnh đang được sử dụng bởi các chất kết dính đã được tái sử dụng và tất nhiên nó chứa dữ liệu cũ.

Tôi đã phát hiện vấn đề trong khi phân tích kỹ lưỡng sự khác biệt giữa mã của Eilon và của tôi, loại bỏ tất cả các khả năng khác. Là Castle documentation says, đây là một "sai lầm khủng khiếp"! Hãy để điều này là một cảnh báo cho người khác!

Cảm ơn bạn đã trả lời Eilon - xin lỗi vì đã dành thời gian của bạn.

+0

Điều EXACT này đã xảy ra với tôi trước đây. Phải mất một thời gian dài tôi mới hiểu được. Trong tương lai, hãy để MvcContrib đăng ký bộ điều khiển của bạn bằng cách sử dụng các phương thức mở rộng WindsorContainer của chúng. –

+0

Mẹo hay - cảm ơn Ben. Bạn nên đặt nó như là một câu trả lời chứ không phải là một bình luận để có được một cuộc bỏ phiếu và một đánh dấu màu xanh lá cây! –

+0

Cảm ơn Alex, tôi đã đánh đầu của tôi chống lại vấn đề chính xác này cho hầu hết các buổi chiều. –

2

Tôi đã cố gắng tái tạo sự cố này nhưng tôi không thấy hành vi tương tự đó. Tôi tạo ra gần như chính xác cùng một bộ điều khiển và quan điểm mà bạn có (với một số giả định) và mỗi khi tôi tạo ra một "RunTime" mới, tôi đặt giá trị của nó trong TempData và gửi nó đi qua Redirect. Sau đó, trên trang mục tiêu tôi nắm lấy giá trị và nó luôn luôn là giá trị tôi đã gõ vào yêu cầu đó - không bao giờ là một giá trị cũ.

Dưới đây là bộ điều khiển của tôi:

public class HomeController: Bộ điều khiển { public ActionResult Index() { ViewData [ "Title"] = "Trang chủ"; thông báo chuỗi = "Chào mừng:" + TempData ["Nội dung"]; nếu (TempData.ContainsKey ("giá trị")) { int theValue = (int) TempData ["value"]; tin nhắn + = "" + 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); 
} 

}

Và đây là quan điểm của tôi (Create.aspx):

<% using (Html.BeginForm()) { %> 
<%= Html.TextBox("newRunTime.TheValue", ViewData.Model.TheValue) %> 
<input type="submit" value="Save" /> 
<% } %> 

Ngoài ra, tôi đã không chắc chắn những gì các "RunTime" loại trông giống như, vì vậy tôi làm cái này:

public class RunTime { 
     public static readonly RunTime Default = new RunTime(-1); 

     public RunTime() { 
     } 

     public RunTime(int theValue) { 
      TheValue = theValue; 
     } 

     public int TheValue { 
      get; 
      set; 
     } 
    } 

Có thể triển khai RunTime của bạn bao gồm một số giá trị tĩnh hay gì đó?

Cảm ơn,

Eilon

2

Tôi không chắc chắn nếu điều này có liên quan hay không, nhưng cuộc gọi của bạn để <% = Html.TextBox ("newRunTime.Time", ViewData.Model.Time)%> thực sự có thể chọn quá tải sai (kể từ khi Thời gian là một số nguyên, nó sẽ chọn quá tải object htmlAttributes, chứ không phải là string value.

Kiểm tra HTML rendered sẽ cho bạn biết nếu điều này xảy ra. thay đổi int để ViewData.Model.Time.ToString() sẽ buộc quá tải chính xác.

Có vẻ như vấn đề của bạn có vấn đề khác, nhưng tôi nhận thấy điều đó và đã bị đốt cháy trong quá khứ.

+0

Cảm ơn đề xuất Ben. Lần này không phải là vấn đề, nhưng nó có vẻ giống như một thứ tôi cần phải biết về tương lai, vì vậy cảm ơn vì đã chú ý đến nó. –

0

Seb, tôi không chắc chắn ý của bạn là một ví dụ. Tôi không biết gì về cấu hình Unity. Tôi sẽ giải thích tình hình với Castle.Windsor và có lẽ điều đó sẽ giúp bạn cấu hình Unity một cách chính xác.

Theo mặc định, Castle.Windsor trả về cùng một đối tượng mỗi khi bạn yêu cầu một loại nhất định. Đây là lối sống singleton. Có một giải thích tốt về các tùy chọn lối sống khác nhau trong Castle.Windsor documentation.

Trong ASP.NET MVC, mỗi phiên bản của lớp điều khiển được gắn với ngữ cảnh của yêu cầu web mà nó được tạo để phân phối. Vì vậy, nếu container IoC của bạn trả về cùng một thể hiện của lớp trình điều khiển của bạn mỗi lần, bạn sẽ luôn nhận được một bộ điều khiển gắn với ngữ cảnh của yêu cầu web đầu tiên sử dụng lớp điều khiển đó. Cụ thể, các ModelState và các đối tượng khác được sử dụng bởi DefaultModelBinder sẽ được sử dụng lại, do đó đối tượng mô hình bị ràng buộc của bạn và các thông báo xác thực trong ModelState sẽ bị cũ.

Vì vậy, bạn cần IoC của bạn để trả về một cá thể mới mỗi khi MVC yêu cầu một cá thể của lớp điều khiển của bạn.

Trong Castle.Windsor, điều này được gọi là lối sống thoáng qua. Để định cấu hình, bạn có hai tùy chọn:

  1. Cấu hình XML: bạn thêm lifestlye = "transient" vào mỗi phần tử trong tệp cấu hình đại diện cho bộ điều khiển.
  2. Cấu hình trong mã: bạn có thể yêu cầu vùng chứa sử dụng lối sống thoáng qua tại thời điểm bạn đăng ký bộ điều khiển. Đây là những gì người trợ giúp MvcContrib mà Ben đã đề cập tự động cho bạn - hãy xem phương thức RegisterControllers trong MvcContrib source code.

Tôi tưởng tượng Unity cung cấp một khái niệm tương tự về lối sống trong Castle.Windsor, vì vậy bạn cần cấu hình Unity để sử dụng tương đương với lối sống thoáng qua cho bộ điều khiển của bạn. MvcContrib dường như có một số Unity support - có thể bạn có thể xem ở đó.

Hy vọng điều này sẽ hữu ích.

0

Có vấn đề tương tự khi cố gắng sử dụng container Windsor IoC trong một ứng dụng ASP.NET MVC tôi đã phải đi qua cùng một hành trình khám phá để làm cho nó hoạt động. Dưới đây là một số chi tiết có thể giúp người khác.

Sử dụng này là thiết lập ban đầu trong Global.asax:

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

Và sử dụng một WindsorControllerFactory mà khi được hỏi cho một trường hợp điều khiển không:

return (IController)_container.Resolve(controllerType); 

Trong khi Windsor đã được liên kết một cách chính xác tất cả của bộ điều khiển, vì một số thông số lý do không được chuyển từ biểu mẫu đến hành động của bộ điều khiển có liên quan. Thay vào đó tất cả chúng đều vô giá trị, mặc dù nó đang gọi hành động chính xác.

Giá trị mặc định là dành cho các container để vượt qua trở lại độc thân, rõ ràng là một điều xấu cho bộ điều khiển và nguyên nhân của vấn đề:

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

Tuy nhiên tài liệu nào chỉ ra rằng lối sống của các bộ điều khiển có thể được thay đổi thành thoáng qua, mặc dù nó không thực sự cho bạn biết cách thực hiện điều đó nếu bạn đang sử dụng tệp cấu hình. Hóa ra nó đủ dễ dàng:

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

Và không có bất kỳ thay đổi mã nào bây giờ sẽ hoạt động như dự kiến ​​(mỗi lần cung cấp). Sau đó bạn có thể làm tất cả các cấu hình IoC của bạn trong tập tin cấu hình chứ không phải là mã như cậu bé/cô gái tốt mà tôi biết bạn đang có.