ViewModel / Controller Architecture。这可能是最终解决方案吗?

时间:2014-07-29 11:05:20

标签: c# asp.net-mvc

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

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

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

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

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

  4. 会议列表使用一些参数构建,即:pageSize和pageNumber。

  5. 模型应包含视图的页面标题,说明和其他项目...... 或者我应该以其他方式传递这些项目吗?

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

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

    }

    我发现这种方法从控制器中删除了很多代码......

    但是我有一些问题需要帮助:

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

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

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

    3. 然后有缺页数据,如页面标题,描述等...... 我如何将其传递给视图?将另一个子类添加到ViewModel?

    4. 这种方法是否符合过度工程的要求?你觉得怎么样?

      如果是,您使用其他方法来解决此问题?

      更新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中。一般而言,代码并不多,但有时它可能是......

      注意: 您如何看待,根据您的经验和我发布的,一个好方法会是什么?

3 个答案:

答案 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

中将args设为嵌套类
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)

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

       
     public interface IScheduleService
     {
        void ScheduleMeeting(MeetingModel meeting);     
        MeetingModel GetMeeting(int meetingId);
     }
    
  6. <强>更新

    就个人而言,我只会使用没有处理程序的服务层。如果您仅为网站使用服务层,则可以直接从中返回View模型。您的代码将更简单,更易读,如果需要,您可以随时添加更多图层。这样,您可以直接将数据库实体映射到视图模型。