ASP.NET MVC:当您的 ViewModel 是 Collection/List/IEnumerable 时如何维护 TextBox 状态
我正在使用 ASP.NET MVC 2 Beta。我可以使用 Steven Sanderson 的技术(在他的《Pro ASP.NET MVC Framework》一书中)创建一个类似向导的工作流程,除了使用 Session 而不是隐藏表单字段来跨请求保留数据。当我的模型不是集合时,我可以在页面之间来回切换并维护文本框中的值,没有任何问题。一个例子是一个简单的 Person 模型:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
但是当我传递 IEnumerable 时,我无法让它工作。在我看来,我试图运行模型并为列表中的每个人生成一个用于名称和电子邮件的文本框。我可以很好地生成表单,并且可以使用我的值提交表单并转到步骤 2。但是,当我单击步骤 2 中的“后退”按钮时,它会将我带回步骤 1,并显示一个空表单。我之前填写的字段都不存在。一定有什么东西我错过了。有人可以帮我吗?
这是我的视图:
<% using (Html.BeginForm()) { %>
<% int index = 0;
foreach (var person in Model) { %>
<fieldset>
<%= Html.Hidden("persons.index", index.ToString())%>
<div>Name: <%= Html.TextBox("persons[" + index.ToString() + "].Name")%></div>
<div>Email: <%= Html.TextBox("persons[" + index.ToString() + "].Email")%></div>
</fieldset>
<% index++;
} %>
<p><input type="submit" name="btnNext" value="Next >>" /></p>
<% } %>
这是我的控制器:
public class PersonListController : Controller
{
public IEnumerable<Person> persons;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
persons = (Session["persons"]
?? TempData["persons"]
?? new List<Person>()) as List<Person>;
// I've tried this with and without the prefix.
TryUpdateModel(persons, "persons");
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
Session["persons"] = persons;
if (filterContext.Result is RedirectToRouteResult)
TempData["persons"] = persons;
}
public ActionResult Step1(string btnBack, string btnNext)
{
if (btnNext != null)
return RedirectToAction("Step2");
// Setup some fake data
var personsList = new List<Person>
{
new Person { Name = "Jared", Email = "[email protected]", },
new Person { Name = "John", Email = "[email protected]" }
};
// Populate the model with fake data the first time
// the action method is called only. This is to simulate
// pulling some data in from a DB.
if (persons == null || persons.Count() == 0)
persons = personsList;
return View(persons);
}
// Step2 is just a page that provides a back button to Step1
public ActionResult Step2(string btnBack, string btnNext)
{
if (btnBack != null)
return RedirectToAction("Step1");
return View(persons);
}
}
I am using ASP.NET MVC 2 Beta. I can create a wizard like workflow using Steven Sanderson's technique (in his book Pro ASP.NET MVC Framework) except using Session instead of hidden form fields to preserve the data across requests. I can go back and forth between pages and maintain the values in a TextBox without any issue when my model is not a collection. An example would be a simple Person model:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
But I am unable to get this to work when I pass around an IEnumerable. In my view I am trying to run through the Model and generate a TextBox for Name and Email for each Person in the list. I can generate the form fine and I can submit the form with my values and go to Step2. But when I click the Back button in Step2 it takes me back to Step1 with an empty form. None of the fields that I previously populated are there. There must be something I am missing. Can somebody help me out?
Here is my View:
<% using (Html.BeginForm()) { %>
<% int index = 0;
foreach (var person in Model) { %>
<fieldset>
<%= Html.Hidden("persons.index", index.ToString())%>
<div>Name: <%= Html.TextBox("persons[" + index.ToString() + "].Name")%></div>
<div>Email: <%= Html.TextBox("persons[" + index.ToString() + "].Email")%></div>
</fieldset>
<% index++;
} %>
<p><input type="submit" name="btnNext" value="Next >>" /></p>
<% } %>
And here is my controller:
public class PersonListController : Controller
{
public IEnumerable<Person> persons;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
persons = (Session["persons"]
?? TempData["persons"]
?? new List<Person>()) as List<Person>;
// I've tried this with and without the prefix.
TryUpdateModel(persons, "persons");
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
Session["persons"] = persons;
if (filterContext.Result is RedirectToRouteResult)
TempData["persons"] = persons;
}
public ActionResult Step1(string btnBack, string btnNext)
{
if (btnNext != null)
return RedirectToAction("Step2");
// Setup some fake data
var personsList = new List<Person>
{
new Person { Name = "Jared", Email = "[email protected]", },
new Person { Name = "John", Email = "[email protected]" }
};
// Populate the model with fake data the first time
// the action method is called only. This is to simulate
// pulling some data in from a DB.
if (persons == null || persons.Count() == 0)
persons = personsList;
return View(persons);
}
// Step2 is just a page that provides a back button to Step1
public ActionResult Step2(string btnBack, string btnNext)
{
if (btnBack != null)
return RedirectToAction("Step1");
return View(persons);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
据我所知,ASP.NET MVC 2 Beta 不支持此功能,ASP.NET MVC 2 RC 也不支持此功能。我仔细研究了 MVC 源代码,看起来支持字典,但不支持 IEnumerable<> 的模型。 (或包含嵌套的 IEnumerable 对象)及其继承者,例如 IList<>。
问题出在 ViewDataDictionary 类中。特别是,GetPropertyValue 方法仅提供一种从字典属性(通过调用 GetIndexedPropertyValue)或简单属性(通过使用 PropertyDescriptor.GetValue 方法提取值)检索属性值的方法。
为了解决这个问题,我创建了一个 GetCollectionPropertyValue 方法来处理集合模型(甚至包含嵌套集合的模型)。我把代码贴在这里供参考。注意:我没有对优雅做出任何声明 - 事实上所有字符串解析都非常丑陋,但它似乎有效。方法如下:
下面是修改后的 GetPropertyValue 方法,它调用新方法:
同样,该方法位于 ASP.NET MVC 2 RC 中的 ViewDataDictionary.cs 文件中。我是否应该创建一个新问题来在 MVC Codeplex 网站上跟踪此问题?
As far as I can tell, this is not supported in ASP.NET MVC 2 Beta, nor is it supported in ASP.NET MVC 2 RC. I dug through the MVC source code and it looks like Dictionaries are supported but not Models that are IEnumerable<> (or that contain nested IEnumerable objects) and it's inheritors like IList<>.
The issue is in the ViewDataDictionary class. Particularly, the GetPropertyValue method only provides a way to retrieve property values from dictionary properties (by calling GetIndexedPropertyValue) or simple properties by using the PropertyDescriptor.GetValue method to pull out the value.
To fix this, I created a GetCollectionPropertyValue method that handles Models that are collections (and even Models that contain nested collections). I am pasting the code here for reference. Note: I don't make any claims about elegance - in fact all the string parsing is pretty ugly, but it seems to be working. Here is the method:
And here is the modified GetPropertyValue method which calls the new method:
Again, this is in the ViewDataDictionary.cs file in ASP.NET MVC 2 RC. Should I create a new issue to track this on the MVC codeplex site?