视图模型/控制器架构.这是最终的解决方案吗?

本文关键字:解决方案 模型 控制器 视图 | 更新日期: 2023-09-27 18:15:17

我使用ASP。. NET MVC学习了几年,构建复杂的视图模型总是很困难。

目前我有一个显示会议列表的视图,我有问题:

  1. 每个会议都有雇主和地点。为了解决这个问题,我只是把模型弄平了。

  2. 过滤器表单有两个字段:日期和位置。我想提交过滤器值作为一个类,所以我可以使用流畅验证。

  3. 过滤器的位置列表用数据库数据填充。

  4. 会议列表使用几个参数,即:pageSize和pageNumber。

  5. 模型应该包括页面标题,描述和其他项目的视图…或者我应该用其他方式传递这些东西吗?

我一直在尝试遵循肥胖模型和精益控制器的想法。

所以我有以下控制器索引操作(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) {
  }

}

我发现这种方法从控制器中删除了大量代码…

但有几个问题,我正在寻求帮助:

  1. 提交动作并不总是有意义的

  2. 当我创建一个模型,我可能需要一个参数,如pageSize和pageNumber…或Id,或…

    理论上我会将这些参数作为参数添加到Create方法中。但是每个情况都是一个情况,所以我不能使用IViewModelHandler。

  3. 然后是缺少的数据,如页面标题,描述等…如何将其传递给视图?添加另一个子类到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);
}

只是不确定图层给你什么…你能解释一下它对你有什么好处吗?也许我错过了你的意图

  1. 你的视图模型看起来很好
  2. 我建议创建一个基类的页面标题/描述
  3. 我不喜欢IViewModelHandler方法。我觉得太普通了。我更喜欢更具体的服务层,具有明确的和特定于域的方法名称
  4. 还可以考虑使用依赖注入,使你的代码更可测试

       
     public interface IScheduleService
     {
        void ScheduleMeeting(MeetingModel meeting);     
        MeetingModel GetMeeting(int meetingId);
     }
    

我个人只会使用服务层而不使用处理程序。如果你只是为网站使用服务层,那么你可以直接从它返回视图模型。你的代码会更简单,更易读,如果需要的话,你还可以添加更多的层。这样你就可以直接将数据库实体映射到你的视图模型。