从 this question 来看,让控制器创建一个 ViewModel 来更准确地反映视图试图显示的模型似乎是有意义的,但我对一些约定感到好奇(我是 MVC 模式的新手,如果它还不是很明显的话)。
基本上,我有以下问题:
我通常喜欢有一个类/文件。如果创建 ViewModel 只是为了将数据从控制器传递到视图,这对它有意义吗?如果 ViewModel 确实属于它自己的文件,并且您使用目录/项目结构来保持独立,那么 ViewModel 文件属于哪里?在控制器目录中?
目前基本上就是这样。我可能还有一些问题要问,但这在过去一个小时左右一直困扰着我,我似乎可以在其他地方找到一致的指导。
编辑:查看 CodePlex 上的示例 NerdDinner app,看起来 ViewModel 是 Controllers 的一部分,但它们不在自己的文件中仍然让我感到不舒服。
我为每个视图创建了我称之为“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,以便允许我在任何视图中引用它们,而无需在每个视图上添加显式导入语句。只是让它更干净一点。
按类别(控制器、视图模型、过滤器等)分离类是无稽之谈。
如果您想为网站的 Home 部分 (/) 编写代码,则创建一个名为 Home 的文件夹,并将 HomeController、IndexViewModel、AboutViewModel 等以及 Home 操作使用的所有相关类放在那里。
如果您有共享类,例如 ApplicationController,您可以将它放在项目的根目录中。
为什么要将相关的东西(HomeController、IndexViewModel)分开,而把完全没有关系的东西(HomeController、AccountController)放在一起?
我写了一篇关于这个主题的blog post。
我将应用程序类保存在名为“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();
}
我认为这非常有效,它可以让你的视图保持整洁,让你的控制器保持精简。
ViewModel 类用于将由类实例表示的多条数据封装到一个易于管理的对象中,您可以将其传递给您的视图。
将 ViewModel 类放在自己的文件中,在自己的目录中是有意义的。在我的项目中,我有一个名为 ViewModels 的 Models 文件夹的子文件夹。这就是我的 ViewModel(例如 ProductViewModel.cs
)所在的位置。
没有保存模型的好地方。如果项目很大并且有很多 ViewModel(数据传输对象),您可以将它们保存在单独的组件中。您也可以将它们保存在站点项目的单独文件夹中。例如,在 Oxite 中,它们被放置在 Oxite 项目中,该项目也包含许多不同的类。 Oxite 中的控制器被移动到单独的项目中,视图也在单独的项目中。
在 CodeCampServer 中,ViewModel 被命名为 *Form,它们被放置在 UI 项目中的 Models 文件夹中。
在 MvcPress 项目中它们被放置在 Data 项目中,它还包含与数据库一起使用的所有代码等等(但我不推荐这种方法,这只是一个示例)
所以你可以看到有很多观点。我通常将我的 ViewModel(DTO 对象)保存在站点项目中。但是当我有超过 10 个模型时,我更喜欢将它们移动到单独的装配中。通常在这种情况下,我也会将控制器移动到单独的程序集。
另一个问题是如何轻松地将所有数据从模型映射到您的 ViewModel。我建议看看 AutoMapper 库。我非常喜欢它,它为我做了所有肮脏的工作。
我也建议看看 SharpArchitecture 项目。它为项目提供了非常好的架构,并且包含许多很酷的框架和指南以及很棒的社区。
这是我的最佳实践中的代码片段:
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 都放在 Models 文件夹中(我们所有的业务逻辑都在一个单独的 ServiceLayer 项目中)
就个人而言,我建议如果 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 ,如果在从 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;
}
}
不定期副业成功案例分享