绑定到 MVC 中的 SelectList
我再次面临“这不应该是这样?*!#很难”的情况。
问题:我想在 MVC 中使用表单来创建对象。该对象的一个元素是一组有限的选择 - 下拉列表的完美候选者。
但是,如果我在模型中使用 SelectList,在视图中使用下拉列表,然后尝试将模型发布回 Create 方法,则会收到错误“缺少方法异常:此对象没有无参数构造函数”。探索 MVC 源代码,似乎为了绑定到模型,Binder 必须首先能够创建它,并且它无法创建 SelectList,因为它没有默认的构造函数。
这是简化的代码: 对于模型:
public class DemoCreateViewModel
{
public SelectList Choice { get; set; }
}
对于控制器:
//
// GET: /Demo/Create
public ActionResult Create()
{
DemoCreateViewModel data = new DemoCreateViewModel();
data.Choice = new SelectList(new string[] { "Choice1", "Choice2", "Choice3" });
ViewData.Model = data;
return View();
}
//
// POST: /Demo/Create
[HttpPost]
public ActionResult Create(DemoCreateViewModel form)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
对于视图:
<fieldset>
<legend>Fields</legend>
<%= Html.LabelFor(model => model.Choice) %>
<%= Html.DropDownListFor(model => model.Choice, Model.Choice) %>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
现在,我知道我可以通过后退 10 码并下注来完成这项工作:绕过模型绑定并返回到 FormCollection 并自己验证和绑定所有字段,但是有必须是一个更简单的方法。我的意思是,这只是一个简单的要求。有没有办法让它在 MVC ModelBinding 架构中工作?如果是的话,那是什么?如果没有,怎么会呢?
编辑:嗯,我脸上有鸡蛋,但这也许对其他人有帮助。我做了更多实验,发现了一个似乎有效的简单解决方案。
提供一个简单值(字符串或整数,具体取决于您的选择列表值类型),并将其命名为您绑定到的模型元素。然后提供第二个元素作为选项的选择列表,并将其命名为其他名称。所以我的模型变成:
public class DemoCreateViewModel
{
public string Choice { get; set; }
public SelectList Choices { get; set; }
}
然后视图中的 DropDownListFor 语句变成:
<%= Html.DropDownListFor(model => model.Choice, Model.Choices) %>
当我这样做时,提交按钮正确地将表单中所做的选择绑定到字符串 Choice,并将模型提交回第二个 Create 方法。
Once again I'm confronted with a "This shouldn't be this ?*!# hard" situation.
Problem: I want to use a form in MVC for creation of an object. One of the elements of the object is a set of limited choices - a perfect candidate for a drop down list.
But if I use a SelectList in my model, and a drop down list in my View, and then try to post the Model back to my Create method, I get the error "Missing Method Exception:No Parameterless constructor for this object". Exploring the MVC source code, it appears that in order to bind to a model, the Binder has to be able to create it first, and it can't create a SelectList because there is no default constructor for it.
Here's the simplified code:
For the model:
public class DemoCreateViewModel
{
public SelectList Choice { get; set; }
}
For the controller:
//
// GET: /Demo/Create
public ActionResult Create()
{
DemoCreateViewModel data = new DemoCreateViewModel();
data.Choice = new SelectList(new string[] { "Choice1", "Choice2", "Choice3" });
ViewData.Model = data;
return View();
}
//
// POST: /Demo/Create
[HttpPost]
public ActionResult Create(DemoCreateViewModel form)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
And for the View:
<fieldset>
<legend>Fields</legend>
<%= Html.LabelFor(model => model.Choice) %>
<%= Html.DropDownListFor(model => model.Choice, Model.Choice) %>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
Now, I know I can MAKE this work by dropping back 10 yards and punting: bypass model binding and drop back to the FormCollection and validate and bind all the fields myself, but there's got to be a simpler way. I mean, this is about as simple a requirement as it gets. Is there a way to make this work within the MVC ModelBinding architecture? If so, what is it? And if not, how come?
Edit: Well, I have egg on my face, but maybe this will help someone else. I did some more experimenting and found a simple solution that seems to work.
Provide a simple value (string or integer, depending on what your select list value type is), and name that as the model element that you bind to. Then provide a second element as the select list of choices, and name it something else. So my model became:
public class DemoCreateViewModel
{
public string Choice { get; set; }
public SelectList Choices { get; set; }
}
And then the DropDownListFor statement in the View becomes:
<%= Html.DropDownListFor(model => model.Choice, Model.Choices) %>
When I do this, the submit button correctly binds the choice made in the form to the string Choice, and submits the model back to the second Create method.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是一种方法:
请注意,我使用 ViewBag 来包含我的 SelectList。这样,当您回发时,客户端不会将整个选择列表作为模型的一部分发送到服务器。
在你的控制器代码中,你只需要设置视图包:
Here is one approach:
Notice that I use ViewBag to contain my SelectList. This way when you post back, the client doesn't send the entire select list up to the server as part of the model.
In your controller code, you just need to set the view bag:
考虑为您的发布操作创建一个不带 SelectList 属性的不同视图模型:
如果模型状态无效并且您想要重新显示视图,则您始终可以从 DemoCreateViewModelPost 实例映射到 DemoCreateViewModel 实例。我倾向于更喜欢视图所需的所有内容都在我的显示视图模型类中,因此使用单独的仅更新视图模型可以让我在返回服务器的过程中保持精简和修剪。
在您看来,您会这样做:
如之前的答案所示,这样就不会往返不必要的数据。
Consider creating a different view model for your post action without the SelectList property:
Then you can always map from the DemoCreateViewModelPost instance to an DemoCreateViewModel instance if the model state is invalid and you want to re-show the view. I tend to prefer everything needed by the view to be in my display view model class, so using a separate update only view model let's me keep things slim and trim for the trip back to the server.
In your view, you'd do:
as in the previous answer, so no unnecessary data would round trip.