C# 如何将错误处理代码从MVC控制器中分离出来?
大多数时候,我都能弄明白如何将业务逻辑从MVC控制器分解到服务或模型中。然而,错误处理是一个例外,在我看来,这是控制器的责任。但是,它可能会导致控制器“不瘦”。例如:C# 如何将错误处理代码从MVC控制器中分离出来?,c#,asp.net-mvc,model-view-controller,controller,refactoring,C#,Asp.net Mvc,Model View Controller,Controller,Refactoring,大多数时候,我都能弄明白如何将业务逻辑从MVC控制器分解到服务或模型中。然而,错误处理是一个例外,在我看来,这是控制器的责任。但是,它可能会导致控制器“不瘦”。例如: if ((foundEvent = repoEvent.GetEventById(id)) == null) { return HttpNotFound("Could not find event with id {0}.".FormatWith(id)); } var assessment = isSample ? f
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
的类不可用)。但是,您可以看到,在短时间后,这将变得多么漫长和混乱。有没有办法排除控制器中的此类错误处理,使控制器更“瘦”,或者这些东西只是属于控制器
下面是一段较大的代码片段,说明了我是如何进行重构以允许两个操作方法使用相同的视图模型设置代码的。但是,它仍然会在控制器中为两个操作方法生成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 });
}
#区域私有方法
私有StartAssessmentViewModel getStartViewModel(int eventId、bool isSample、out ActionResult actionRes){
actionRes=null;
EventRepository repoEvent=新的EventRepository();
事件发现事件;
if((foundEvent=repoEvent.GetEventById(eventId))==null){
actionRes=HttpNotFound(“找不到id为{0}的事件。”.FormatWith(eventId));
返回null;
}
var评估=isSample?foundEvent.Samplessessment:foundEvent.ActualAssessment;
如果(评估==null){
actionRes=HttpNotFound(“找不到id为{1}的事件的{0}评估”。FormatWith(isSample?“sample”:“”,eventId));
返回null;
}
如果(assessment.unappedCaseStudiesCount==0){
TempData[TempDataKeys.ErrorMessage.GetEnumName()]=“此评估(\“{0}\”)没有案例研究!”.FormatWith(assessment.Name);
actionRes=重定向到操作(“索引”、“事件”);
返回null;
}
试一试{
//评估完成了吗?(样本不算在内)
UserAssessmentRepository repoUa=新的UserAssessmentRepository();
var foundUa=repoUa.GetUserAssessment(foundEvent.EventId,assessment.AssessmentId,User.Identity.Name);
//TODO:检查foundUa.Assessment.IsSample是否正常;可能需要在repo方法中创建一个具体实例
if(foundUa!=null&!foundUa.Assessment.IsSample){
如果(_svcAsmt.IsAssessmentFinished(foundUa)){
//TODO:测试这种显示错误的方式是否有效。
TempData[TempDataKeys.ErrorMessage.GetEnumName()]=“您已经完成了此事件({0})的评估;无法再次启动它。”.FormatWith(foundEvent.Name);
actionRes=重定向到操作(“索引”、“事件”);
返回null;
}
//已经开始了吗?
if(_svcAsmt.IsAssessmentStarted(foundEvent.EventId、foundUa.AssessmentId、User.Identity.Name)){
actionRes=RedirectToAction(“问题”,new{id=foundUa.UserAssessmentId});
返回null;
}
}
返回Mapper.Map(评估);
}
捕获(例外情况除外){
TempData[TempDataKeys.ErrorMessage.GetEnumName()]=“无法显示事件{1}上评估{0}的开始屏幕;错误:{2}”。FormatWith(assessment.AssessmentId,foundEvent.EventId,ex.Message);
actionRes=重定向到操作(“索引”、“事件”);
返回null;
}
}
#端区
公共操作结果开始(int-id,bool-isSample=false){
//设置视图模型
行动结果行动;
StartAssessmentViewModel viewModel=getStartViewModel(id、isSample、out actionRes);
if(viewModel==null){
返还诉讼;
}
返回视图(viewModel);
}
[HttpPost,ActionName(“开始”)]
public ActionResult startconfirm(int-id,StartAssessmentViewModel-viewModel){
//设置视图模型
行动结果行动;
StartAssessmentViewModel newViewModel=getStartViewModel(id,viewModel.AssessmentIsSample,out actionRes);
if(newViewModel==null){
返还诉讼;
}
如果(!ModelState.IsValid){
返回视图(newViewModel);
}
//模型是有效的;如果它不是示例,我们需要检查访问代码
如果(!viewModel.AssessmentIsSample){
如果(viewModel.AccessCode!=“12345”){
//无效的访问代码
TempData[TempDataKeys.ErrorMessage.GetEnumName()]=“访问代码{0}不正确。”.FormatWith(viewModel.AccessCode);
返回视图(newViewModel);
}
}
//访问代码有效或评估为示例;重定向到第一个问题
返回重定向到操作(“问题”,新的{id=1});
}
我同意,这种错误处理是控制器的责任,因此我认为您已经把它放在了正确的位置。关于使方法“更精简”,您可能比查看Bob Martin的干净代码指南更糟糕,该指南建议将较大的方法重构为较小的方法,例如例如:
if (assessment.UnmappedCaseStudiesCount == 0) {
TempData[TempDataKeys.ErrorMessage.GetEnumName()] =
"This assessment (\"{0}\") has no case studies!".FormatWith(assessment.Name);
actionRes = RedirectToAction("Index", "Events");
return null;
}
if (AssessmentHasNoCaseStudies(assessment, out actionRes)) {
return null;
}
...
private bool AssessmentHasNoCaseStudies(Assessment assessment, out ActionResult actionRes)
{
actionRes = (assessment.UnmappedCaseStudiesCount == 0)
? RedirectToAction("Index", "Events")
: null;
if (actionRes == null)
{
return false;
}
TempData[TempDataKeys.ErrorMessage.GetEnumName()] =
"This assessment (\"{0}\") has no case studies!".FormatWith(assessment.Name);
return true;
}
…要使用帮助器方法,请执行以下操作:
if (assessment.UnmappedCaseStudiesCount == 0) {
TempData[TempDataKeys.ErrorMessage.GetEnumName()] =
"This assessment (\"{0}\") has no case studies!".FormatWith(assessment.Name);
actionRes = RedirectToAction("Index", "Events");
return null;
}
if (AssessmentHasNoCaseStudies(assessment, out actionRes)) {
return null;
}
...
private bool AssessmentHasNoCaseStudies(Assessment assessment, out ActionResult actionRes)
{
actionRes = (assessment.UnmappedCaseStudiesCount == 0)
? RedirectToAction("Index", "Events")
: null;
if (actionRes == null)
{
return false;
}
TempData[TempDataKeys.ErrorMessage.GetEnumName()] =
"This assessment (\"{0}\") has no case studies!".FormatWith(assessment.Name);
return true;
}
但是你会把像
assessment hasnocasestudies
这样的方法留在controller类中吗?你不会把它们移到另一个类中吗?不,我想我不会。该方法决定说应该将某人重定向到事件索引,这肯定是controller的决定。你已经接受了答案,但这是一个有趣的问题ting在博客上发表了一篇关于保持控制器代码干净的方法的文章,你可能会发现这些方法在将来很有用。@hawkke那篇文章的例子似乎不需要对