Demystifying ASP.NET MVC 5 Error Pages and Error Logging
出处:http://dusted.codes/demystifying-aspnet-mvc-5-error-pages-and-error-logging
Error pages and error logging, both so elementary and yet so complex in ASP.NET MVC. Perhaps complex is not entirely true, but it is certainly not very straight forward for a fresh ASP.NET MVC starter.
The reason being is there are numerous ways of implementing error pages in ASP.NET MVC 5 and when you search for advice you will find a dozen different StackOverflow threads, each suggesting a different implementation.
Overview
What is the goal?
Generally when speaking of error pages and error logging I mean error pages and logging forunhandled exceptions in your application.
The basic goal is:
- Human friendly error pages
- Custom page per error code (e.g.: 404, 403, 500, etc.)
- Preserving the error code in the response to avoid Google indexing error pages
- Logging unhandled errors
Error pages and logging in ASP.NET MVC 5
I am sure one could think of many more solutions to the problem, but typically you will find solutions which involve at least one or a combination of more of these methods:
- HandleErrorAttribute
- Controller.OnException Method
- Application_Error event
- customErrors element in web.config
- httpErrors element in web.config
- Custom HttpModule
That's a lot of different ways for processing an error and they all have a historically justifyable reason. There is no golden solution which works for every application.
Before I will go through each in detail I want to explain some fundamentals which will hopefully make the whole understanding a lot easier.
ASP.NET MVC Fundamentals
ASP.NET MVC is nothing more than a HttpHandler plugged into the ASP.NET framework. The easiest way to illustrate this is by opening the Global.asax.cs:
public class MvcApplication : System.Web.HttpApplication
Navigating to the implementation of HttpApplication will reveal the underlying IHttpHandler and IHttpAsyncHandler interfaces:
public class HttpApplication : IComponent, IDisposable, IHttpAsyncHandler, IHttpHandler
ASP.NET itself is a larger framework to process incoming requests. Even though it could handle incoming requests from different sources, it is used almost exclusively with IIS. It can be extended with HttpModules and HttpHandlersg.
HttpModules are plugged into the pipeline to process a request at any point of the ASP.NET life cycle. A HttpHandler is responsible for producing a response/output for a request.
IIS (Microsoft's web server technology) will create an incoming request for ASP.NET, which then will start processing it and eventually initialize the HttpApplication (the default handler) and create a response:

The point is that ASP.NET can only handle requests which IIS has forwarded to it. This is determined by the registered HttpHandlers (e.g. by default a request to a .htm file is not handled by ASP.NET).
And finally, MVC is only one of potentially many registered handlers within ASP.NET.
This is crucial to understand the different solutions for error handling.
Breaking down the options
HandleErrorAttribute
The HandleErrorAttribute is an MVC FilterAttribute, which can be applied to a class or method:
namespace System.Web.Mvc
{
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Method,
Inherited = true,
AllowMultiple = true)]
public class HandleErrorAttribute : FilterAttribute, IExceptionFilter
{
// ...
}
}
It's error handling capabilities are limited to action methods within the MVC framework. This means it won't be able to catch and process exceptions raised from outside the ASP.NET MVC handler (e.g. exceptions at an earlier stage in the life cycle, errors in other handlers, etc.) nor any exceptoins where your action method is not part of the call stack (e.g. routing errors, etc.).
The HandleErrorAttribute only handles 500 internal errors. For example this will not redirect to the custom error page:
[HandleError]
public ActionResult Index()
{
throw new HttpException(404, "Not found");
}
You can use the attribute to decorate a controller class or a particular action method. It supports custom error pages per exception type out of the box:
[HandleError(ExceptionType = typeof(SqlException), View = "DatabaseError")]]
In order to get the HandleErrorAttribute working you also need to turn on customErrors in your web.config:
<customErrors mode="On" />
Use case
The HandleErrorAttribute is the most limited in scope. Many application errors will bypass it and therefore it is not ideal for generic error handling cross application.
It is a great tool for action specific error handling though (e.g. additional fault tolerance for critical action methods).
Controller.OnException Method
This method gets called if any action method inside the controller throws an exception. Unlike the HandleErrorAttribute it will also catch 404 and other HTTP error codes and doesn't require setting customErrors mode on.
The implementation is simple, just override the OnException method in your controller:
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
// Redirect on error:
filterContext.Result = RedirectToAction("Index", "Error");
// OR set the result without redirection:
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Error/Index.cshtml"
};
}
You can check if the exception has alrady been handled by another component (e.g.: the HandleErrorAttribute):
if (filterContext.ExceptionHandled)
return;
Many solutions on the internet suggest to create a base controller class and implement the method in one location for a generic error handler.
However, the fact that it is almost as limited as the HandleErrorAttribute, it is not an ideal solution for generic error handling. You will end up duplicating your work at least in one other place.
Use case
It gives a little bit more flexibility than the HandleErrorAttribute, but it is still too limited for generic error processing. It seems to be popular when you need to distinguish your error processing between regular and AJAX requests on a controller level.
Application_Error event
The Applicatoin_Error method is far more generic than the previous two options. It is not limited to the MVC scope any longer and needs to be implemented in the Global.asax.cs:
protected void Application_Error(Object sender, EventArgs e)
{
var raisedException = Server.GetLastError();
// Process exception
}
If you've noticed it doesn't come from an interface, an abstract class or an overriden method. It is purely convention based, similar like the Page_Load event in ASP.NET Web Forms applications.
Any unhandeled exception within ASP.NET will bubble up to this event. There is also no concept of routes anymore (because it is outside the MVC scope). If you want to redirect to a specific error page you have to know the exact URL or configure it to co-exist with customErrors or httpErrors.
Use case
In terms of generic error logging this is a great place to start with! It will capture all errors which haven't been handled at an earlier stage. But be careful, if you have used controller exception handling and set filterContext.ExceptionHandled = true then the exception will not bubble up to Applicatoin_Error.
For custom error pages it is still not perfect. This event will trigger for all ASP.NET errors, but what if someone navigates to a URL which isn't handled by ASP.NET? For example try navigating to http://{your-applicatoin}/a/b/c/d/e/f/g/h. The route is not mapped to ASP.NET and therefore will not be captured in your application and provided with a custom error page from the Application_Error event.
customErrors in web.config
This web.config setting enables you to provide a default error page as well as custom error pages for specific error codes:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error/Index">
<error statusCode="404" redirect="~/Error/NotFound"/>
<error statusCode="403" redirect="~/Error/BadRequest"/>
</customErrors>
<system.web/>
The default implementation redirects the user to the specified error page. This is really bad practise because it will change the original HTTP error code to 302 HTTP Redirect and eventually finish with HTTP 200 OK at the error page. Additionally the original URL will have changed as well. This is not only confusing but has other negative side effects too, like Google will start indexing your error pages!
Luckily you can change this behaviour by setting the redirectMode to ResponseRewrite:
<customErrors mode="On" redirectMode="ResponseRewrite">
This fixes the initial problem, but now you will end up with an error when redirecting to your error page:
An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated.
It is because ResponseRewrite mode uses Server.Transfer under the covers, which looks for a file on your file system. As a result we need to change the redirect path to a static file, for example to an .aspx or .html file:
<customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="~/Error.aspx"/>
Now there is only one last issue left with this - the response code of the error page will still be 200 OK. The only fix for this is to manually set the correct error code in your .aspx error page:
<% Response.StatusCode = 404; %>
This is already pretty good in terms of custom error pages, but we can do better! Noticed how the customErrors section goes into the system.web section? This means we are still in the scope of ASP.NET.
Files and routes which are not handled by our application will render a 404 page by IIS (e.g.: http://myapp/some/path/not/existing/image.gif).
Another downside of customErrors is that if you use a HttpStatusCodeResult instead of throwing an actual exception, then it will bypass ASP.NET customErrors and get handled by IIS as well:
public ActionResult Index()
{
return HttpNotFound();
//throw new HttpException(404, "Not found");
}
There is no hacks we can apply to display a friendly error page in these cases with the customErrors technique.
Use case
The customErrors setting gets us very far, but still has its limits. You can think of it as a legacy version of httpErrors, which has been introduced with IIS 7.0.
The only time when customErrors still make sense is if you can't use httpErrors, because you are running on IIS 6.0.
httpErrors in web.config
The httpErrors section is similar to customErrors, but with the main difference that it is an IIS level setting rather than ASP.NET and therefore needs to go into the system.webserver section in the web.config:
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<clear/>
<error statusCode="404" path="/WebForms/Index.aspx" responseMode="ExecuteURL"/>
</httpErrors>
<system.webServer/>
It allows more configuration than customErrors but has its own little caveats as well. I'll try to explain the most important settings in a nutshell:
- httpErrors can be inherited from a higher level (e.g. set in the machine.config)
- Use the
<remove/>tag to remove an inherited setting for a specific error code. - Use the
<clear/>tag to remove all inherited settings. - Use the
<error/>tag to configure the behaviour for one error code. - responseMode "ExecuteURL" will render a dynamic page with status code 200.
- The workaround to set the correct error code in the .aspx page works here as well.
- responseMode "Redirect" will redirect (302) to any URL.
- responseMode "File" will preserve the original error code and output the static file.
- .aspx files will get output in plain text.
- .html files will render as expected.
The main advantage of httpErrors is that it is handled on IIS level. It will literally pick up all error codes and redirect to a friendly error page. If you want to benefit from master pages I recommend to go with the ExecuteURL and status code fix appraoch. If you want to have rock solid error pages which IIS can serve even when hell is on earth, then I'd recommend to go with the static file approach (preferably .html files).
Use case
This is currently the best place to configure friendly error pages in one location to catch them all. The only reason not to use httpErrors is if you are still running on IIS 6.0.
Custom HttpModule
Last but not least I wanted to quickly scratch on a custom HttpModule. It has nothing to do with friendly error pages anymore (httpErrors is the way to go), but it is great for error logging.
The error event which can be subscribed to insde a custom HttpModule behaves the exact same way as the Application_Error event. If you have both implemented then it gets called before Applicatoin_Error.
The only benefit of the HttpModule is that it is reusable in other ASP.NET applications. Adding/Removing a HttpModule is as simple as adding or removing one line of code in your web.config:
<system.webServer>
<modules>
<add name="CustomModule" type="SampleApp.CustomModule, SampleApp"/>
</modules>
</system.webServer>
In fact, someone has already created a powerful reusable error logging module and it is open source and called ELMAH.
If you need to create application wide error logging, I highly recommend to look at this project!
Final words
I hope this overview was helpful to better unerstand which tool might fit your own error handling requirements in ASP.NET MVC.
Each of the techniques has a justifable reason and it really depends what you need to do. Today I focused on application wide error handling and in this case I would recommend the combination of httpErrors and an error logging module like ELMAH to get the best out of both worlds.
Demystifying ASP.NET MVC 5 Error Pages and Error Logging的更多相关文章
- CSRF in asp.net mvc and ap.net core
如果在方法上添加了[ValidateAntiForgeryToken],没处理好 请求没有带参数 2019-09-17 14:02:45,142 ERROR [36]: System.Web.Mvc. ...
- Announcing the Release of ASP.NET MVC 5.1, ASP.NET Web API 2.1 and ASP.NET Web Pages 3.1 for VS2012
The NuGet packages for ASP.NET MVC 5.1, ASP.NET Web API 2.1 and ASP.NET Web Pages 3.1 are now live o ...
- 安装了VS2010 sp1 后再安装ASP.NET MVC 3.0的问题(Final Result: Installation failed with error code: (0x80070643), "安装时发生严重错误 " (Ela)
原文:安装了VS2010 sp1 后再安装ASP.NET MVC 3.0的问题(Final Result: Installation failed with error code: (0x800706 ...
- ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇
在之前的文章中,我为大家介绍了OWIN和Katana,有了对它们的基本了解后,才能更好的去学习ASP.NET Identity,因为它已经对OWIN 有了良好的集成. 在这篇文章中,我主要关注ASP. ...
- ASP.NET MVC 中如何用自定义 Handler 来处理来自 AJAX 请求的 HttpRequestValidationException 错误
今天我们的项目遇到问题 为了避免跨站点脚本攻击, 默认我们项目是启用了 validateRequest,这也是 ASP.NET 的默认验证规则.项目发布后,如果 customError 启用了,则会显 ...
- Ubuntu 环境 运行Asp.net mvc +EntityFramework+ Mysql
关键词:ubuntu,mono,.Net framework 4.5,asp.net mvc 4,Entityframework 6,Mysql Mono安装 参考文章: Install Mono o ...
- ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇(转)
ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇 阅读目录 ASP.NET Identity 前世今生 建立 ASP.NET Identity 使用ASP.NET ...
- 【转】ASP.NET MVC 的最佳实践
[This post is based on a document authored by Ben Grover (a senior developer at Microsoft). It is ou ...
- 转载 ASP.NET MVC中使用ASP.NET Identity
转载原地址: http://blog.jobbole.com/90695/ 在之前的文章中,我为大家介绍了OWIN和Katana,有了对它们的基本了解后,才能更好的去学习ASP.NET Identit ...
随机推荐
- 使用phar上线你的代码包
在我前一阵子写的一篇文章<新版 SegmentFault 重构之系统架构>中,很多人对其中提到的利用phar上线代码比较感兴趣,我就在这边跟大家分享下我目前的做法. 哪些项目适合phar打 ...
- XAF学习笔记2,关于XAF
简单的说下XAF,王北的博客写得非常好了.我就不在啰嗦, XAF能解决几个问题 1,不用自己创建数据库(设定好Model自动创建数据库,我们只要配置好app.config文件的数据库路径就行.) 2, ...
- Http协议与TCP协议简单理解(转)
在C#编写代码,很多时候会遇到Http协议或者TCP协议,这里做一个简单的理解.TCP协议对应于传输层,而HTTP协议对应于应用层,从本质上来说,二者没有可比性.Http协议是建立在TCP协议基础之上 ...
- Codeforces Round #200 (Div. 1) D. Water Tree(dfs序加线段树)
思路: dfs序其实是很水的东西. 和树链剖分一样, 都是对树链的hash. 该题做法是:每次对子树全部赋值为1,对一个点赋值为0,查询子树最小值. 该题需要注意的是:当我们对一棵子树全都赋值为1的 ...
- 京东2017校园招聘笔试题 【第K个幸运数】
题目描述 4和7是两个幸运数字,我们定义,十进制表示中,每一位只有4和7两个数的正整数都是幸运数字. 前几个幸运数字为:4,7,44,47,74,77,444,447... 现在输入一个数字K,输出第 ...
- SQl server 关于重复插入数据的测试
最近发布的脚本,有那种防止重复插入数据(包括存在时更新,不存在是插入的处理,判断的方向可能与下面的示例相反) 使用类似下面的 SQL declare @id int, @value int if no ...
- HTML5中判断横屏竖屏
在移动端中我们经常碰到横屏竖屏的问题,那么我们应该如何去判断或者针对横屏.竖屏来写不同的代码呢. 这里有两种方法: 一:CSS判断横屏竖屏 写在同一个CSS中 1 2 3 4 5 6 @media s ...
- C#装箱和拆箱
1.装箱是将值类型转换为引用类型(或者转换为此值类型所实现的任何接口类型)的隐式转换,当 CLR 对值类型进行装箱时,会将该值包装到 System.Object 内部,再将后者存储在托管堆上. ; / ...
- noip2012-day2-t2
[问题描述] 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样. 面对海量租借教室的信息,我们 ...
- MAC PRO 的网关在哪里
mac pro的网关就是路由器地址. 1.路由器在系统偏好设置里 2.双击点开此图标 选中1.然后点击高级设置 3.选中TCP/IP,然后查看自己的路由器后边的数字,就是你的mac网关号 4.IPV4 ...