我应该在这里使用viewmodel吗?

时间:2011-02-04 15:38:34

标签: asp.net-mvc viewmodel

所以我想说我有两个型号: Thingy和状态。 ThingyStatus,状态有很多Thingies。它是典型的“对象和对象类型关系”。

我有一个观点,我只想要每个状态中的东西数量。或者基本上是Status.Name和Status.Thingies.Count的列表。我可以做到这一点,但是“正确”要做的事情是在表单中创建一个视图模型:

ThingiesByStatusViewModel
-StatusName
-StatusThingiesCount

并将其与AutoMapper等内容联系起来。

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

4 个答案:

答案 0 :(得分:11)

  

我应该在这里使用viewmodel吗?

这是一个修辞问题吗?

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

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<Status>的实例。

映射定义可能如下所示:

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);
}

答案 1 :(得分:3)

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

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

答案 2 :(得分:1)

是的,你应该使用VM。

猜猜,你有时间做一些实验,所以尽量以适当的方式做。下次,你会更快地做到这一点。

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

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

答案 3 :(得分:1)

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

想一想:

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

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

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

  4. 使用这种方式,您可以很容易地看到编写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);
            }
        }
    }
    

    服务:

    服务类看起来像:

    //注意以下属性: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,主要是为了提供一种编写代码的结构化方式。在创建相应的视图之前,您可以在早上写完整个内容。

    也就是说,一切都很快,你确切地知道你要做什么。

    完成后,您可以创建视图,看看会发生什么......

    开玩笑,美丽的是,当你到达观点时,你知道你在做什么,你知道你的数据,它的形状和视图设计只是流动。然后,您添加了视图所需的附加内容,并且完成了工作。

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

    重点:

    上述内容的重点是强调与整体工作相比,编写ViewModel的工作量有多少。