ViewModel最佳做法


238

这个问题来看,让控制器创建一个ViewModel来更准确地反映视图试图显示的模型似乎很有意义,但是我对某些约定感到好奇(我是MVC模式的新手。 ,如果还不是很明显的话)。

基本上,我有以下问题:

  1. 我通常喜欢一个班级/一个文件。如果仅创建ViewModel只是为了将数据从控制器传递到视图,这是否有意义?
  2. 如果ViewModel确实属于其自己的文件,并且您正在使用目录/项目结构将各部分分开,则ViewModel文件属于什么地方?在Controllers目录中?

目前基本上就是这样。我可能还会提出一些其他问题,但这在最后一个小时左右一直困扰着我,而且我似乎可以在其他地方找到一致的指导。

编辑:在CodePlex上查看 示例NerdDinner应用程序,看起来ViewModels是控制器的一部分,但仍然令我感到不舒服的是它们不在自己的文件中。


66
我不会将NerdDinner称为“最佳实践”示例。您的直觉对您有好处。:)
Ryan Montgomery

Answers:


211

我为每个视图创建所谓的“ ViewModel”。我将它们放在MVC Web项目中名为ViewModels的文件夹中。我以它们代表的控制器和动作(或视图)命名。因此,如果需要将数据传递到Membership控制器上的SignUp视图,则可以创建MembershipSignUpViewModel.cs类并将其放在ViewModels文件夹中。

然后,我添加了必要的属性和方法,以促进数据从控制器到视图的传输。我使用Automapper从ViewModel到Domain Model,然后在必要时再次返回。

这对于包含其他ViewModel类型的属性的复合ViewModel也很好。例如,如果在成员资格控制器的索引页面上有5个小部件,并且为每个局部视图创建了一个ViewModel-怎样将数据从Index动作传递到局部视图?您将一个属性添加到MyPartialViewModel类型的MembershipIndexViewModel中,并且在渲染部分时,您将传递Model.MyPartialViewModel。

这样,您就可以调整部分ViewModel属性,而不必完全更改Index视图。它仍然只是传递给Model.MyPartialViewModel,所以当您要做的只是向部分ViewModel添加属性时,您几乎不必遍历整个部分链来修复某些问题。

我还将在Web.config中添加名称空间“ MyProject.Web.ViewModels”,以便允许我在任何视图中引用它们,而无需在每个视图上添加显式import语句。只是使它更清洁。


3
如果要从局部视图进行POST并返回整个视图(如果发生模型错误)怎么办?在局部视图中,您无权访问父模型。
Cosmo 2010年

5
@Cosmo:然后发布到可以在发生模型错误的情况下返回整个视图的操作。在服务器端,您有足够的时间来重新创建父模型。
Tomas Aschan 2011年

登录[POST]和登录[GET]操作如何?有不同的视图模型?
巴特·卡利克斯托

通常,登录[GET]不会调用ViewModel,因为不需要加载任何数据。
Andre Figueiredo

很好的建议。模型/ VM属性的数据访问,处理和设置应该去哪里?就我而言,我们将有一些来自本地CMS数据库的数据和一些来自Web服务的数据,需要在对模型进行设置之前对其进行处理/操作。将所有内容放到控制器中会变得很混乱。
xr280xr 2015年

124

按类别(控制器,ViewModel,过滤器等)分隔类是无稽之谈。

如果要为网站(/)的“主页”部分编写代码,请创建一个名为Home的文件夹,然后在其中放置HomeController,IndexViewModel,AboutViewModel等以及Home动作使用的所有相关类。

如果您拥有诸如ApplicationController之类的共享类,则可以将其放在项目的根目录。

为什么要分开相关的事物(HomeController,IndexViewModel),并把根本没有任何关系的事物(HomeController,AccountController)放在一起?


我写了一篇有关该主题的博客文章


13
如果这样做,事情很快就会变得很混乱。
09年

14
不,麻烦的是将所有控制器放在一个目录/名称空间中。如果您有5个控制器,每个控制器使用5个视图模型,那么您就有25个视图模型。命名空间是组织代码的机制,在这里应该没有什么不同。
Max Toro

41
@Max Toro:惊讶于您被否决了。在ASP.Net MVC工作一段时间后,我感觉很多痛苦从具有全部在一个地方的ViewModels,所有在另一个控制器,以及所有在另一个视图。MVC是三个相关的部分,它们相互关联的-它们相互支持。我觉得自己像一个解决方案可我很多更有条理,如果控制器的ViewModels,并在同一目录视图对于给定的部分生活在一起。MyApp / Accounts / Controller.cs,MyApp / Accounts / Create / ViewModel.cs,MyApp / Accounts / Create / View.cshtml等
quentin-starin 2011年

13
@RyanJMcGowan关注的分离不是类的分离。
Max Toro

12
@RyanJMcGowan无论您以何种方式进行开发,问题都会最终产生,特别是对于大型应用程序。进入维护模式后,您无需考虑所有型号,而是考虑所有控制器,一次只能添加一个功能。
Max Toro

21

我将应用程序类保存在一个名为“ Core”(或单独的类库)的子文件夹中,并使用与KIGG示例应用程序相同的方法,但进行了一些细微更改以使我的应用程序更加干燥。

我在/ 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;
    }

最后,在我的Controller动作中,我执行以下操作来构建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类放在自己的文件中,位于自己的目录中会很有意义。在我的项目中,我有一个名为ViewModels的Models文件夹的子文件夹。那就是我的ViewModels(例如ProductViewModel.cs)所在的地方。


13

没有适合放置模型的好地方。如果项目很大并且有很多ViewModel(数据传输对象),则可以将它们放在单独的程序集中。您也可以将它们保存在站点项目的单独文件夹中。例如,在Oxite中,它们被放置在Oxite项目中,该项目也包含许多不同的类。Oxite中的控制器移至单独的项目,视图也位于单独的项目中。
CodeCampServer中, ViewModels命名为* Form,并将其放置在Models文件夹中的UI项目中。
MvcPress中项目中,它们被放置在Data项目中,该项目还包含所有与数据库一起使用的代码以及更多代码(但是我不推荐这种方法,它仅用于示例)
因此您可以看到有很多观点。我通常将ViewModels(DTO对象)保留在站点项目中。但是当我有10个以上的模型时,我更喜欢将它们移到单独的组件中。通常在这种情况下,我也将控制器移到单独的组件上。
另一个问题是如何轻松地将所有数据从模型映射到ViewModel。我建议看一下AutoMapper库。我非常喜欢它,它为我完成了所有肮脏的工作。
我也建议您看一下SharpArchitecture项目。它为项目提供了很好的体系结构,并且包含许多很酷的框架和指南以及很棒的社区。


8
ViewModels!= DTO
Bart Calixto

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

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


4

我个人建议,如果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类的结构吗?
丹尼斯·克鲁兹

0

创建一个具有通用属性的视图模型基类,例如操作结果和上下文数据,还可以放置当前用户数据和角色

class ViewModelBase 
{
  public bool HasError {get;set;} 
  public string ErrorMessage {get;set;}
  public List<string> UserRoles{get;set;}
}

在基本控制器类中,有一个类似PopulateViewModelBase()的方法,该方法将填充上下文数据和用户角色。HasError和ErrorMessage,如果从service / db提取数据时发生异常,请设置这些属性。在视图上绑定这些属性以显示错误。用户角色可用于基于角色在视图上显示隐藏部分。

要在不同的get操作中填充视图模型,可以通过使用具有抽象方法FillModel的基本控制器来使其一致

class BaseController :BaseController 
{
   public PopulateViewModelBase(ViewModelBase model) 
{
   //fill up common data. 
}
abstract ViewModelBase FillModel();
}

在控制器中

class MyController :Controller 
{

 public ActionResult Index() 
{
   return View(FillModel()); 
}

ViewModelBase FillModel() 
{ 
    ViewModelBase  model=;
    string currentAction = HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString(); 
 try 
{ 
   switch(currentAction) 
{  
   case "Index": 
   model= GetCustomerData(); 
   break;
   // fill model logic for other actions 
}
}
catch(Exception ex) 
{
   model.HasError=true;
   model.ErrorMessage=ex.Message;
}
//fill common properties 
base.PopulateViewModelBase(model);
return model;
}
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.