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

发布于 2024-10-16 01:41:34 字数 402 浏览 3 评论 0原文

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

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

ThingiesByStatusViewModel
-StatusName
-StatusThingiesCount

并将其与 AutoMapper 之类的东西挂钩。

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

So lets say I have two models:
Thingy and Status. Thingy has a Status, and Status has many Thingies. It's typical "Object and Object type relationship".

I have a view where I just want the number of thingies in each status. Or basically a list of Status.Name and Status.Thingies.Count. I could do exactly this, but is the "right" thing to do to create a view model in the form:

ThingiesByStatusViewModel
-StatusName
-StatusThingiesCount

and hook it up with something like AutoMapper.

For such a trivial example, it probably doesn't make much of a difference, but it would help me understand better the proper 'separation of concerns'.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

失退 2024-10-23 01:41:34

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

这是一个反问句吗?

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

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

然后您的控制器将返回 IEnumerable。然后,在您的视图中,您可以简单地使用显示模板:

@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 实例。

映射定义可能如下所示:

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

Should I use a viewmodel here?

Is this a rhetorical question?

Your view model would look exactly as you propose and it is perfectly adapted to what you are trying to display here:

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

and then your controller would return an IEnumerable<ThingiesByStatusViewModel>. Then in your view you could simply use a display template:

@Html.DisplayForModel()

and the corresponding display template (~/Views/Shared/DisplayTemplates/ThingiesByStatusViewModel.cshtml):

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

Now let's look at the mapping layer. Suppose that we have the following domain:

public class Thingy
{ }

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

and we have an instance of IEnumerable<Status>.

The mapping definition could look like this:

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

and finally the controller action would simply be:

public ActionResult Foo()
{
    IEnumerable<Status> statuses = _repository.GetStatuses();
    IEnumerable<ThingiesByStatusViewModel> statusesVM = Mapper.Map<IEnumerable<Status>, IEnumerable<ThingiesByStatusViewModel>>(statuses);
    return View(statusesVM);
}
二智少女 2024-10-23 01:41:34

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

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

I, personally, don't like to send non-trivial types to the view because then the person designing the view might feel obligated to start stuffing business logic into the view, that that's bad news.

In your scenario, I'd add a StatusName property to your view model and enjoy success.

任谁 2024-10-23 01:41:34

是的,你应该使用VM。

我猜,你有时间做一些实验,所以尽量用正确的方式来做。下次,你会做得更快。

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

另外,将来你的应用程序将会增长,你永远不知道什么时候需要扩展它。因此,以正确的方式实施它可以为您提供良好的未来可维护性。

Yes, you should use VM.

Guess, You have time for some experiments, so try to do it in a proper way. Next time, you will do that much quicker.

If current example is trivial - then it will be easier to get practice session.

Also, In future your application will grow up and you never know when you need to extend it. So implementing it in proper way provide you a good maintainability for future.

煮茶煮酒煮时光 2024-10-23 01:41:34

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

想一想:

  1. ViewModel 的用途是作为将数据发送到视图的容器。

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

  3. 为了提供这种形状的数据,我使用了“Service”类。 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);
        }
    }
}

服务:

服务类如下所示:

//注意以下属性: 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();
    }
}

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

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

在我看来,这样做的原因很大程度上是为了提供一种编写代码的结构化方式。在创建相应的视图之前,您可以在一个早上将整个事情写下来。

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

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

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

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

要点:

上面的要点是强调与总体工作相比,编写 ViewModel 所花费的精力是多么少。

The answer is yes. Of course. Why not? It will only involve about 1% of your code!

Think about it:

  1. The purpose of a ViewModel is as a container to send data to the view.

  2. As such, it can "shape" the data sent to the view, that may not correspond to your Domain Model. EG, if Thingies has 50 properties (or columns in a table...), you may only need 3 of those properties.

  3. To provide this shaped data, I use a "Service" class. EG, StatusService (tied down by an interface to allow DI, eg, IStatusService). So the Service class gets instances of your repositories, provides methods to use in your controllers and special read only properties that build your ViewModels for packing the data for the views.

Using this way of doing things, you can easily see that the effort that goes into writing a ViewModel is risible. In terms of lines of code, probably 1 percent.

Want proof?

Look at the following:

A typical Controller would be:

The Controller:

//NOTE THE USE OF: 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);
        }
    }
}

The Service:

The Service class would look like:

// NOTE THE FOLLOWING PROPERTY: 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
    }
}

The Inteface:

And the interface would look like:

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

The ViewModel class:

And, finally, the ViewModel class itself:

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

By comparison with all the rest, it is nothing.

So why not do it?

Other bits:

The Generic Repository:

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

NOTE: This is what I am working on now, so it is a work in progress and alot simpler than the final thing will be, but this is certainly the first and second iteration, and it gives an idea of how structured your work can be.

Extra complexity will creep in when the client wants new features in the views etc. But even so, this is a framework you can build on and test the changes very easily.

The reason for doing things this way, imo, is largely to provide a structured way of writing your code. You can have the whole thing written up in a morning, before you have even created the corresponding Views.

Ie, it all goes very quickly and you know exactly what you are trying to do.

Once you have done that, you create your Views and see what happens...

Joking appart, the beauty is that by the time you get to the views, you know what you are doing, you know your data, its shape, and the view design just flows. You then add the extras that the Views demands and hey presto, job done.

Of course, the other reason is testing. But even here, you benefit from a highly structured approach: your tests will follow a very distinct pattern too. So easy to write.

The whole point:

The whole point of the above is to highlight how little effort goes into writing the ViewModel, by comparison with the overall effort.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文