视图模型/控制器架构.这是最终的解决方案吗?
本文关键字:解决方案 模型 控制器 视图 | 更新日期: 2023-09-27 18:15:17
我使用ASP。. NET MVC学习了几年,构建复杂的视图模型总是很困难。
目前我有一个显示会议列表的视图,我有问题:
-
每个会议都有雇主和地点。为了解决这个问题,我只是把模型弄平了。
-
过滤器表单有两个字段:日期和位置。我想提交过滤器值作为一个类,所以我可以使用流畅验证。
-
过滤器的位置列表用数据库数据填充。
-
会议列表使用几个参数,即:pageSize和pageNumber。
-
模型应该包括页面标题,描述和其他项目的视图…或者我应该用其他方式传递这些东西吗?
我一直在尝试遵循肥胖模型和精益控制器的想法。
所以我有以下控制器索引操作(HttpGet和HttpPost):
(注意:Dispatcher用于发送和接收包含dto的消息)
public partial class MeetingController : BaseController {
public MeetingController(IDispatcher dispatcher) : base(dispatcher) {}
[HttpGet]
public virtual ActionResult Index(Int32 pageNumber = 1) {
MeetingIndexModel model = new MeetingIndexModelHandler(_dispatcher).Create();
return View(model);
} // Index
[HttpPost]
public virtual ActionResult Index(MeetingIndexModel.Filter filter, Int32 p = 1) {
if (ModelState.IsValid) {
MeetingIndexModel model = new MeetingIndexModelHandler(_dispatcher).Update();
return View(Views.Index, model);
} else {
MeetingIndexModel model = new MeetingIndexModelHandler(_dispatcher).Create();
return View(Views.Index, model);
}
} // Index
所以这个模型有点复杂因为所有的需要所以我想:
public class MeetingIndexModel {
public IPagedList<ViewEntity> Entities { get; set; }
public ViewFilter Filter { get; set; }
public ViewHelper Helper { get; set; }
public class ViewEntity {
public Int32 Id { get; set; }
public String EmployeeId { get; set; }
public String EmployeeName { get; set; }
public String LocationName { get; set; }
public DateTime Date { get; set; }
}
public class ViewFilter {
public String Location { get; set; }
public String Stamp { get; set; }
}
public class ViewHelper {
public Page Page { get; set; }
public SelectList Locations { get; set; }
}
}
实体是使用AutoMapper从DTO平面化的会议;
Filter是用来过滤会议并在dispatcher中传递的参数。
Helper包含SelectLists和其他参数…
然后我有一个MeetingIndexHandler创建,更新甚至提交视图模型。例如,在创建或更新会议时发生提交。
public interface IViewModelHandler<T> {
T Create();
T Update(T model);
void Submit(T model);
}
当然,对于MeetingIndexModel来说,有一个Submit方法是没有意义的。
首先,在这个例子中,Submit方法没有意义。
最后是
public class MeetingIndexModelHandler : IViewModelHandler<MeetingIndexModel> {
private IDispatcher _dispatcher;
public ScheduleIndexModelHandler(IDispatcher dispatcher) {
_dispatcher = dispatcher;
}
public ScheduleIndexModel Create() {
IList<MeetingDTO> meetings = _dispatcher.GetMeetings(DateTime.UtcNow, pageSize, pageNumber);
// Map MeetingDTO to MeetingIndexModel.ViewEntity
// Call GetLocations to get locations from database into Helper.Locations
} // Create
private SelectList GetLocations(String currentLocation) {
}
}
我发现这种方法从控制器中删除了大量代码…
但有几个问题,我正在寻求帮助:
提交动作并不总是有意义的
当我创建一个模型,我可能需要一个参数,如pageSize和pageNumber…或Id,或…
理论上我会将这些参数作为参数添加到Create方法中。但是每个情况都是一个情况,所以我不能使用IViewModelHandler。
然后是缺少的数据,如页面标题,描述等…如何将其传递给视图?添加另一个子类到ViewModel?
这种方法是否符合过度工程的要求?你觉得呢?
如果是,您还使用了哪些其他方法来解决这个问题?
UPDATE 1 -我已经使用了一个服务层:dispacther
请注意,我已经有一个服务层(IDispatcher),我用它来从数据库中获取数据等等…例如,我这样使用它:
FindMeetingsByDataQuery query = new FindMeetingsByDataQuery(DateTime.UtcNow);
FindMeetingsByDataReply reply = _dispacther.Send<FindMeetingsByDataReply>(query);
// Map reply.Models to my View Models
现在清楚了吗?
所以我需要的是一种方法来移动这个逻辑(与服务层通信)来构建视图模型远离控制器。
UPDATE 2 -只使用ViewModel而不使用ViewModelHandler的方法
另一种方法是在ViewModel中填充ViewModel的所有逻辑,而不是使用ViewModel Handler。在Controller中输入:
public virtual ActionResult Index(Int32 pageNumber = 1) {
MeetingIndexViewModel model = new MeetingIndexViewModel(_dispatcher);
model.Create(pageNumber, 20, DateTime.UtcNow);
return View(model);
} // Index
和MeetingIndexViewModel是:
public class MeetingIndexViewModel : ViewModel {
private IDispatcher _dispatcher;
public IPageList<EntityModel> Entities { get; set; }
public FilterModel Filter { get; set; }
public HelperModel Helper { get; set; }
public MeetingIndexViewModel(IDispatcher dispatcher) {
_dispatcher = dispatcher;
}
public void Create(Int32 pageNumber, Int32 pageSize, DateTime date) {
FindMeetingsByDateQuery query = new FindMeetingsByDateQuery(date, pageNumber, pageSize);
FindMeetingByDateReply reply = _dispatcher.Send<FindMeetingByDateReply>(query);
IEnumerable<MeetingIndexViewModel.EntityModel> entities = Mapper.Map<IEnumerable<FindMeetingByDateReply.MeetingModel>, IEnumerable<MeetingIndexViewModel.EntityModel>>(reply.Meetings);
Entities = new PagedList(entities);
Filter = new FilterModel { Date = date.ToString() };
Helper = new HelperModel {
PageSize = pageSize,
PageNumber = pageNumber,
Locations = _GetLocations(x.Filter.Location)
};
} // Create
public void Submit() {
// In cases where a ViewModel must be sent to the business layer for saving than it is done here. Otherwise this method is removed.
}
private SelectList _GetLocations(Int32? selected) {
GetLocationsQuery query = new GetLocationsQuery();
GetLocationsReply reply = _dispatcher.Send<GetLocationsReply>(query);
return new SelectList(reply.Locations.Select(x => new { Id = x.Id, Name = x.Name }).ToList(), "Id", "Name", selected);
} // _GetLocations
public class EntityModel {
public Int32 Id { get; set; }
public String EmployeeId { get; set; }
public String EmployeeName { get; set; }
public String LocationName { get; set; }
public DateTime Date { get; set; }
} // EntityModel
public class FilterModel {
public Int32? Location { get; set; }
public String Date { get; set; }
} // FilterModel
public class HelperModel {
public Int32 PageSize { get; set; }
public Int32 PageSize { get; set; }
public SelectList Locations { get; set; }
} // HelperModel
}
因此,这样我就从控制器中删除了构建视图模型的所有逻辑,并将其传递给视图模型本身。这可以接受吗?现在它不仅仅是一个POCO。
UPDATE 3 -使用ViewModel和自定义ViewModelHandler的方法
public class MeetingIndexViewModelHandler {
public MeetingIndexViewModel Create(Int32 pageNumber, Int32 pageSize, DateTime date) {
// Code to create the ViewModel
}
// Code to update the ViewModel
// Code to submit the ViewModel
}
我相信从一个接口有ViewModelHandler使一切困难,因为每个视图模型是一个视图模型。
UPDATE 4 -将代码留在控制器
另一种方法是,如在3中使ViewModel只是一个POCO,而不是有一个处理程序,只是在控制器中留下代码。一般来说,代码不多,但有时它可以…
注意:根据你的经验和我发表的文章,你认为一个好的方法是什么?
我推荐MVVM(模型-视图-视图模型)设计模式。MVVM所做的是在模型之上添加新的包装器VM,这允许您保持模型(DTO)干净,但您可以添加额外的属性,如标题,描述,…这将允许你保持你的模型干净,他们可以在多个虚拟机之间共享。
我会保持所有的动作在VM,并有模型作为DTO。大多数情况下,每个动作都有自己的VM,但大多数情况下,同一控制器的每个VM将共享相同的Model。
只是一个建议,如何一个方法,你保持相同的接口,但增加一个参数的Create
方法,以ViewModelArgs
基类:
public interface IViewModelHandler<T> {
T Create(ViewModelArgs args);
T Update(T model);
void Submit(T model);
}
然后你可以使args嵌套类在你的ViewModel
:
public class SomeViewModel
{
public class SomeViewModelArgs : ViewModelArgs
{
public SomeViewModelArgs(string value) { // blah }
}
}
并像这样调用:
SomeViewModel model = new SomeViewModelHandler(_dispatcher).Create(new SomeViewModel.SomeViewModelArgs("someValue"));
试着想到这里的缺点,可能是一些问题时嘲弄/测试?这值得考虑,但这意味着你可以保留你的接口
编辑
仍然,不确定这给了你什么,你不能实现只是把一个构造函数在ViewModel..VM不能决定它需要从调度程序获得什么吗?
这似乎是一个没有真正关注它自己的层
。它有什么:
[HttpGet]
public virtual ActionResult Index(Int32 pageNumber = 1)
{
MeetingIndexModel model = new MeetingIndexModel();
model.GetData();
return View(model);
}
或者如果依赖注入(如果你不想在构造函数中做与数据相关的工作,你可以让模型绑定器调用一个'after-resolve'方法——你也可以在那里更好地处理错误):
[HttpGet]
public virtual ActionResult Index(Int32 pageNumber = 1, MeetingIndexModel model)
{
return View(model);
}
只是不确定图层给你什么…你能解释一下它对你有什么好处吗?也许我错过了你的意图
- 你的视图模型看起来很好
- 我建议创建一个基类的页面标题/描述 我不喜欢IViewModelHandler方法。我觉得太普通了。我更喜欢更具体的服务层,具有明确的和特定于域的方法名称
还可以考虑使用依赖注入,使你的代码更可测试
public interface IScheduleService { void ScheduleMeeting(MeetingModel meeting); MeetingModel GetMeeting(int meetingId); }
我个人只会使用服务层而不使用处理程序。如果你只是为网站使用服务层,那么你可以直接从它返回视图模型。你的代码会更简单,更易读,如果需要的话,你还可以添加更多的层。这样你就可以直接将数据库实体映射到你的视图模型。