我使用ASP.NET MVC已有几年了,构建复杂的视图模型总是很困难。
目前我有一个显示会议列表的视图,我遇到了问题:
每次会议都有雇主和地点。为了解决这个问题,我只是将模型弄平了。
过滤器表单有两个字段:日期和位置。 我想将过滤器值作为一个类提交,以便我可以使用Fluent验证。
过滤器的位置列表中填充了数据库数据。
会议列表使用一些参数构建,即: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展平的会议;
过滤器是过滤会议和传入调度程序时使用的参数。
Helper包含SelectLists和其他参数......
然后我有一个MeetingIndexHandler,它可以创建,更新甚至提交视图模型。 例如,在创建或更新会议时会发生提交。
public interface IViewModelHandler<T> {
T Create();
T Update(T model);
void Submit(T model);
}
当然,对于MeetingIndexModel,使用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?
这种方法是否符合过度工程的要求?你觉得怎么样?
如果是,您使用其他方法来解决此问题?
更新1 - 我已经在使用服务层:发送者
请注意,我已经有了一个服务层(IDispatcher),用于从数据库中获取数据等等...例如,我这样使用它:
FindMeetingsByDataQuery query = new FindMeetingsByDataQuery(DateTime.UtcNow);
FindMeetingsByDataReply reply = _dispacther.Send<FindMeetingsByDataReply>(query);
// Map reply.Models to my View Models
现在清楚了吗?
所以我需要的是一种移动这种逻辑(与服务层通信)的方法,以便远离控制器构建视图模型。
更新2 - 仅使用ViewModel而非ViewModelHandler的方法
另一种方法是将所有逻辑填充在ViewModel中,而不是使用ViewModel Handler。所以在控制器中我有:
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
}
所以这样我就从控制器中删除了构建viewmodel的所有逻辑,并将其传递给viewmodel本身。这可以接受吗?现在它不仅仅是一个POCO。
更新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会使一切变得困难,因为每个视图模型都是一个视图模型。
更新4 - 将代码留在控制器中
另一种方法是,如在3中使ViewModel只是一个POCO,而不是让Handler将代码留在Controller中。一般而言,代码并不多,但有时它可能是......
注意: 您如何看待,根据您的经验和我发布的,一个好方法会是什么?
答案 0 :(得分:1)
我会为此推荐MVVM(模型 - 视图 - 视图模型)设计模式。 MVVM的作用是在Model之上添加新的包装器VM,它允许您保持模型(DTO)清洁,但是您可以添加额外的属性,例如Title,Description,...这将允许您保持模型清洁和它们可以在多个VM之间共享。
我会将所有操作保留在VM中,并将模型设置为DTO。大多数情况下,每个操作都有自己的VM,但大多数情况下,同一个控制器的每个VM都会共享相同的模型。
答案 1 :(得分:0)
只是一个建议,如何保持相同的接口,但为Create
基类的ViewModelArgs
方法添加参数:
public interface IViewModelHandler<T> {
T Create(ViewModelArgs args);
T Update(T model);
void Submit(T model);
}
然后,您可以在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不能决定它需要从调度程序获得什么?
它看起来像是一层并不真正关心它自己的
e.g。它有什么结果:
[HttpGet]
public virtual ActionResult Index(Int32 pageNumber = 1)
{
MeetingIndexModel model = new MeetingIndexModel();
model.GetData();
return View(model);
}
或者如果依赖注入(如果你不想在构造函数中执行与数据相关的工作,你可以让modelbinder调用'after-resolve'方法 - 你也可以在那里更好地处理错误):< / p>
[HttpGet]
public virtual ActionResult Index(Int32 pageNumber = 1, MeetingIndexModel model)
{
return View(model);
}
只是不确定图层给你的是什么..你能解释一下你认为它为你做了什么吗?也许我错过了你的意图
答案 2 :(得分:0)
还可以考虑使用依赖注入,这使您的代码更易于测试
public interface IScheduleService
{
void ScheduleMeeting(MeetingModel meeting);
MeetingModel GetMeeting(int meetingId);
}
<强>更新强>
就个人而言,我只会使用没有处理程序的服务层。如果您仅为网站使用服务层,则可以直接从中返回View模型。您的代码将更简单,更易读,如果需要,您可以随时添加更多图层。这样,您可以直接将数据库实体映射到视图模型。