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".
所以我想说我有两种模式:Thingy和Status。 Thingy有一个状态,Status有许多Thingies。它是典型的“对象和对象类型关系”。
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:
我有一个观点,我只想要每个状态中的东西数量。或者基本上是Status.Name和Status.Thingies.Count的列表。我可以做到这一点,但是在表单中创建视图模型是“正确”的事情:
ThingiesByStatusViewModel
-StatusName
-StatusThingiesCount
and hook it up with something like AutoMapper.
并使用类似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'.
对于这样一个微不足道的例子,它可能没有多大区别,但它可以帮助我更好地理解正确的“关注点分离”。
4 个解决方案
#1
11
Should I use a viewmodel here?
我应该在这里使用viewmodel吗?
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:
然后你的控制器将返回一个IEnumerable
@Html.DisplayForModel()
and the corresponding display template (~/Views/Shared/DisplayTemplates/ThingiesByStatusViewModel.cshtml
):
和相应的显示模板(〜/ 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>
.
我们有一个IEnumerable
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);
}
#2
3
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.
在您的方案中,我将向您的视图模型添加StatusName属性并享受成功。
#3
1
Yes, you should use VM.
是的,你应该使用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.
此外,将来您的应用程序将会成长,您永远不知道何时需要扩展它。因此,以适当的方式实施它可以为您提供良好的可维护性。
#4
1
The answer is yes. Of course. Why not? It will only involve about 1% of your code!
Think about it:
想一想:
-
The purpose of a ViewModel is as a container to send data to the view.
ViewModel的目的是作为将数据发送到视图的容器。
-
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.
因此,它可以“塑造”发送到视图的数据,这可能与您的域模型不对应。 EG,如果Thingies有50个属性(或表中的列......),您可能只需要其中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.
为了提供这种形状的数据,我使用了“服务”类。 EG,StatusService(由允许DI的接口绑定,例如,IStatusService)。因此,Service类获取存储库的实例,提供在控制器中使用的方法以及构建ViewModel以包装视图数据的特殊只读属性。
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.
使用这种方式,您可以轻松地看到编写ViewModel所付出的努力是可行的。就代码行而言,大概是1%。
Want proof?
Look at the following:
看看以下内容:
A typical Controller would be:
典型的控制器是:
The Controller:
//NOTE THE USE OF: service.ViewModel
//注意使用: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:
Service类看起来像:
// NOTE THE FOLLOWING PROPERTY: public StepEditorVM 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
}
}
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:
最后,ViewModel类本身:
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.
这样做的原因,imo,主要是为了提供一种编写代码的结构化方式。在创建相应的视图之前,您可以在早上写完所有内容。
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.
以上的重点是强调与整体工作相比,编写ViewModel的努力有多少。
#1
11
Should I use a viewmodel here?
我应该在这里使用viewmodel吗?
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:
然后你的控制器将返回一个IEnumerable
@Html.DisplayForModel()
and the corresponding display template (~/Views/Shared/DisplayTemplates/ThingiesByStatusViewModel.cshtml
):
和相应的显示模板(〜/ 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>
.
我们有一个IEnumerable
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);
}
#2
3
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.
在您的方案中,我将向您的视图模型添加StatusName属性并享受成功。
#3
1
Yes, you should use VM.
是的,你应该使用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.
此外,将来您的应用程序将会成长,您永远不知道何时需要扩展它。因此,以适当的方式实施它可以为您提供良好的可维护性。
#4
1
The answer is yes. Of course. Why not? It will only involve about 1% of your code!
Think about it:
想一想:
-
The purpose of a ViewModel is as a container to send data to the view.
ViewModel的目的是作为将数据发送到视图的容器。
-
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.
因此,它可以“塑造”发送到视图的数据,这可能与您的域模型不对应。 EG,如果Thingies有50个属性(或表中的列......),您可能只需要其中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.
为了提供这种形状的数据,我使用了“服务”类。 EG,StatusService(由允许DI的接口绑定,例如,IStatusService)。因此,Service类获取存储库的实例,提供在控制器中使用的方法以及构建ViewModel以包装视图数据的特殊只读属性。
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.
使用这种方式,您可以轻松地看到编写ViewModel所付出的努力是可行的。就代码行而言,大概是1%。
Want proof?
Look at the following:
看看以下内容:
A typical Controller would be:
典型的控制器是:
The Controller:
//NOTE THE USE OF: service.ViewModel
//注意使用: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:
Service类看起来像:
// NOTE THE FOLLOWING PROPERTY: public StepEditorVM 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
}
}
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:
最后,ViewModel类本身:
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.
这样做的原因,imo,主要是为了提供一种编写代码的结构化方式。在创建相应的视图之前,您可以在早上写完所有内容。
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.
以上的重点是强调与整体工作相比,编写ViewModel的努力有多少。