ViewModel最佳实践

240
这个问题来看,似乎有一个控制器创建一个更准确反映视图试图显示的模型的 ViewModel 是有意义的,但我对一些惯例很好奇。
基本上,我有以下问题:
  1. 通常我喜欢有一个类/文件。 如果 ViewModel 仅被创建为从控制器到视图传递数据,这是否有意义?
  2. 如果 ViewModel 确实属于自己的文件,并且您正在使用目录/项目结构来保持事物分开,那么 ViewModel 文件应该放在哪里? 在 Controllers 目录中吗?
现在基本上就是这样。 我可能还有一些问题,但这已经困扰了我大约一个小时,我似乎找不到一致的指导。 编辑: 查看CodePlex上的NerdDinner应用程序示例,看起来ViewModels是Controllers的一部分,但他们不在自己的文件中仍然让我感到不舒服。

66
我不会将NerdDinner完全称为“最佳实践”示例。你的直觉很准确。 :) - rmontgomery429
11个回答

214
我为每个视图创建一个称为“ViewModel”的实例,将它们放置在我的MVC Web项目的名为ViewModels的文件夹中。我根据它们所代表的控制器和操作(或视图)来命名它们。因此,如果我需要向Membership控制器上的SignUp视图传递数据,则创建一个名为MembershipSignUpViewModel.cs的类并将其放入ViewModels文件夹中。
然后,我添加必要的属性和方法以便于从控制器向视图传递数据。如果必要,我使用Automapper从我的ViewModel到域模型并反之亦然。
这也适用于包含其他ViewModel类型属性的复合ViewModel。例如,如果您在成员资格控制器的索引页面上有5个小部件,并且为每个部分视图创建了ViewModel - 您如何将数据从Index操作传递到部分?您可以向MembershipIndexViewModel添加一个MyPartialViewModel类型的属性,而在呈现部分时,您将传递 Model.MyPartialViewModel。
用这种方式做可以让您调整局部ViewModel的属性,而不必更改Index视图。它仍然只是传递Model.MyPartialViewModel,因此当您仅添加局部ViewModel属性时,您就不必完全遍历所有局部来修复问题。
我还将命名空间“MyProject.Web.ViewModels”添加到web.config中,以便在任何视图中引用它们,而无需在每个视图中添加显式导入语句。这只是使它看起来更清洁一些。

3
如果您想从一个局部视图进行POST并返回整个视图(在模型出错的情况下),该怎么办?在局部视图中,您无法访问父模型。 - Cosmo
5
@Cosmo:接着将数据POST到一个操作,以便在模型出错时能够返回完整的视图。在服务器端,您有足够的信息来重新创建父模型。 - Tomas Aschan
通常,登录 [GET] 不会调用 ViewModel,因为不需要加载任何数据。 - Andre Figueiredo
很好的建议。数据访问、处理和模型/VM属性设置应该放在哪里?在我的情况下,我们将从本地CMS数据库获取一些数据,还有一些来自Web服务,这些数据需要在设置到模型之前进行处理/操作。把所有这些都放在控制器中会变得非常混乱。 - xr280xr
一个使用复合ViewModel的复合视图将发送哪个ViewModel到Action? - SMUsamaShah
显示剩余4条评论

128

按类别(控制器、视图模型、过滤器等)分离类是无意义的。

如果你想为网站的首页部分(/)编写代码,那么创建一个名为Home的文件夹,将HomeController、IndexViewModel、AboutViewModel等所有与Home actions相关的类放在其中。

如果有共享类,比如一个ApplicationController,可以把它放在项目根目录下。

为什么要分离相关的东西(HomeController、IndexViewModel),而将毫不相关的东西放在一起(HomeController、AccountController)呢?


我写了一篇关于此主题的博客文章


13
如果你这样做的话,情况会很快变得非常混乱。 - UpTheCreek
16
不,混乱的做法是将所有控制器放在一个目录/命名空间中。如果你有5个控制器,每个控制器使用5个视图模型,那么你就有25个视图模型。命名空间是组织代码的机制,在这里也不应该有任何不同。 - Max Toro
43
很惊讶你被投了这么多负票,经过一段时间的ASP.Net MVC开发,我因为所有的ViewModel都在一个地方、所有的控制器在另一个地方、而所有的View则在另一个地方而感到很痛苦。MVC是由三个相关部分组成,它们是相互耦合的,它们相互支持。如果给定部分的控制器、ViewModel和View位于同一个目录中,那么我觉得解决方案可以更加有条理。例如:MyApp/Accounts/Controller.cs,MyApp/Accounts/Create/ViewModel.cs,MyApp/Accounts/Create/View.cshtml等。 - quentin-starin
15
关注点分离并不等同于类的分离。 - Max Toro
13
@RyanJMcGowan 我想补充Max的评论并声明,即使在最初的开发阶段,我们也希望逐个功能地工作。逐个功能是例如SCRUM开发过程采用的方法,其中每个故事都增加了业务价值。仅花费2个月时间开发视图模型,实际上添加的业务价值仍然为零,因为没有任何可用的东西。 - Oskar Berggren
显示剩余26条评论

21
我将我的应用程序类放在名为“Core”的子文件夹中(或单独的类库中),并使用与KIGG示例应用程序相同的方法,但稍作更改以使我的应用程序更加DRY。
我在/Core/ViewData/中创建了一个BaseViewData类,其中存储了常见的站点范围属性。
此后,我还在同一文件夹中创建了所有视图ViewData类,然后这些类从BaseViewData派生,并具有特定于视图的属性。
然后我创建了一个ApplicationController,所有控制器都从中派生。ApplicationController具有以下通用的GetViewData方法:
protected T GetViewData<T>() where T : BaseViewData, new()
    {
        var viewData = new T
        {
           Property1 = "value1",
           Property2 = this.Method() // in the ApplicationController
        };
        return viewData;
    }

最后,在我的控制器操作中,我执行以下步骤来构建我的 ViewData 模型。

public ActionResult Index(int? id)
    {
        var viewData = this.GetViewData<PageViewData>();
        viewData.Page = this.DataContext.getPage(id); // ApplicationController
        ViewData.Model = viewData;
        return View();
    }

我认为这很有效,可以让你的视图保持整洁,让你的控制器更加简洁明了。

13

ViewModel类用于将多个数据对象封装为一个易于管理的对象,并将其传递给View。

最好将您的ViewModel类放在自己的文件中,在自己的目录中。在我的项目中,我有一个Models文件夹的子文件夹叫做ViewModels。这就是我的ViewModels(例如ProductViewModel.cs)所在的位置。


13

没有一个好的地方来存储你的模型。如果项目很大并且有许多ViewModels(数据传输对象),则可以将它们放在单独的程序集中。此外,您还可以将它们放在站点项目的单独文件夹中。例如,在Oxite中,它们被放置在包含许多其他类的Oxite项目中。Oxite中的控制器已移至单独的项目中,视图也是如此。
CodeCampServer中,ViewModels命名为*Form,并放置在UI项目的Models文件夹中。
MvcPress项目中,它们被放置在Data项目中,该项目还包含所有与数据库交互的代码以及更多内容(但我不推荐这种方法,仅供参考)
因此,您可以看到有许多观点。通常,我将我的ViewModels(DTO对象)保存在站点项目中。但是,当我有超过10个模型时,我更喜欢将它们移到单独的程序集中。通常在这种情况下,我也会将控制器移动到单独的程序集中。
另一个问题是如何轻松地将所有数据从模型映射到ViewModel。我建议看看AutoMapper库。我非常喜欢它,它可以为我完成所有脏活。
我还建议看看SharpArchitecture项目。它为项目提供了非常好的架构,并且包含许多很酷的框架、指导和伟大的社区。


6

这是我最佳实践中的代码片段:

    public class UserController : Controller
    {
        private readonly IUserService userService;
        private readonly IBuilder<User, UserCreateInput> createBuilder;
        private readonly IBuilder<User, UserEditInput> editBuilder;

        public UserController(IUserService userService, IBuilder<User, UserCreateInput> createBuilder, IBuilder<User, UserEditInput> editBuilder)
        {
            this.userService = userService;
            this.editBuilder = editBuilder;
            this.createBuilder = createBuilder;
        }

        public ActionResult Index(int? page)
        {
            return View(userService.GetPage(page ?? 1, 5));
        }

        public ActionResult Create()
        {
            return View(createBuilder.BuildInput(new User()));
        }

        [HttpPost]
        public ActionResult Create(UserCreateInput input)
        {
            if (input.Roles == null) ModelState.AddModelError("roles", "selectati macar un rol");

            if (!ModelState.IsValid)
                return View(createBuilder.RebuildInput(input));

            userService.Create(createBuilder.BuilEntity(input));
            return RedirectToAction("Index");
        }

        public ActionResult Edit(long id)
        {
            return View(editBuilder.BuildInput(userService.GetFull(id)));
        }

        [HttpPost]
        public ActionResult Edit(UserEditInput input)
        {           
            if (!ModelState.IsValid)
                return View(editBuilder.RebuildInput(input));

            userService.Save(editBuilder.BuilEntity(input));
            return RedirectToAction("Index");
        }
}

5

我们将所有的ViewModel放在Models文件夹中(所有业务逻辑都在单独的ServiceLayer项目中)


4

个人建议,如果ViewModel不是微不足道的话,请使用单独的类。

如果您有多个ViewModel,则建议将其分区至少一个目录中。如果ViewModel稍后共享,则在目录中暗示的名称空间使其更容易移动到新的程序集中。


2
在我们的项目中,模型和控制器是分开的,不与视图混在一起。
作为一个经验法则,我们试图将大部分 ViewData["..."] 的内容移动到 ViewModel 中,这样我们就可以避免强制转换和魔术字符串,这是一件好事。
ViewModel 还包含一些常见属性,比如列表的分页信息或页面的头部信息,以绘制面包屑和标题。目前,我认为基类包含了太多信息,我们可以将其分成三个部分:对于 99% 的页面来说,基本且必要的信息在一个基础视图模型中,然后一个用于列表的模型和一个用于表单的模型,它们继承自基础视图模型并保存特定场景的数据。
最后,我们为每个实体实现一个视图模型,以处理特定的信息。

0

控制器中的代码:

    [HttpGet]
        public ActionResult EntryEdit(int? entryId)
        {
            ViewData["BodyClass"] = "page-entryEdit";
            EntryEditViewModel viewMode = new EntryEditViewModel(entryId);
            return View(viewMode);
        }

    [HttpPost]
    public ActionResult EntryEdit(Entry entry)
    {
        ViewData["BodyClass"] = "page-entryEdit";            

        #region save

        if (ModelState.IsValid)
        {
            if (EntryManager.Update(entry) == 1)
            {
                return RedirectToAction("EntryEditSuccess", "Dictionary");
            }
            else
            {
                return RedirectToAction("EntryEditFailed", "Dictionary");
            }
        }
        else
        {
            EntryEditViewModel viewModel = new EntryEditViewModel(entry);
            return View(viewModel);
        }

        #endregion
    }

视图模型中的代码:

public class EntryEditViewModel
    {
        #region Private Variables for Properties

        private Entry _entry = new Entry();
        private StatusList _statusList = new StatusList();        

        #endregion

        #region Public Properties

        public Entry Entry
        {
            get { return _entry; }
            set { _entry = value; }
        }

        public StatusList StatusList
        {
            get { return _statusList; }
        }

        #endregion

        #region constructor(s)

        /// <summary>
        /// for Get action
        /// </summary>
        /// <param name="entryId"></param>
        public EntryEditViewModel(int? entryId)
        {
            this.Entry = EntryManager.GetDetail(entryId.Value);                 
        }

        /// <summary>
        /// for Post action
        /// </summary>
        /// <param name="entry"></param>
        public EntryEditViewModel(Entry entry)
        {
            this.Entry = entry;
        }

        #endregion       
    }

项目:

  • DevJet.Web(ASP.NET MVC Web 项目)

  • DevJet.Web.App.Dictionary(一个独立的类库项目)

    在这个项目中,我创建了一些文件夹,如: DAL(数据访问层), BLL(业务逻辑层), BO(业务对象), VM(视图模型文件夹)


你好,能否分享一下Entry类的结构是怎样的? - Dinis Cruz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接