ViewModel最佳实践

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

基本上,我有以下几个问题:

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

现在基本上就是这样了。我可能会有更多的问题,但这已经困扰了我一个小时左右,我似乎可以在其他地方找到一致的指导。

<强>编辑: 查看CodePlex上的NerdDinner应用示例,它看起来像ViewModels是控制器的一部分,但它仍然让我不舒服,因为它们不在自己的文件中

84625 次浏览

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

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

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

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

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

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

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

我认为这工作得很好,它保持你的视图整洁,你的控制器瘦。

我为每个视图创建了所谓的“ViewModel”。我把它们放在MVC Web项目中一个名为ViewModels的文件夹中。我根据它们所代表的控制器和动作(或视图)来命名它们。所以如果我需要传递数据到会员控制器上的注册视图,我创建了一个MembershipSignUpViewModel.cs类,并把它放在ViewModels文件夹中。

然后我添加必要的属性和方法,以促进数据从控制器传输到视图。我使用Automapper从我的ViewModel到域模型,如果需要的话再返回。

这也适用于包含其他viewmodel类型属性的复合viewmodel。例如,如果你在成员控制器的索引页上有5个小部件,并且你为每个分部视图创建了一个ViewModel——你如何将数据从index动作传递给分部?你在MembershipIndexViewModel中添加一个MyPartialViewModel类型的属性,当呈现部分时,你将在Model.MyPartialViewModel中传递。

这样做允许您调整部分ViewModel属性,而不必更改Index视图。它仍然只是传入模型。MyPartialViewModel所以当你所做的只是向partial ViewModel添加一个属性时,你就不太可能需要通过整个partial链来修复一些东西。

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

在我们的例子中,我们将模型和控制器放在一个独立于视图的项目中。

根据经验,我们已经尝试移动和避免大多数ViewData[”…]的东西,因此我们避免了类型转换和魔术字符串,这是一件好事。

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

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

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

按类别划分类(控制器、视图模型、过滤器等)是毫无意义的。

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

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

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


我写了一个博客关于这个主题。

控制器中的代码:

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

项目:

    <李> < p > DevJet。Web (ASP。NET MVC web 李项目)< / p > < / > <李> < p > DevJet.Web.App。字典(a 单独的类库项目)

    在这个项目中,我做了一些文件夹,比如: 木豆, BLL, 薄 VM(视图模型文件夹)

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

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

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

class ViewModelBase
{
public bool HasError {get;set;}
public string ErrorMessage {get;set;}
public List<string> UserRoles{get;set;}
}
在基控制器类中有一个类似PopulateViewModelBase()的方法,该方法将填充上下文数据和用户角色。 HasError和ErrorMessage,如果从service/db中提取数据时出现异常,请设置这些属性。在视图上绑定这些属性以显示错误。 用户角色可用于根据角色在视图中显示隐藏部分。< / p >

要在不同的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;
}
}