ChatGPT解决这个技术问题 Extra ChatGPT

ViewModel 最佳实践

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

基本上,我有以下问题:

我通常喜欢有一个类/文件。如果创建 ViewModel 只是为了将数据从控制器传递到视图,这对它有意义吗?如果 ViewModel 确实属于它自己的文件,并且您使用目录/项目结构来保持独立,那么 ViewModel 文件属于哪里?在控制器目录中?

目前基本上就是这样。我可能还有一些问题要问,但这在过去一个小时左右一直困扰着我,我似乎可以在其他地方找到一致的指导。

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

我不会将 NerdDinner 称为“最佳实践”示例。你的直觉很适合你。 :)

D
DOK

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

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

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

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

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


如果您想从局部视图 POST 并返回整个视图(在模型错误的情况下)怎么办?在局部视图中,您无权访问父模型。
@Cosmo:然后 POST 到一个可以在模型错误的情况下返回整个视图的操作。在服务器端,您有足够的资源来重新创建父模型。
登录 [POST] 和登录 [GET] 操作怎么样?使用不同的视图模型?
通常,登录 [GET] 不会调用 ViewModel,因为不需要加载任何数据。
很好的建议。模型/VM属性的数据访问、处理和设置应该去哪里?在我的例子中,我们将有一些来自本地 CMS 数据库的数据和一些来自 Web 服务的数据,这些数据需要在模型上设置之前进行处理/操作。将所有这些都放在控制器中会变得非常混乱。
M
Max Toro

按类别(控制器、视图模型、过滤器等)分离类是无稽之谈。

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

如果您有共享类,例如 ApplicationController,您可以将它放在项目的根目录中。

为什么要将相关的东西(HomeController、IndexViewModel)分开,而把完全没有关系的东西(HomeController、AccountController)放在一起?

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


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

我将应用程序类保存在名为“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;
    }

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

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

我认为这非常有效,它可以让你的视图保持整洁,让你的控制器保持精简。


J
JMS

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

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


z
zihotki

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


O
Omu

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

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

B
Beep beep

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


P
Preet Sangha

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

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


M
Marc Climent

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

根据经验,我们尝试将大部分 ViewData["..."] 内容移动并避免到 ViewModel,因此我们避免了强制转换和魔术字符串,这是一件好事。

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

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


C
C.T.

控制器中的代码:

    [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类的结构吗?
A
Ajay Kelkar

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

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

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅