大多数时候,我可以弄清楚如何将 MVC 控制器中的业务逻辑分解为服务或模型。然而,这个例外是错误处理,在我看来这是控制器的责任。但是,它可能会导致相当“瘦”的控制器。例如:
if ((foundEvent = repoEvent.GetEventById(id)) == null) {
return HttpNotFound("Could not find event with id {0}.".FormatWith(id));
}
var assessment = isSample ? foundEvent.SampleAssessment : foundEvent.ActualAssessment;
if (assessment == null) {
return HttpNotFound("Could not find {0}assessment for event with id {1}".FormatWith(isSample ? "sample " : "", id));
}
if (assessment.UnmappedCaseStudiesCount == 0) {
TempData[TempDataKeys.ErrorMessage.GetEnumName()] = "This assessment (\"{0}\") has no case studies!".FormatWith(assessment.Name);
return RedirectToAction("Index", "Events");
}
HttpNotFound
在上述所有情况下,在我看来,逻辑确实属于控制器,因为控制器正在设置错误消息而不是返回视图(这是由于类似和RedirectToAction
是类成员的事实而得到加强的Controller
,因此对于不扩展的类不可用Controller
)。但是,您可以看到这会在短时间内变得漫长而混乱。有没有办法在控制器中排除这种错误处理以使控制器更“瘦”,或者这些东西只是属于控制器?
这是一个更大的代码片段,说明我如何重构以允许 2 个操作方法使用相同的视图模型设置代码。但是,它仍然会导致控制器中仅针对 2 个操作方法的 90 多行代码;再次,不是一个非常“瘦”的控制器:
#region Private methods
private StartAssessmentViewModel getStartViewModel(int eventId, bool isSample, out ActionResult actionRes) {
actionRes = null;
EventRepository repoEvent = new EventRepository();
Event foundEvent;
if ((foundEvent = repoEvent.GetEventById(eventId)) == null) {
actionRes = HttpNotFound("Could not find event with id {0}.".FormatWith(eventId));
return null;
}
var assessment = isSample ? foundEvent.SampleAssessment : foundEvent.ActualAssessment;
if (assessment == null) {
actionRes = HttpNotFound("Could not find {0}assessment for event with id {1}".FormatWith(isSample ? "sample " : "", eventId));
return null;
}
if (assessment.UnmappedCaseStudiesCount == 0) {
TempData[TempDataKeys.ErrorMessage.GetEnumName()] = "This assessment (\"{0}\") has no case studies!".FormatWith(assessment.Name);
actionRes = RedirectToAction("Index", "Events");
return null;
}
try {
// Has the assessment finished? (samples don't count)
UserAssessmentRepository repoUa = new UserAssessmentRepository();
var foundUa = repoUa.GetUserAssessment(foundEvent.EventId, assessment.AssessmentId, User.Identity.Name);
// TODO: check that foundUa.Assessment.IsSample is OK; may need to make .Assessment a concrete instance in the repo method
if (foundUa != null && !foundUa.Assessment.IsSample) {
if (_svcAsmt.IsAssessmentFinished(foundUa)) {
// TODO: test that this way of displaying the error works.
TempData[TempDataKeys.ErrorMessage.GetEnumName()] = "You have already completed the assessment for this event ('{0}'); you cannot start it again.".FormatWith(foundEvent.Name);
actionRes = RedirectToAction("Index", "Events");
return null;
}
// Has it been started already?
if (_svcAsmt.IsAssessmentStarted(foundEvent.EventId, foundUa.AssessmentId, User.Identity.Name)) {
actionRes = RedirectToAction("Question", new { id = foundUa.UserAssessmentId });
return null;
}
}
return Mapper.Map<StartAssessmentViewModel>(assessment);
}
catch (Exception ex) {
TempData[TempDataKeys.ErrorMessage.GetEnumName()] = "Could not display start screen for assessment {0} on event {1}; error: {2}".FormatWith(assessment.AssessmentId, foundEvent.EventId, ex.Message);
actionRes = RedirectToAction("Index", "Events");
return null;
}
}
#endregion
public ActionResult Start(int id, bool isSample = false) {
// Set up view model
ActionResult actionRes;
StartAssessmentViewModel viewModel = getStartViewModel(id, isSample, out actionRes);
if (viewModel == null) {
return actionRes;
}
return View(viewModel);
}
[HttpPost, ActionName("Start")]
public ActionResult StartConfirmed(int id, StartAssessmentViewModel viewModel) {
// Set up view model
ActionResult actionRes;
StartAssessmentViewModel newViewModel = getStartViewModel(id, viewModel.AssessmentIsSample, out actionRes);
if (newViewModel == null) {
return actionRes;
}
if (!ModelState.IsValid) {
return View(newViewModel);
}
// Model is valid; if it's not a sample, we need to check the access code
if (!viewModel.AssessmentIsSample) {
if (viewModel.AccessCode != "12345") {
// Invalid access code
TempData[TempDataKeys.ErrorMessage.GetEnumName()] = "The access code '{0}' is incorrect.".FormatWith(viewModel.AccessCode);
return View(newViewModel);
}
}
// Access code is valid or assessment is sample; redirect to first question
return RedirectToAction("Question", new { id = 1 });
}