所以我想说我有两个型号:
Thingy和状态。 Thingy
有Status
,状态有很多Thingies。它是典型的“对象和对象类型关系”。
我有一个观点,我只想要每个状态中的东西数量。或者基本上是Status.Name和Status.Thingies.Count的列表。我可以做到这一点,但是“正确”要做的事情是在表单中创建一个视图模型:
ThingiesByStatusViewModel
-StatusName
-StatusThingiesCount
并将其与AutoMapper等内容联系起来。
对于这样一个微不足道的例子,它可能没有太大的区别,但它可以帮助我更好地理解正确的“关注点分离”。
答案 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)
想一想:
ViewModel的目的是作为向视图发送数据的容器。
因此,它可以“塑造”发送到视图的数据,这可能与您的域模型不对应。 EG,如果Thingies有50个属性(或表中的列......),您可能只需要其中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);
}
}
}
服务类看起来像:
//注意以下属性: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类本身:
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
}
}
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的工作量有多少。