答案是肯定的。当然。为什么不?它只涉及大约 1% 的代码!
想一想:
ViewModel 的目的是作为向视图发送数据的容器。
因此,它可以“塑造”发送到视图的数据,这可能与您的域模型不对应。例如,如果 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);
}
}
}
服务:
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 所付出的努力是多么的少。