ViewModel最佳实践

时间:2009-03-19 21:48:58

标签: asp.net-mvc asp.net-mvc-viewmodel

this question开始,看起来让控制器创建一个 ViewModel 更准确地反映视图试图显示的模型是有道理的,但我很好奇一些惯例(我是MVC模式的新手,如果它还不是很明显的话)。

基本上,我有以下问题:

  1. 我通常喜欢有一个类/文件。如果只是创建它以将数据从控制器传递到视图,这对于 ViewModel 是否有意义?
  2. 如果 ViewModel 确实属于自己的文件,并且您正在使用目录/项目结构来保持分离,那么 ViewModel 文件属于哪里?在控制器目录?
  3. 现在基本上就是这样。我可能还会提出一些问题,但这在最后一个小时左右一直困扰着我,而且我似乎可以在其他地方找到一致的指导。

    修改 查看CodePlex上的示例NerdDinner app,看起来ViewModel是Controllers的一部分,但它仍然让我感到不舒服,因为它们不在自己的文件中。

11 个答案:

答案 0 :(得分:208)

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

然后我添加必要的属性和方法,以便于将数据从控制器传输到视图。我使用Automapper从我的ViewModel到域模型,然后在必要时再次返回。

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

这样做可以让您调整部分ViewModel属性,而无需更改索引视图。它仍然只是在Model.MyPartialViewModel中传递,因此当您所做的只是向部分ViewModel添加属性时,您将不太可能通过整个局部链来修复某些内容。

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

答案 1 :(得分:121)

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

如果你想为网站的主页部分编写代码(/),那么创建一个名为Home的文件夹,然后放置HomeController,IndexViewModel,AboutViewModel等以及Home操作使用的所有相关类。

如果你有共享类,比如ApplicationController,你可以将它放在项目的根目录下。

为什么要分开相关的东西(HomeController,IndexViewModel)并将所有东西放在一起(HomeController,AccountController)?


我写了一篇关于这个主题的blog post

答案 2 :(得分:21)

我将我的应用程序类保存在名为“Core”(或单独的类库)的子文件夹中,并使用与KIGG示例应用程序相同的方法,但稍作更改会使我的应用程序更加干燥。< / p>

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

我认为这种方法效果非常好,它可以保持您的视图整洁,并且您的控制器很瘦。

答案 3 :(得分:14)

ViewModel类用于将由类实例表示的多个数据封装到一个易于管理的对象中,您可以将其传递给View。

将ViewModel类放在自己的文件中,在自己的目录中是有意义的。在我的项目中,我有一个名为ViewModels的Models文件夹的子文件夹。这就是我的ViewModel(例如ProductViewModel.cs)生活的地方。

答案 4 :(得分:12)

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

答案 5 :(得分: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");
        }
}

答案 6 :(得分:5)

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

答案 7 :(得分:4)

就我个人而言,我建议如果ViewModel是微不足道的,那么就使用一个单独的类。

如果你有多个视图模型,那么我建议在至少一个目录中对它进行分区是有意义的。如果稍后共享视图模型,则目录中隐含的名称空间可以更轻松地移动到新程序集。

答案 8 :(得分:2)

在我们的例子中,我们将模型与控制器放在一个与视图分开的项目中。

根据经验,我们试图将大部分ViewData [“...”]内容移动到ViewModel,因此我们避免使用castings和魔术字符串,这是一件好事。

ViewModel还包含一些常见属性,例如列表的分页信息或页面的标题信息,以绘制面包屑和标题。此时基类在我看来拥有太多信息,我们可能将它分为三部分,基本视图模型中99%页面的最基本和必要信息,然后是列表模型和模型对于包含该场景的特定数据并从基础场景继承的表单。

最后,我们为每个实体实现一个视图模型来处理特定信息。

答案 9 :(得分: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网站 项目)

  • DevJet.Web.App.Dictionary(a 单独的类库项目)

    在这个项目中,我做了一些文件夹,如: DAL, BLL, BO, VM(视图模型的文件夹)

答案 10 :(得分: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;
}
}