- `httpErrors`:IIS 层面捕获,不管请求有没有进入 .NET 管道;静态文件、无后缀、不存在的地址都能捕获。
- `Application_Error`:[ASP.NET](https://ASP.NET) Runtime 托管管道内捕获,只有请求进入 .NET 托管模块才会触发,纯静态 / 未映射后缀请求直接被 IIS 拦截,不会进这里。
这是推荐的组合方案:两套互补。
- 非托管请求(静态 / 无后缀不存在 URL):IIS httpErrors 捕获
- aspx 托管代码异常:Global 捕获,写日志,Server.Transfer,跳过 IIS httpErrors
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" path="/HttpErrors/Page404.aspx" responseMode="ExecuteURL" />
<remove statusCode="410" subStatusCode="-1" />
<error statusCode="410" path="/HttpErrors/Page410.aspx" responseMode="ExecuteURL" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" path="/HttpErrors/Page500.aspx" responseMode="ExecuteURL" />
</httpErrors>
Page404.aspx Page410.aspx Page500.aspx 都要设置StatusCode 。
Page404.aspx: protected void Page_Load(object sender, EventArgs e)
{
Response.StatusCode = 404;
Response.TrySkipIisCustomErrors = true;
string rawUrl = Request.Url.PathAndQuery;
// 这里可以写日志:记录rawUrl、IP、UA
}
Page410.aspx: protected void Page_Load(object sender, EventArgs e)
{
Response.StatusCode = 410;
Response.TrySkipIisCustomErrors = true;
string rawUrl = Request.Url.PathAndQuery;
string ip = Request.UserHostAddress;
string ua = Request.UserAgent;
// 日志记录:rawUrl、ip、ua
}
Page500.aspx: protected void Page_Load(object sender, EventArgs e)
{
Response.StatusCode = 500;
Response.TrySkipIisCustomErrors = true;
string rawUrl = Request.Url.PathAndQuery;
string ip = Request.UserHostAddress;
string ua = Request.UserAgent;
}
protected void Application_Error(object sender, EventArgs e)
{
//# 核心原因
//`http://localhost:12345/5555` 这种不存在的静态路径,根本没有进入[ASP.NET](https://ASP.NET)管道,直接在 IIS 层面返回 404,不会触发 Global.asax 的 Application_Error。
//> Application_Error 只在:请求进入[ASP.NET](https://ASP.NET)运行时(比如访问 xxx.aspx 页面,代码抛异常)才会触发。
//> 访问 `/ 5555` 这种无后缀路径,IIS 先判断:不是 aspx、不是托管处理程序,直接返回 404,不走.NET,Global 不跑 * *。
Exception ex = Server.GetLastError();
if (ex == null) return;
// 1. 记录日志(NLog/Log4Net 或者写文件)
// Logger.Error(ex, "全局异常");
// 清除服务器异常
Server.ClearError();
// 区分错误类型
var httpEx = ex as HttpException;
int code = httpEx?.GetHttpCode() ?? 500;
Response.StatusCode = code;
Response.TrySkipIisCustomErrors = true;
string targetUrl = "";
switch (code)
{
case 404:
targetUrl = "/HttpErrors/Page404.aspx";
break;
case 410:
targetUrl = "/HttpErrors/Page410.aspx";
break;
default:
targetUrl = "/HttpErrors/Page500.aspx";
break;
}
// Server.Transfer 内部跳转,地址栏不变
Server.Transfer(targetUrl);
}