在ASP.NET MVC中执行基于实体的验证(每个实体类具有验证其内部成员的IsValid()
方法)的最佳方法是什么,具有“每个请求的会话”模型,< strong>控制器对ISession的了解是否为零(或有限)?这是我正在使用的模式:
使用包含当前NH会话的IFooRepository
按ID获取实体。这将返回一个连接的实体实例。
加载具有可能无效数据的实体,来自表单帖子。
通过调用其IsValid()
方法验证实体。
如果有效,请致电IFooRepository.Save(entity)
,代表ISession.Save()
。否则,显示错误消息。
会话当前在请求开始时打开,在请求结束时刷新。由于我的实体已连接到会话,因此即使对象无效,刷新会话也会尝试保存更改。
在实体类中保留验证逻辑的最佳方法是什么,限制控制器对NH的了解,并避免在请求结束时保存无效更改?
选项1:显式清除验证失败,隐式刷新:如果验证失败,我可以手动驱逐操作方法中的无效对象。如果成功,我什么都不做,会话会自动刷新。
Con :容易出错并且反直觉(“我没有调用.Save(),为什么我的无效更改仍然被保存?”)
选项2:显式刷新,默认情况下不执行任何操作:默认情况下,我可以在请求结束时处理会话,仅在控制器指示成功时才刷新。我可能在我的基本控制器中创建一个SaveChanges()
方法,设置一个表示成功的标志,然后在请求结束时关闭会话时查询该标志。
Pro :如果dev忘记了此步骤[相对于选项1],则更直观地进行故障排除
Con :我必须致电IRepository.Save(entity)
'和 SaveChanges()
。
选项3:始终使用断开连接的对象:我可以修改我的存储库以返回断开连接的/瞬态对象,并修改Repo.Save()
方法以重新附加它们。
Pro :最直观的,因为控制器不了解NH。
Con :这会破坏我从NH获得的许多好处吗?
答案 0 :(得分:2)
选项1毫无疑问。这不是反直觉,而是NH的运作方式。使用NH检索的对象是持久的,并且在刷新会话时将保存更改。调用Evict使对象成为瞬态,这正是您想要的行为。
您没有提及它,但另一种选择可能是使用Manual或Commit FlushMode。
答案 1 :(得分:0)
验证服务如何使用IsValid(或类似的)方法来验证传递给它的对象,如果失败则可以发布ValidationFailed事件。然后,当您的请求完成而不是调用会话的flush时,您可以发布RequestEnd事件。然后,您可以拥有一个侦听RequestEnd事件和ValidationFailed事件的处理程序 - 如果存在ValidationFailed事件,则不刷新会话但如果没有则刷新它。
说过我只做选项2!
答案 2 :(得分:0)
正如Mauricio和Jamie在他们的回答/评论中所指出的那样,要完全按照问题提出要求并不容易(也可能不可取)。 NH返回持久对象,因此将这些对象暴露给控制器意味着控制器负责将它们视为。我想使用延迟加载,因此暴露分离的实例将不起作用。
选项4:引入提供所需语义的新模式
这个问题的关键在于我正在使用手动滚动的类似活动记录的DAL将NH +存储库引入现有项目。我希望代码编写NH使用类似于遗留代码的模式。
我创建了一个名为UnitOfWork
的新类,它是ITransaction
上的一个非常薄的包装器,它知道如何访问与当前HttpRequest相关的环境会话。此类旨在用于使用块,类似于团队熟悉的TransactionScope
:
using (var tx = new UnitOfWork()) {
var entity = FooRepository.GetById(x);
entity.Title = "Potentially Invalid Data";
if (!entity.IsValid()) {
tx.DiscardChanges();
return View("ReloadTheCurrentView");
}
else {
tx.Success();
return RedirectToAction("Success");
}
}
tx.DiscardChanges()
是可选的,这个类与TransactionScope具有相同的语义,这意味着如果在设置成功标志之前将其置位,它将隐式回滚。
在绿地NH项目上,我认为最好使用选项1,正如Jamie在他的回答中所说的那样。但我认为选项4是在已经使用类似模式的遗留项目中引入NH的一种不错的方式。