- ASP.NET Core MVC中的两种404错误。
- 如何在ASP.NET Core MVC中处理404 Not Found错误。
- 401状态码:请求要求身份验证。对于需要登录的网页,服务器可能返回此响应。
- 403状态码:服务器已经理解请求,但是拒绝执行它。与401响应不同的是,身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交。
- 404状态码:请求失败,请求希望得到的资源在服务器上未发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话,应当使用410状态码来告知旧资源,因为某些内部的配置机制问题,访问内容已不可用,而且没有任何可以跳转的地址。404状态码被广泛应用于服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。
- 500状态码:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现。
- 503状态码:由于临时的服务器维护或者过载,因此服务器当前无法处理请求。通常这是暂时状态,一段时间后会恢复。
var student = _studentRepository.GetStudentById(id);
//判断学生信息是否存在
if(student == null)
{
Response.StatusCode = 404;
return View("StudentNotFound",id);
}
//实例化HomeDetailsViewModel并存储Student详细信息和PageTitle
HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
{
Student = student,
PageTitle = "学生详情"
};
//将HomeDetailsViewModel对象传递给View()方法
return View(homeDetailsViewModel);
可以通过传递一个ID为99的值来调用HomeController中的Details()方法:http://localhost:13380/home/details/99,查询不到该学生的信息,然后跳转到StudentNotFound视图中。
19.1.2 404错误信息的视图代码
在Views/Home文件夹中创建一个名为StudentNotFound.cshtml的视图文件,我们使用Bootstrap 4的样式来优化视图,代码如下。
@model int
@{
ViewBag.Title = "404错误";
}
<div class="alert alert-danger mt-1 mb-1">
<h4>404 Not Found错误 :</h4>
<hr/>
<h5>
查询不到学生ID为 @Model的信息。
</h5>
</div>
<a asp-controller="home" asp-action="index" class="btn btn-outline-success"
style="width:auto">单击此处查看学生信息列表</a>
在这种情况下,我们知道用户正在尝试转到学生详情视图页面,但因为提供的ID值无效,所以我们需要返回一个带有提示消息的自定义错误页面,提示用户找不到ID以及可以查看学生信息列表的链接,效果如图19.1所示。
- UseStatusCodePages()。
- UseStatusCodePagesWithRedirects()。
- UseStatusCodePagesWithReExecute()。
- 找不到指定ID的资源信息。关于如何处理这种类型的404错误,我们在前面的章节已经介绍了——制作自定义的错误页面。
- 请求的URL和路由不匹配。在本节中,我们将学习如何以统一的方式处理此类404错误。
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
//如果环境是Development serve Developer Exception Page
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//否则显示用户友好的错误页面
else if(env.IsStaging() || env.IsProduction() || env.IsEnvironment("UAT"))
{
app.UseExceptionHandler("/Error");
}
//使用纯静态文件支持的中间件,而不使用带有终端的中间件
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name:"default",
pattern:"{controller=Home}/{action=Index}/{id?}");
});
}
目前,我们在此HTTP请求处理管道中没有配置任何处理404错误的内容。因此,如果导航到http://localhost:13380/market/food,我们会看到图19.2所示的默认404错误页面。这是因为URL/market/food与应用程序中的所有路由都不匹配,从而引发了错误。
- UseStatusCodePages()。
- UseStatusCodePagesWithRedirects()。
- UseStatusCodePagesWithReExecute()。
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
//如果环境是Development serve Developer Exception Page
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{ //用于处理错误异常
app.UseStatusCodePages();
}
//使用纯静态文件支持的中间件,而不使用带有终端的中间件
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name:"default",
pattern:"{controller=Home}/{action=Index}/{id?}");
});
}
"IIS Express":{
"commandName":"IISExpress",
"launchBrowser":true,
"environmentVariables":{
"ASPNETCORE_ENVIRONMENT":"Production",
"MyKey":" launchsettings.json中Mykey的值"
}
},
因为添加了UseStatusCodePages()中间件,所以如果我们浏览http://localhost:13380/market/food,则会返回如图19.3所示的简单文本响应。
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
//如果环境是Development serve Developer Exception Page
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseStatusCodePagesWithRedirects("/Error/{0}");
}
//使用纯静态文件支持的中间件,而不使用带有终端的中间件
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name:"default",
pattern:"{controller=Home}/{action=Index}/{id?}");
});
}
我们将下面的代码添加到Configure()方法中,如果出现404错误,则会将用户重定向到/Error/404。这里采用了占位符 {0} ,它会自动接收HTTP中的状态码。
app.UseStatusCodePagesWithRedirects("/Error/{0}");
public class ErrorController:Controller
{
//如果状态码为404,则路径将变为Error/404
[Route("Error/{statusCode}")]
public IActionResult HttpStatusCodeHandler(int statusCode)
{
switch(statusCode)
{
case 404:
ViewBag.ErrorMessage = "抱歉,读者访问的页面不存在";
break;
}
return View("NotFound");
}
}
@{
ViewBag.Title = "页面不存在";
}
<h1>@ViewBag.ErrorMessage</h1>
<a asp-action="index" asp-controller="home">
单击此处返回首页
</a>
此时,如果进入http://localhost:13380/market/food,我们会看到页面已经被导航到了NotFound.cshtml页面,显示自定义404错误信息,如图19.4所示。
app.UseStatusCodePagesWithRedirects("/Error/{0}");
通过访问一个不存在的控制器与操作方法,如http://localhost:13380/market/food,发起请求时,由于此URL与我们的应用程序中的任何路由都不匹配,因此会引发404错误。
这是因为UseStatusCodePagesWithRedirects()中间件会拦截404状态码,顾名思义,它表示发出重定向到指定的错误路径中(在本例中路径为/Error/404)。
- StatusCodePagesWithRedirects()中间件拦截此请求,并将其更改为302,将其指向错误路径(/Error/404)。
- 302状态码表示所请求资源的URL已被暂时更改,在我们的示例中,它被更改为/Error/404。因此,它会发出另一个GET请求以满足重定向的请求。
- 由于发出了重定向,因此地址栏中的URL也从/market/food更改为/Error/404。
- 请求会经过HTTP管道并由MVC中间件处理,最终返回状态码为200,然后导航到NotFound视图中,这意味着请求已成功完成。
- 对整个请求流程中的浏览器而言,没有404错误信息。
- 如果读者仔细观察此请求和响应流,就会发现在实际发生错误时返回成功状态码为200,这在语义上是不正确的。
- UseStatusCodePagesWithReExecute()中间件拦截404状态码并重新执行将其指向URL的管道,即/Error/404中。
- 整个请求流经HTTP管道并由MVC中间件处理,该中间件返回的NotFound视图HTML的状态码依然是200。
- 当响应流出到客户端时,它会通过UseStatusCodePagesWithReExecute()中间件使用HTML响应,将200状态码替换为原始的404状态码。
- 这个中间件重新执行管道应该正确的(404)状态码。它只返回自定义视图(NotFound)。
- 因为它只是重新执行管道而不发出重定向请求,所以我们还在地址栏中保留原始 http://localhost:13380/market/food
public class ErrorController:Controller
{
//使用属性路由,如果状态码为404,则路径将变为Error/404
[Route("Error/{statusCode}")]
public IActionResult HttpStatusCodeHandler(int statusCode)
{
var statusCodeResult =
HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
switch(statusCode)
{
case 404:
ViewBag.ErrorMessage = "抱歉,读者访问的页面不存在";
ViewBag.Path = statusCodeResult.OriginalPath;
ViewBag.QS = statusCodeResult.OriginalQueryString;
break;
}
return View("NotFound");
}
}
代码说明如下。
- statusCodeResult.OriginalPath可以获取URL请求信息。
- statusCodeResult.OriginalQueryString可以获取查询字符串的搜索信息。
@{ViewBag.Title = "页面不存在";}
<h1>@ViewBag.ErrorMessage</h1>
<h1>@ViewBag.Path</h1>
<h1>@ViewBag.QS</h1>
<a asp-action="index" asp-controller="home"> 单击此处返回首页</a>
重新运行程序,在地址栏中输入http://localhost:13380/market/food/3?name=apple,得到的返回视图如图19.7所示。
public ViewResult Details(int?id)
{
throw new Exception("在Details视图中抛出异常");
//其他代码
}
访问http://localhost:13380/Home/Details/2,结果如图19.8所示。
public void Configure(IApplicationBuilder app,IHostingEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//其他代码
}
从上面的代码中我们得知,已经将DeveloperExceptionPage()中间件配置到HTTP请求处理管道中 ,因此在开发环境中运行应用程序时,如果存在未处理的异常,则会触发如图19.9所示的开发人员异常页面。
"ASPNETCORE_ENVIRONMENT":"Production"
在默认情况下,如果在生产等非开发环境中存在未处理的异常,则会看到如图19.10所示的默认页面。
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
//其他代码
}
修改ErrorController代码,它会搜索异常详细信息并返回到指定的自定义错误视图。在生产中,不会在错误视图上显示异常详细信息。我们可以将它们记录到数据库表、文件和事件查看器等,以便开发人员查看它们,并在需要时提供代码修复。我们将在稍后的章节中讨论日志记录。
public class ErrorController:Controller
{
[Route("Error")]
public IActionResult Error()
{
//获取异常细节
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
ViewBag.ExceptionPath = exceptionHandlerPathFeature.Path;
ViewBag.ExceptionMessage = exceptionHandlerPathFeature.Error.Message;
ViewBag.StackTrace = exceptionHandlerPathFeature.Error.StackTrace;
return View("Error");
}
}
请注意,IExceptionHandlerPathFeature位于Microsoft.AspNetCore.Diagnostics命名空间中。
接下来实现错误视图,我们在Views/Error文件夹中添加一个Error.cshtml文件。
<h3>
程序请求时发生了一个内部错误,我们会反馈给团队,我们正在努力解决这个问题。
</h3>
<h5>请通过ltm@ddxc.org与我们取得联系</h5>
<hr />
<h3>错误详情:</h3>
<div class="alert alert-danger">
<h5>异常路径:</h5>
<hr />
<p>@ViewBag.ExceptionPath</p>
</div>
<div class="alert alert-danger">
<h5>异常信息:</h5>
<hr />
<p>@ViewBag.ExceptionMessage</p>
</div>
<div class="alert alert-danger">
<h5>异常堆栈跟踪:</h5>
<hr />
<p>@ViewBag.StackTrace</p>
</div>
请注意,当前系统的环境变量需要为Production或者非Development。
运行项目,访问http://localhost:13380/Home/Details/2,效果如图19.11所示。
- 404错误触发是当访问不存在的地址或者ID没有对应的值时产生的。
- 500错误触发是当发生异常错误时产生的。
[HttpGet]
public ViewResult Edit(int id)
{
Student student = _studentRepository.GetStudentById(id);
if(student == null)
{
Response.StatusCode = 404;
return View("StudentNotFound",id);
}
StudentEditViewModel studentEditViewModel = new StudentEditViewModel
{
Id = student.Id,
Name = student.Name,
Email = student.Email,
Major = student.Major,
ExistingPhotoPath = student.PhotoPath
};
return View(studentEditViewModel);
}
而在Startup.cs中Configure的代码如下。
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
//如果环境是Development serve Developer Exception Page
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//否则显示用户友好的错误页面
else if(env.IsStaging() || env.IsProduction() || env.IsEnvironment("UAT"))
{
app.UseExceptionHandler("/Error");
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}
//使用纯静态文件支持的中间件,而不使用带有终端的中间件
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name:"default",
pattern:"{controller=Home}/{action=Index}/{id?}");
});
}
19.6 小结
本章我们为系统实现了用户友好的错误页面信息以及对全局异常的处理,讲解了为实现这些功能都提供了哪些中间件,以及中间件的特点和加载顺序。在接下来的章节中,我们将会实现将异常信息记录到文件中,这样便于开发人员分析这些错误信息,以优化系统。
本文摘自《深入浅出 ASP.NET Core》