ViewModel最佳实践
从这个问题 ,看起来有一个控制器创建一个ViewModel ,更准确地反映了视图试图显示的模型是有道理的,但我很好奇的一些约定(我是新来的MVC模式,如果它不是很明显)。
基本上,我有以下问题:
- 我通常喜欢有一个类/文件。 如果只创建一个ViewModel来将数据从一个控制器传递到一个视图,这是否有意义?
- 如果一个ViewModel属于它自己的文件,并且你正在使用目录/项目结构来保持独立, ViewModel文件属于哪里? 在控制器目录中?
这基本上是现在。 我可能还会提出几个问题,但是在过去的一个多小时里,这一直困扰着我,而且我似乎在别处找到了一致的指导。
编辑:看CodePlex上的示例NerdDinner应用程序 ,它看起来像ViewModels是控制器的一部分,但它仍然让我不舒服,他们不在自己的文件。
我为每个视图创建了我所谓的“ViewModel”。 我把它们放在我的MVC Web项目中名为ViewModels的文件夹中。 我把它们命名为它们所代表的控制者和行动(或观点)。 所以,如果我需要传递数据到成员控制器上的SignUp视图,我创建一个MembershipSignUpViewModel.cs类,并将其放在ViewModels文件夹中。
然后,我添加必要的属性和方法,以便于从控制器到视图的数据传输。 我使用Automapper从我的ViewModel获取到域模型,如果需要的话再次返回。
这也适用于包含其他ViewModel类型的属性的组合ViewModels。 例如,如果您在成员控制器的索引页上有5个小部件,并且您为每个部分视图创建了一个ViewModel,那么如何将索引操作中的数据传递给部分? 您将一个属性添加到类型为MyPartialViewModel的MembershipIndexViewModel,并在呈现部分时,您将在Model.MyPartialViewModel中传递。
这样做可以让您调整部分ViewModel属性,而不必更改索引视图。 它仍然只是在Model.MyPartialViewModel中传递,所以当你所做的只是将一个属性添加到部分ViewModel中时,你将不得不通过整个partials链来修复某些东西。
我还将命名空间“MyProject.Web.ViewModels”添加到web.config中,以便我可以在任何视图中引用它们,而不必在每个视图中添加显式导入语句。 只是使它更清洁。
按类别分类(控制器,视图模型,过滤器等)是无稽之谈。
如果要为网站的主页(/)编写代码,请创建一个名为Home的文件夹,并在其中放置HomeController,IndexViewModel,AboutViewModel等以及Home操作使用的所有相关类。
如果你有共享类,比如一个ApplicationController,你可以把它放在你的项目的根目录下。
为什么要分开相关的东西(HomeController,IndexViewModel)并且把所有东西放在一起(HomeController,AccountController)呢?
我写了一篇关于这个话题的博客文章 。
我将我的应用程序类保存在一个名为“Core”(或一个单独的类库)的子文件夹中,并使用与KIGG示例应用程序相同的方法,但只做了一些细微的更改,使我的应用程序更加干爽。
我在/ Core / ViewData /中创建了一个BaseViewData类,用于存储常见的站点范围属性。
在此之后,我也创建了所有的ViewView类,然后从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(); }
我认为这个效果非常好,它保持你的意见整洁,你的控制器瘦。
ViewModel类可以将由实例类表示的多个数据封装到一个易于管理的对象中,您可以将其传递给View。
将ViewModel类放在自己的文件中,在自己的目录中是有意义的。 在我的项目中,我有一个名为ViewModels的Models文件夹的子文件夹。 这就是我的ViewModel(例如ProductViewModel.cs
)存在的地方。
如果项目很大,并且有很多ViewModel(数据传输对象),则可以将它们保存在单独的程序集中。 您也可以将它们保存在网站项目的单独文件夹中。 例如,在Oxite中,它们被放置在包含许多不同类的Oxite项目中。 Oxite中的控制器被移动到独立的项目中,视图也在单独的项目中。
在CodeCampServer中, ViewModel被命名为* Form,并被放置在Models文件夹中的UI项目中。
在MvcPress项目中,它们被放置在Data项目中,该项目也包含了所有可以与数据库一起工作的代码(但我并不推荐这种方法,只是为了举例)
所以你可以看到有很多观点。 我通常保持我的ViewModels(DTO对象)在网站项目。 但是,当我有超过10个模型,我宁愿将它们移动到单独的程序集。 通常在这种情况下,我正在移动控制器来分离组件。
另一个问题是如何轻松地将模型中的所有数据映射到ViewModel。 我建议看看AutoMapper库。 我非常喜欢它,它为我做了所有肮脏的工作。
我也建议看看SharpArchitecture项目。 它为项目提供了非常好的架构,并且包含了很多很酷的框架和指导以及优秀的社区。
我们将所有ViewModel都放在Models文件夹中(我们所有的业务逻辑都在一个单独的ServiceLayer项目中)
以下是我的最佳做法中的代码段:
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"); } }
我个人建议,如果ViewModel是什么,但微不足道的,然后使用一个单独的类。
如果你有多个视图模型,那么我建议把它分成至少一个目录。 如果稍后共享视图模型,则目录中隐含的名称空间使移动到新的程序集更容易。
在我们的例子中,我们将模型和控制器放在一个独立于视图的项目中。
作为一个经验法则,我们试图移动和避免大部分ViewData [“…”]的东西到ViewModel,因此我们避免了铸件和魔法字符串,这是一件好事。
ViewModel还拥有一些常见的属性,如页面的分页信息或页面的标题信息以绘制面包屑和标题。 在这个时候,我认为基类包含了太多的信息,我们可以把它分成三部分,一个基本视图模型中99%的页面的最基本和必要的信息,然后是一个列表和一个模型为那些场景保存特定数据的表单,并从基本的继承。
最后,我们为每个实体实现一个视图模型来处理具体的信息。
代码在控制器中:
[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(查看模型的文件夹)
创建一个视图模型的基类,通常需要像操作结果和上下文数据一样的属性,还可以将当前用户数据和角色
class ViewModelBase { public bool HasError {get;set;} public string ErrorMessage {get;set;} public List<string> UserRoles{get;set;} }
在基类控制器类中有一个像PopulateViewModelBase()这样的方法将填充上下文数据和用户角色。 HasError和ErrorMessage,如果在从服务/数据库中提取数据时出现异常,请设置这些属性。 将这些属性绑定在视图上以显示错误。 用户角色可以用来显示基于角色的隐藏部分。
为了在不同的获取动作中填充视图模型,可以通过使基本控制器具有抽象方法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; } }