首页 文章

ViewModel最佳实践

提问于
浏览
234

this question开始,看起来让控制器创建一个更准确地反映视图试图显示的模型的 ViewModel 是有意义的,但是如果它还不是很明显的话,那我就是MVC模式的新手 .

基本上,我有以下问题:

  • 我通常喜欢有一个类/文件 . 如果只是创建它以将数据从控制器传递到视图,这对 ViewModel 有意义吗?

  • 如果 ViewModel 确实属于自己的文件,并且您正在使用目录/项目结构来保持分离,那么 ViewModel 文件属于哪里?在 Controllers 目录中?

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

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

11 回答

  • 120

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

  • 14

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

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

  • 2

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

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

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

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

  • 6

    我为每个视图创建了我称之为“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语句 . 只是让它更清洁一点 .

  • 0

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

    如果要为网站的Home部分编写代码(/),则创建一个名为Home的文件夹,并将HomeController,IndexViewModel,AboutViewModel等以及Home操作使用的所有相关类放在那里 .

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

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


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

  • 0

    我将我的应用程序类保存在一个名为"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();
        }
    

    我认为这很有效,它可以保持你的视野整洁,你的控制器很瘦 .

  • 5

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

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

  • 208

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

  • 4

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

    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");
            }
    }
    
  • 12

    控制器中的代码:

    [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(视图模型的文件夹)

  • 20

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

    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;
    }
    }
    

相关问题