在 ASP.NET MVC 3 中使用视图模型
我对查看模型还比较陌生,并且在使用它们时遇到了一些问题。在一种情况下,我想知道最佳实践是什么......
我将视图所需的所有信息放入视图模型中。这是一个例子——请原谅任何错误,这是我凭空想象出来的。
public ActionResult Edit(int id)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
var model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
return View(model);
}
如果屏幕只允许编辑一两个字段,那么当视图模型返回时,它会丢失相当多的数据(理应如此)。
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
try
{
if (!ModelState.IsValid)
return View(model) // Won't work, view model is incomplete.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // Won't work, view model is incomplete.
}
}
我的临时解决方案是从头开始重新创建视图模型,从域模型重新填充它,将表单数据重新应用到它,然后正常进行。但这使得视图模型参数有些毫无意义。
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
// Recreate the view model from scratch.
model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
try
{
TryUpdateModel(model); // Reapply the form data.
if (!ModelState.IsValid)
return View(model) // View model is complete this time.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // View model is complete this time.
}
}
有更优雅的方式吗?
编辑
两个答案都是正确的,所以如果可以的话我会奖励他们两个。然而,经过反复试验,我发现他的解决方案是最精简的,所以我向 MJ 点头。
我仍然可以使用助手,吉米。如果我将需要显示的内容添加到视图包(或视图数据)中,就像这样...
ViewBag.Project= project;
然后我可以执行以下操作...
@Html.LabelFor(model => ((Project)ViewData["Project"]).Name)
@Html.DisplayFor(model => ((Project)ViewData["Project"]).Name)
有点黑客,它需要用 < 装饰域模型在某些情况下,code>System.ComponentModel.DisplayNameAttribute,但我已经这样做了。
我很想打电话...
@Html.LabelFor(model => ViewBag.Project.Name)
但是动态会导致表达式出现问题。
I'm relatively new to view models and I'm running into a few problems with using them. Here's one situation where I'm wondering what the best practice is...
I'm putting all the information a view needs into the view model. Here's an example -- please forgive any errors, this is coded off the top of my head.
public ActionResult Edit(int id)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
var model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
return View(model);
}
If the screen only allows editing of one or two fields, when the view model comes back it's missing quite a bit of data (as it should be).
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
try
{
if (!ModelState.IsValid)
return View(model) // Won't work, view model is incomplete.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // Won't work, view model is incomplete.
}
}
My temporary solution is to recreate the view model from scratch, repopulate it from the domain model, reapply the form data to it, then proceed as normal. But this makes the view model parameter somewhat pointless.
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
// Recreate the view model from scratch.
model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
try
{
TryUpdateModel(model); // Reapply the form data.
if (!ModelState.IsValid)
return View(model) // View model is complete this time.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // View model is complete this time.
}
}
Is there a more elegant way?
EDIT
Both answers are correct so I would award them both if I could. The nod goes to MJ however since after trial and error I find his solution to be the leanest.
I'm still able to use the helpers too, Jimmy. If I add what I need to be displayed to the view bag (or view data), like so...
ViewBag.Project= project;
I can then do the following...
@Html.LabelFor(model => ((Project)ViewData["Project"]).Name)
@Html.DisplayFor(model => ((Project)ViewData["Project"]).Name)
A bit of a hack, and it requires the domain model to be decorated with System.ComponentModel.DisplayNameAttribute
in some cases, but I already do that.
I'd love to call...
@Html.LabelFor(model => ViewBag.Project.Name)
But dynamic causes a problem in expressions.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
经过一些反复试验(又名编码,然后讨厌它)学习,我目前首选的方法是:
我仅使用视图模型来绑定输入字段。因此,在您的情况下,如果您的视图仅编辑两个字段,那么您的视图模型将只有两个属性。对于填充视图(下拉列表、标签等)所需的数据,我使用动态 ViewBag。
我相信显示视图(即填充视图需要显示的任何内容)和捕获发布的表单值(绑定、验证等)是两个独立的问题。我发现将填充视图所需的数据与从视图发回的数据混合在一起会变得很混乱,并且常常会准确地创建您的情况。我不喜欢部分填充的对象被传递。
我不确定 Automapper(用于将域对象映射到动态 ViewBag)如何发挥作用,因为我还没有使用过它。我相信它有一个可以工作的 DynamicMap 方法?将发布的强类型 ViewModel 自动映射到 Domain 对象上应该不会有任何问题。
After some trial-and-error (aka code it, then hate it) learning, my currently preferred approach is:
I use view-models only to bind input fields. So in your case, if your view is only editing two fields, then your view-model will only have two properties. For the data required to populate the view (drop-down lists, labels, etc), I use the dynamic ViewBag.
I believe that displaying the view (i.e. populating anything the view needs to display), and capturing the posted form values (binding, validation, etc) are two separate concerns. And I find that mixing the data required to populate the view with that which is posted back from the view gets messy, and creates exactly your situation more often than not. I dislike partially populated objects being passed around.
I’m not sure how this plays out with Automapper (for mapping the domain object to the dynamic ViewBag) though, as I haven’t used it. I believe it has a DynamicMap method that may work? You shouldn’t have any issues auto-mapping the posted strongly-typed ViewModel onto the Domain object.
如果我理解正确,您的视图模型可能看起来与您的域实体非常相似。您提到视图模型可能会返回大部分为空,因为只有某些字段是可编辑的。
假设您的视图中只有少数字段可用于编辑(或显示),那么这些是您应该在视图模型中提供的唯一字段。我通常为每个视图创建一个视图模型,并让控制器或服务处理用户的输入,并在执行一些验证后将其与域实体映射回来。
这里有一个关于视图模型最佳实践的主题,您可能会觉得有用。
编辑:您还可以在“编辑/POST”操作中接受与“编辑/GET”操作所提供的不同的视图模型。我相信只要模型绑定器能够弄清楚,这就应该有效。
If I understand correctly, your viewmodel probably looks very similar to your domain entity. You mentioned that the viewmodel can come back mostly empty because only certain fields were editable.
Assuming you have a view where only a few fields are available for edit (or display), these are the only fields you should make available in your viewmodel. I usually create one viewmodel per view, and let either the controller or a service handle the user's input and map it back up with the domain entity after performing some validation.
Here's a thread concerning best-practices for viewmodels that you might find useful.
Edit: You can also accept a different view model in your Edit/POST action than your Edit/GET action serves up. I believe this should work as long as the model binder can figure it out.