【问题标题】:Should I use a viewmodel here?我应该在这里使用视图模型吗?
【发布时间】:2011-02-04 15:38:34
【问题描述】:

假设我有两个模型: 事物和状态。 Thingy 有一个Status,Status 有很多东西。这是典型的“对象与对象类型关系”。

我有一个视图,我只想要每个状态下的事物数量。或者基本上是 Status.Name 和 Status.Thingies.Count 的列表。我完全可以这样做,但是以“正确”的方式创建表单中的视图模型:

ThingiesByStatusViewModel
-StatusName
-StatusThingiesCount

并将其与 AutoMapper 之类的东西连接起来。

对于这样一个微不足道的例子,它可能没有太大的区别,但它会帮助我更好地理解正确的“关注点分离”。

【问题讨论】:

    标签: asp.net-mvc viewmodel


    【解决方案1】:

    我应该在这里使用视图模型吗?

    这是一个反问句吗?

    您的视图模型看起来与您建议的完全一样,并且完全适合您在此处显示的内容:

    public class ThingiesByStatusViewModel
    {
        public string StatusName { get; set; }
        public int StatusThingiesCount { get; set; }
    }
    

    然后你的控制器会返回一个IEnumerable<ThingiesByStatusViewModel>。然后在您看来,您可以简单地使用显示模板:

    @Html.DisplayForModel()
    

    以及对应的展示模板(~/Views/Shared/DisplayTemplates/ThingiesByStatusViewModel.cshtml):

    @model AppName.Models.ThingiesByStatusViewModel
    <div>
        <span>StatusName: @Model.StatusName</span>
        <span>Number of thingies: @Model.StatusThingiesCount</span>
    </div>
    

    现在让我们看看映射层。假设我们有以下域:

    public class Thingy
    { }
    
    public class Status
    {
        public string StatusName { get; set; }
        public IEnumerable<Thingy> Thingies { get; set; }
    }
    

    我们有一个IEnumerable&lt;Status&gt; 的实例。

    映射定义可能如下所示:

    Mapper
        .CreateMap<Status, ThingiesByStatusViewModel>()
        .ForMember(
            dest => dest.StatusThingiesCount,
            opt => opt.MapFrom(src => src.Thingies.Count())
        );
    

    最后,控制器动作将是:

    public ActionResult Foo()
    {
        IEnumerable<Status> statuses = _repository.GetStatuses();
        IEnumerable<ThingiesByStatusViewModel> statusesVM = Mapper.Map<IEnumerable<Status>, IEnumerable<ThingiesByStatusViewModel>>(statuses);
        return View(statusesVM);
    }
    

    【讨论】:

    • 哇。这是我所希望的最完整的答案。绝对不是反问;我正在努力做“正确”的事情,并拖着我的表单事件同事和我一起踢和尖叫。而且我很难理解一些原因和时间。谢谢!
    【解决方案2】:

    就我个人而言,我不喜欢向视图发送非平凡的类型,因为那样设计视图的人可能会觉得有义务开始将业务逻辑填充到视图中,这是个坏消息。

    在您的场景中,我会在您的视图模型中添加一个 StatusName 属性并享受成功。

    【讨论】:

      【解决方案3】:

      是的,你应该使用虚拟机。

      猜猜,你有时间做一些实验,所以试着以适当的方式去做。下一次,你会做得更快。

      如果当前示例很简单 - 那么练习会更容易。

      此外,将来您的应用程序会不断发展壮大,而您永远不知道何时需要扩展它。因此,以适当的方式实施它可为您提供良好的未来可维护性。

      【讨论】:

        【解决方案4】:

        答案是肯定的。当然。为什么不?它只涉及大约 1% 的代码!

        想一想:

        1. ViewModel 的目的是作为向视图发送数据的容器。

        2. 因此,它可以“塑造”发送到视图的数据,这可能与您的域模型不对应。例如,如果 Thingies 有 50 个属性(或表中的列......),您可能只需要其中 3 个属性。

        3. 为了提供这种形状的数据,我使用了一个“服务”类。 EG,StatusService(由接口绑定以允许 DI,例如 IStatusService)。因此,Service 类获取存储库的实例,提供在控制器中使用的方法以及构建 ViewModel 以打包视图数据的特殊只读属性。

        使用这种做事方式,您可以很容易地看到编写 ViewModel 所付出的努力是可笑的。就代码行数而言,大概是 1%。

        想要证据?

        看下面:

        典型的控制器是:

        控制器:

        //注意使用:service.ViewModel

        namespace ES.eLearningFE.Areas.Admin.Controllers
        {
            public partial class StepEditorController : Controller
            {
                IStepEditorService service;
                public StepEditorController(IStepEditorService service)
                {
                    this.service = service;
                }
        
                [HttpGet]
                public virtual ActionResult List(int IdCourse)
                {
                    service.CourseId = IdCourse;
                    return View(service.Steps());
                }
        
                [HttpGet]
                public virtual ActionResult Edit(int IdCourse, int IdStep)
                {
                    service.CourseId = IdCourse;
                    service.CurrentStepId = IdStep;
                    return View(service.ViewModel);
                }
        
                [HttpPost]
                public virtual ActionResult Edit(CourseStep step)
                {
                    service.CourseId = step.CourseId;
                    service.CurrentStepId = step.CourseStepId; 
                    service.CourseId = step.CourseId;
                    try
                    {
                        UpdateModel(service.CurrentStep);
                        service.Save();
                        return RedirectToAction(Actions.Edit(step.CourseId, step.CourseStepId));
                    }
                    catch
                    {
                        // Refactor notice : empty catch block : Return errors!
                    }
                    return View(service.ViewModel);
                }
        
                [HttpGet]
                public virtual ActionResult New(int IdCourse)
                {
                    service.CourseId = IdCourse;
                    return View(service.ViewModel);
                }
                [HttpPost]
                public virtual ActionResult New(CourseStep step)
                {
                    service.CourseId = step.CourseId;            
                    if (ModelState.IsValid)
                    {
                        service.AddStep(step);
                        try
                        {
                            service.Save();
                            service.CurrentStepId = step.CourseStepId;
                            return View(Views.Edit, service.ViewModel);
                        }
                        catch
                        {
                            // Refactor notice : empty catch block : Return errors!
                        }
                    }
                    return View(service.ViewModel);
                }
            }
        }
        

        服务:

        Service 类如下所示:

        // 注意以下属性:public StepEditorVM ViewModel

        namespace ES.eLearning.Domain.Services.Admin
        {
            public class SqlStepEditorService : IStepEditorService
            {
                DataContext db;
        
                public SqlStepEditorService(DbDataContextFactory contextFactory)
                {
                    db = contextFactory.Make();
                    CoursesRepository = new SqlRepository<Course>(db);
                    StepsRepository = new SqlRepository<CourseStep>(db);
                }
        
                #region IStepEditorService Members
        
                public StepEditorVM ViewModel
                {
                    get
                    {
                        if (CurrentStep != null)
                        {
                            return new StepEditorVM
                            {
                                CurrentStep = this.CurrentStep,
                                Steps = this.Steps()
                            };
                        }
                        else // New Step
                        {
                            return new StepEditorVM
                            {
                                CurrentStep = new CourseStep(),
                                Steps = this.Steps()
                            };
                        }
                    }
                }
        
                public CourseStep CurrentStep
                {
                    get
                    {
                        return FindStep(CurrentStepId, CourseId);
                    }
                }
        
                // Refactor notice : Expose Steps with a CourseId parameter, instead of reading from the CourseId property?
                public List<CourseStep> Steps()
                {
                    if (CourseId == null) throw new ApplicationException("Cannot get Steps [CourseId == null]");
                    return (from cs in StepsRepository.Query where cs.CourseId == CourseId select cs).ToList();
                }
        
                // Refactor notice :  Pattern for dealing with null input parameters
                public int ? CourseId { get; set; }
                public int ? CurrentStepId { get; set; }
        
                public CourseStep FindStep(int ? StepId, int ? CourseId)
                {
                    // Refactor notice :  Pattern for dealing with null input parameters
                    if (CourseId == null) throw new ApplicationException("Cannot Find Step [CourseId == null]");
                    if (CurrentStepId == null) throw new ApplicationException("Cannot Find Step [StepId == null]");
                    try
                    {
                        return (from cs in StepsRepository.Query where ((cs.CourseStepId == StepId) && (cs.CourseId == CourseId)) select cs).First();
                    }
                    catch
                    {
                        return null;
                    }
                }
        
                public void AddStep(CourseStep step)
                {
                    StepsRepository.Add(step);
                }
        
                public void DeleteStep(CourseStep step)
                {
                    StepsRepository.Delete(step);
                }
        
                public void Clear()
                {
                    CurrentStepId = null;
                    CourseId = null;
                }
        
                public void Save()
                {
                    db.SubmitChanges();
                }
        
                #endregion
        
                #region Repositories
        
                private IRepository<Course> CoursesRepository
                {
                    get;
                    set;
                }
        
                private IRepository<CourseStep> StepsRepository
                {
                    get;
                    set;
                }
        
                #endregion
            }
        }
        

        接口:

        界面看起来像:

        namespace ES.eLearning.Domain.Services.Interfaces
        {
            public interface IStepEditorService
            {
                StepEditorVM ViewModel { get; }
        
                CourseStep CurrentStep { get; }
                List<CourseStep> Steps();
                int ? CourseId { get; set; }
                int ? CurrentStepId { get; set; }
                CourseStep FindStep(int ? StepId, int ? CourseId);
                void AddStep(CourseStep step);
                void DeleteStep(CourseStep step);
                void Clear();
                void Save();
            }
        }
        

        ViewModel 类:

        最后是 ViewModel 类本身:

        namespace ES.eLearning.Domain.ViewModels
        {
            public class StepEditorVM
            {
                public CourseStep CurrentStep { get; set; }
                public List<CourseStep> Steps { get; set; }
            }
        }
        

        与所有其他人相比,它什么都不是。

        那么为什么不这样做呢?

        其他位:

        通用存储库:

        namespace ES.eLearning.Domain
        {
            public class SqlRepository<T> : IRepository<T> where T : class
            {
                DataContext db;
                public SqlRepository(DataContext db)
                {
                    this.db = db;
                }
        
                #region IRepository<T> Members
        
                public IQueryable<T> Query
                {
                    get { return db.GetTable<T>(); }
                }
        
                public List<T> FetchAll()
                {
                    return Query.ToList();
                }
        
                public void Add(T entity)
                {
                    db.GetTable<T>().InsertOnSubmit(entity);
                }
        
                public void Delete(T entity)
                {
                    db.GetTable<T>().DeleteOnSubmit(entity);
                }
        
                public void Save()
                {
                    db.SubmitChanges();
                }
        
                #endregion
            }
        }
        

        IRepository:

        namespace Wingspan.Web.Mvc
        {
            public interface IRepository<TEntity> where TEntity : class
            {
                List<TEntity> FetchAll();
                IQueryable<TEntity> Query {get;}
                void Add(TEntity entity);
                void Delete(TEntity entity);
                void Save();
            }
        }
        

        注意:这是我现在正在做的工作,因此它正在进行中,比最终的工作要简单得多,但这肯定是第一次和第二次迭代,它让您了解您的结构如何工作可以。

        当客户想要视图中的新功能等时,额外的复杂性会逐渐增加。但即便如此,这是一个您可以非常轻松地构建和测试更改的框架。

        imo 这样做的原因主要是为了提供一种结构化的方式来编写代码。在创建相应的视图之前,您可以在早上写完所有内容。

        也就是说,一切都进行得非常快,而且您确切地知道自己要做什么。

        一旦你这样做了,你就可以创建你的视图,看看会发生什么......

        开玩笑的 appart,美妙之处在于,当您看到视图时,您就知道自己在做什么,您知道您的数据、它的形状,并且视图设计只是流动的。然后,您添加 Views 所需的额外内容,嘿,工作完成了。

        当然,另一个原因是测试。但即使在这里,您也可以从高度结构化的方法中受益:您的测试也将遵循非常独特的模式。很容易写。

        整点:

        上面的重点是强调与整体工作相比,编写 ViewModel 所付出的努力是多么的少。

        【讨论】:

          猜你喜欢
          • 2014-04-05
          • 1970-01-01
          • 2023-04-03
          • 1970-01-01
          • 1970-01-01
          • 2011-08-09
          • 1970-01-01
          • 1970-01-01
          • 2019-09-05
          相关资源
          最近更新 更多