換言之,任何數據註釋(如必填字段,長度驗證,正則表達式等)都應該在您的視圖模型上完成,並且在發生錯誤時添加到模型狀態。
而且您可能會擁有不僅僅依賴「表單」的業務/域規則,所以您應該在域模型(在映射回來後執行驗證)或者使用服務層。
我們所有的模型都有一個名爲「Validate」的方法,我們在持續之前調用服務。如果它們失敗了業務驗證,那麼它們會拋出自定義異常,該業務驗證會被控制器捕獲並添加到模型狀態。
可能不是每個人的一杯茶,但它是一致的。按照要求業務驗證的
例子:
這裏是我們的域模型,它代表了通用的「郵報」的一個例子(問題,照片,視頻等):
public abstract class Post
{
// .. fields, properties, domain logic, etc
public void Validate()
{
if (!this.GeospatialIdentity.IsValidForThisTypeOfPost())
throw new DomainException(this, BusinessException.PostNotValidForThisSpatial.);
}
}
你看到那裏,我檢查業務規則,並拋出自定義異常。 DomainException
是我們的基礎,我們有許多派生的實現。我們有一個名爲BusinessException
的枚舉,其中包含我們所有例外的值。我們在枚舉上使用擴展方法來提供基於資源的錯誤消息。
這不僅僅是檢查模型上的字段,例如「所有帖子都必須有主題」,因爲雖然這是域的一部分,但它首先是輸入驗證,因此通過數據註釋處理在視圖模型上。
現在,控制器:
[HttpPost]
public ActionResult Create(QuestionViewModel viewModel)
{
if (!ModelState.IsValid)
return View(viewModel);
try
{
// Map to ViewModel
var model = Mapper.Map<QuestionViewModel,Question>(viewModel);
// Save.
postService.Save(model); // generic Save method, constraint: "where TPost: Post, new()".
// Commit.
unitOfWork.Commit();
// P-R-G
return RedirectToAction("Index", new { id = model.PostId });
}
catch (Exception exc)
{
var typedExc = exc as DomainException;
if (typedExc != null)
{
// Internationalised, user-friendly domain exception, so we can show
ModelState.AddModelError("Error", typedExc.BusinessError.ToDescription());
}
else
{
// Could be anything, e.g database exception - so show generic msg.
ModelState.AddModelError("Error", "Sorry, an error occured saving the Post. Support has been notified. Please try again later.");
}
}
return View(viewModel);
}
所以,通過我們得到的「保存」方法上的服務的時候,該模型已通過輸入驗證。然後Save方法調用post.Validate()
,調用業務規則。
如果發生異常,控制器將捕獲並顯示消息。如果它通過Save方法併發生另一個錯誤(90%的時間,例如Entity Framework),我們將顯示一條通用錯誤消息。
正如我所說,不是每個人,但這對我們的團隊很好。我們有清晰的表示和域驗證分離,以及從原始HTTP POST到成功後重定向的一致控制流程。
HTH
你的「商業模式」究竟是什麼? –
Person是一個由EntityFramework跟蹤的類。 PersonViewModel顯然不是..閱讀我鏈接到的博客文章,你將理解我試圖遵循的實踐..因此,驗證邏輯應該到哪裏去的問題。 – ignaciofuentes
謝謝 - 抱歉,我掃描了文章並在其中搜索了「商業模式」,但沒有點擊。 –