MVC InvalidOperationException with custom error pages - asp.net-mvc

I have custom error pages setup using
<customErrors mode="On" defaultRedirect="~/Home/Error">
<error statusCode="404" redirect="~/Home/PageNotFound" />
</customErrors>
I created a page that throws and exception and I get redirected to the correct error pages.
However I am noticing these errors in elmah on the production webserver:
System.InvalidOperationException The view 'Error' or its master was
not found or no view engine supports the searched locations. The
following locations were searched:
~/Areas/Football/Views/Draft/Error.aspx
~/Areas/Football/Views/Draft/Error.ascx
~/Areas/Football/Views/Shared/Error.aspx
~/Areas/Football/Views/Shared/Error.ascx ~/Views/Draft/Error.aspx
~/Views/Draft/Error.ascx ~/Views/Shared/Error.aspx
~/Views/Shared/Error.ascx ~/Areas/Football/Views/Draft/Error.cshtml
~/Areas/Football/Views/Draft/Error.vbhtml
~/Areas/Football/Views/Shared/Error.cshtml
~/Areas/Football/Views/Shared/Error.vbhtml ~/Views/Draft/Error.cshtml
~/Views/Draft/Error.vbhtml ~/Views/Shared/Error.cshtml
~/Views/Shared/Error.vbhtml
Why is it looking for the error page elsewhere? I deleted ~/Views/Shared/Error.cshtml and added my custom error page at ~/Home/Error since i specified a new default in my config file.
Any ideas?
Thanks.

MVC projects by default adds the HandleErrorAttribute in the Global.asax.cs file
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
This filter is executed when an unhandled exception is thrown. It sets the view as Error. Hence MVC runtime tries to render that view. But in your case, there is no such view. So it again throws another exception which is handled by ASP.NET runtime and shows your error page that you have configured in Web.Config file.
You can create your own exception filter and register it.

I ended up taking out the registration of HandleErrorAttribute in Global.asax and just using the <customErrors> section. ELMAH now properly logs errors, and I'm able to specify custom error pages.
Am I missing something?

You can also make sure that the HandleErrorAttribute is not registered by removing it from the global filters, using the Remove method:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Remove(new HandleErrorAttribute());
/* ... your other filters */
}
}

For future readers of this post, note that Elmah adds its own HandleErrorAttribute so is also expecting the Error.cshtml view. Note that I also installed the Elmah.MVC NuGet package but that is just used to set it up properly.
Yes, just noticed on nuget.org that Elmah.MVC is responsible for adding the HandleErrorAttribute:
Painless integration of ELMAH functionality into ASP.NET MVC
Application. Just drop the package inside your ASP.NET MVC application
and access /elmah URL. It will also install global HandleError filter,
that guarantees all unhandled errors are logged (even if customError
turned "On").

To disable ELMAH's HandleErrorAttribute add the following line to the appSettings section of your Web.Config file:
<!-- language: lang-xml -->
<add key="elmah.mvc.disableHandleErrorFilter" value="true" />

Related

ELMAH not logging exceptions if Custom error page is enabled

I have this path /test/account and it has a code
throw new Exception("error");
in my web.config
<customErrors mode="On">
<error statusCode="404" redirect="~/404.html" />
</customErrors>
When it's mode is On, it shows custom error page instead of exception and elmah doesn't log it. When it's Off, it displays the exception and it's logged.
My understanding is that we need to log exceptions and we have elmah for that, however we don't want to display those detailed exceptions in production so we have custom error page.
I want that even though I display custom error page when there's an exception, I still want elmah to log it.
Custom error pages do actually "swallow" exceptions and as a result, ELMAH is never notified about the exception. There's a nice little trick to log handled exceptions, taken from ELMAH and custom errors. Create this class:
public class ElmahExceptionLogger : IExceptionFilter
{
public void OnException (ExceptionContext context)
{
if (context.ExceptionHandled)
{
ErrorSignal.FromCurrentContext().Raise(context.Exception);
}
}
}
Then configure the filter:
protected void Application_Start()
{
GlobalConfiguration.Configuration.Filters.Add(new ElmahExceptionLogger());
}

ASP.NET MVC 5 application on Azure won't log exceptions

I'm having difficulties getting uncaught exceptions logged when deploying my ASP.NET MVC 5 application to an Azure App Service slot. When an uncaught exception occurs, the app rightly returns a 500 and the standard "Error. An error occurred while processing your request" Error.cshtml page, but never records any mention of the exception in any of my logs.
Current Setup:
Azure Settings:
Application Logging: On, logging to both filesystem and storage blobs, at the information level
Detailed error messages: On
Failed request tracing: On
Application Setup:
Using NLog to write to both a file and Trace destinations, which is working fine elsewhere and on caught exceptions
Haven't explicitly set <customErrors /> in my Web.configs, but they are showing as off locally and on remotely, as expected and desired
Added an Application_Error override to Global.asax (see below)
Added a custom ExceptionLoggingFilter (see below) and registered it in my FilterConfig.cs pipepline as the first item
Application_Error override in Global.asax:
protected void Application_Error(Object sender, EventArgs e)
{
Trace.TraceError("Uncaught exception bubbled up. Details: {0}", e);
Logger log = LogManager.GetCurrentClassLogger();
Exception ex = Server.GetLastError();
log.Fatal(ex, "Uncaught exception bubbled up to MVC root.");
}
Customer ExceptionLoggingFilter registered in GlobalFilters:
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public void OnException(ExceptionContext filterContext)
{
var ex = filterContext.Exception;
var request = filterContext.HttpContext.Request;
Logger.Fatal(ex, "Uncaught exception bubbled up to MVC root.");
}
FilterConfig.cs:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ExceptionLoggerFilter());
filters.Add(new HandleErrorAttribute());
}
NLog.config:
...
<targets async="true">
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log" layout="${longdate} ${uppercase:${level}} ${message} ${exception:format=tostring}" />
<target xsi:type="Trace" name="trace" layout="${logger} ${message} ${exception:format=tostring}" />
<!-- another target for sending data to Slack here... -->
</targets>
...
<rules>
<logger name="*" minlevel="Debug" writeTo="f" />
<logger name="*" minlevel="Trace" writeTo="trace" />
</rules>
...
What I've tried:
I created an action that purposely triggers an uncaught exception (var neverResult = Convert.ToInt32("a");) and when I navigate to that page I get the custom error page. Implementing <customErrors mode="Off" /> in production on Azure does what I'd expect: I know see the full error message and stack trace when navigating to that example action BUT I do not get any logging going on, not from NLog, traces, watching the streaming logs, nada.
I've tried adding the additional handlers and filters for logging errors as explained above, and still nothing, though I know by remote debugging that the code is all being run in those additional handlers. Locally everything gets logged just fine.
Help!
I'm at a bit of a loss here even where to look next. Not sure if I need to look at Azure setting, how ASP.NET is handling things, or if NLog is off (even though it logs everything else fine). Any direction is greatly appreciated.
Update: Added actual filters config and NLog config.
I actually tried this with your configuration and found the problem.
If you use Logger.Fatal, the Trace message gets written in Verbose level for some reason. Change your logging filter to use the Error function:
Logger.Error(ex, "Uncaught exception bubbled up to MVC root.");
And also, exception filters run last-to-first, so in your case HandleErrorAttribute runs first. It however doesn't mean your filter won't run (ASP.NET Core is a bit different in this regard).
The Fatal function of NLog calls the Fail function of the Trace class, which is supposed to be used to show a message box to the user in Windows environments. For some reason that is on Verbose level on IIS.
TL;DR Don't use Fatal(), use Error().

ASP.NET MVC 4 - Exception Handling Not Working

When an error occurs in my ASP.NET MVC 4 application, I would like to customize a view for the user depending on the type of error. For example, page not found or an exception has occurred (with some user-friendly details about the exception). I've checked other examples of how to do this on StackOverflow and other online sources, but none of the answers are working for me.
The basic [HandleError] attribute does not seem to be working in VS2012 with an MVC 4 application targeting .NET 4.5. Here is the code I have in my home controller:
[HandleError]
public ActionResult Index()
{
Response.TrySkipIisCustomErrors = true; //doesn't work with or without this
throw new NullReferenceException("Uh oh, something broke.");
}
It is just throwing an exception, and I would expect the default ~/Shared/Error.cshtml view to be returned because of the [HandleError] attribute, but all I get is an HTTP 500 Internal Server Error indicating that the page could not be displayed. I checked my web.config, and different configurations seem to be behaving weird. In the section, it currently contains:
<customErrors mode="On" />
(I've tried adding defaultRedirect and with customErrors mode="Off" as well but that didn't have any effect... neither the shared Error view or the CustomError view I have is being rendered. If I change customErrors mode to off, then I can see the exception details as expected, so it is throwing the "Uh oh, something broke" exception properly.
I've also tried adding an OnException handler to the HomeController, and although I can debug through and see that the OnException event is being raised, it doesn't make any difference:
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
filterContext.ExceptionHandled = true;
if (filterContext == null)
{
filterContext.Result = View("CustomError");
return;
}
Exception e = filterContext.Exception;
// TODO: Log the exception here
ViewData["Exception"] = e; // pass the exception to the view
filterContext.Result = View("CustomError");
}
I have also tried changing [HandleError] to specify a view, but that doesn't seem to do anything either:
[HandleError(View="CustomError")]
Any help would be much appreciated. Please let me know if you need any more details.
I also went on a seamingly endless journey of reading SO answers and assorted blog postings trying to get custom error pages to work. Below is what finally worked for me.
The first step is to use IIS Express for debugging instead of the built-in Cassini web server to "guarantee" that the debug experience will mirror the live environment.
Create a controller to handle application errors and add an action for each custom error you will handle. The action should do any logging, set the status code, and return the view.
public class ErrorsController : Controller
{
// 404
[HttpGet]
public ActionResult NotFound()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
// I also have test actions so that I can verify it's working in production.
[HttpGet]
public ActionResult Throw404()
{
throw new HttpException((int)HttpStatusCode.NotFound, "demo");
}
}
Configure the customErrors section in web.config to redirect to your custom error actions.
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="Errors/InternalServerError">
<error statusCode="400" redirect="Errors/BadRequest" />
<error statusCode="403" redirect="Errors/Forbidden" />
<error statusCode="404" redirect="Errors/NotFound" />
<error statusCode="500" redirect="Errors/InternalServerError" />
</customErrors>
Add the httpErrors section to system.webServer and set the errorMode to Detailed in web.config. Why this works is a mystery to me, but this was the key.
<system.webServer>
<httpErrors errorMode="Detailed" />
Add a catchall route last to the defined routes to direct 404s to the custom page.
// catchall for 404s
routes.MapRoute(
"Error",
"{*url}",
new {controller = "Errors", action = "NotFound"});
You may also want to create a custom HandleErrorAttribute and register it as a global filter to log 500 errors.
These steps worked for me in both development (IIS Express) and production (IIS7) environments. You need to change customErrors mode="On" to see the effect in development.
I seem to recall that you had to call the page from a non-localhost IP address (typically another computer). And it has to be an IIS based server, not the built-in development server (so IIS or IIS Express, but then you have to configure IIS Express for external access, which is a pain).
You can in fact debug it, you have to configure your local server on your debug computer to accept external requests, then call your local server from the remote server.
I faced a similar problem and lost sometime trying to work out what was going on. So just in case any others face a similar problem here is what my problem was.
The error page was trying to use my _Layout page. Just ensure that you Error.cshtml page has
#{
Layout = null;
}
Try adding the following attribute to the customErrors tag:
defaultRedirect="Error"
This explicitly defines to which view MVC should redirect the user when the error is thrown and no view is specified by default in the attribute. This will of course only work if Error.cshtml exists in the Shared views folder.

How do I handle uncaught exceptions in an ASP.NET MVC 3 application?

I want to handle uncaught exceptions in my ASP.NET MVC 3 application, so that I may communicate the error to the user via the application's error view. How do I intercept uncaught exceptions? I'd like to be able to do this globally, not for each controller (although I wouldn't mind knowing how to do this as well).
You can set up a global error filter in Global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
The above sets up a default error handler which directs all exceptions to the standard error View. The error view is typed to a System.Web.Mvc.HandleErrorInfo model object which exposes the exception details.
You also need to turn on custom errors in the web.config to see this on your local machine.
<customErrors mode="On"/>
You can also define multiple filters for specific error types:
filters.Add(new HandleErrorAttribute
{
ExceptionType = typeof(SqlException),
View = "DatabaseError",
Order = 1
});
/* ...other error type handlers here */
filters.Add(new HandleErrorAttribute()); // default handler
Note that HandleErrorAttribute will only handle errors that happen inside of the MVC pipeline (i.e. 500 errors).
you can use HandleErrorAttribute filters,
[ErrorHandler(ExceptionType = typeof(Exception), View = "UnhandledError", Order = 1)]
public abstract class BaseController : Controller
{
}
basically you can have this on top of a base controller and define the UnhandledError.cshtml in the Shared views folder.
And if you want to log the unhandled errors before you show the error message then you can extend the HandleErrorAttribute class and put the logic to do the logging inside the OnException method.
public class MyErrorHandlerAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext exceptionContext)
{
Logger.Error(exceptionContext.Exception.Message,exceptionContext.Exception);
base.OnException(exceptionContext);
}
}
For completeness sake, there is also the Application_Error handler in Global.asax.
Global Error Handling
Add in web.config
<customErrors mode="On"/>
Error will be displayed on Error.cshtml which is resides in shared
folder
Change in Error.cshtml
#model System.Web.Mvc.HandleErrorInfo
#{
ViewBag.Title = "Error"; }
<h2>
<p>Sorry, an error occurred while processing your request.</p>
<p>Controller Name: #Model.ControllerName</p>
<p>Action Name : #Model.ActionName</p>
<p>Message: #Model.Exception.Message</p> </h2>
in order to make this work I followed the following blog post and then make the following addition to both Web.config files (the root one and the one in the Views folder) inside the <system.web> node:
...
<system.web>
<customErrors mode="On"/>
...
Hope it helps...

Can't find the custom error page

Here's how I have it defined (locally, on my development machine):
<customErrors mode="On" defaultRedirect="Error.aspx">
<error statusCode="404" redirect="NotFound.aspx" />
</customErrors>
And I have the [HandleError] attribute:
[Authorize]
[HandleError]
public class HomeController : Controller
{ // etc.
Yet when I type in http://localhost:1986/blah, I get the following error:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /NotFound.aspx
The URL it's trying to go to is as you would expect:
http://localhost:1986/NotFound.aspx?aspxerrorpath=/blah
So it IS attempting to go to the custom error file -- however it can't find it. I do have NotFound.aspx in the Shared directory -- same place as the Error.aspx supplied by Microsoft as a default. Why can't it find it?
If the Error.aspx and NotFound.aspx are in the shared directory is there a controller wired to served them? If you do not have some sort of controller route configured to serve the files then the fact that they are in the shared folder is irrelevant.
You have a few options, you could create an ErrorController which will handle the requests for those views and define routes pointing to those controller actions:
[OutputCache(CacheProfile = "Default", VaryByParam = "none")]
public class ErrorController : DefaultAreaBaseController
{
public ViewResult ServiceUnavailable() {
Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
return View("ServiceUnavailable");
}
public ViewResult ServerError() {
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return View("ServerError");
}
public new ViewResult NotFound() {
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View("NotFound");
}
}
Or as an alternative, you can create ignore routes pointing at the physical files and place the error pages somewhere other than the Views folder (like your root directory):
routes.IgnoreRoute("Error.aspx/{*pathInfo}");
routes.IgnoreRoute("NotFound.aspx/{*pathInfo}");
Either of these solutions is viable however depending on your configuration using an IgnoreRoute() may be more ideal as it will forgo the need to pipe the request to MVC only to serve a static error page.
Option One:
is to build an Errors Controller with a "NotFound" view along with a "Unknown" view. This will take anything that is a 500 Server error or a 404 NotFound error and redirect you to the appropriate URL. I don't totally love this solution as the visitor is always redirected to an error page.
http://example.com/Error/Unknown
<customErrors mode="On" defaultRedirect="Error/Unknown">
<error statusCode="404" redirect="Error/NotFound" />
<error statusCode="500" redirect="Error/Unknown" />
</customErrors>
wwwroot/
Controllers
Error.cs
Views/
Error/
NotFound.aspx
Unknown.aspx
Option Two:
I Definitely don't prefer this method (as it is basically reverting back to web forms, The second option is to simply have a static Error.aspx page and ignore the route in MVC), but it works none the less. What you're doing here is ignoring a "Static" directory, placing your physical Error pages in there, and skirting around MVC.
routes.IgnoreRoute("/Static/{*pathInfo}"); //This will ignore everything in the "Static" directory
wwwroot/
Controllers/
Static/
Error.aspx
Views/
Option Three:
The third option (THIS IS MY FAVORITE) is to return an Error View from whatever view is catching the error. This would require you to code up Try/Catch blocks along the way for "known" errors and then you can use HandleError for the unknown errors that might creep up. What this will do is preserve the originally requested URL but return the ERROR view.
EXAMPLE:
http://example.com/Products/1234 will show a details page for ProductID 1234
http://example.com/Products/9999 will show a NotFound error page because ProductID 9999 doesn't exist
http://example.com/Errors/NotFound "should" never be shown because you handle those errors individually in your controllers.
Web.Config
<customErrors mode="On">
</customErrors>
Controller
// Use as many or as few of these as you need
[HandleError(ExceptionType = typeof(SqlException), View = "SqlError")]
[HandleError(ExceptionType = typeof(NullReferenceException), View = "NullError")]
[HandleError(ExceptionType = typeof(SecurityException), View = "SecurityError")]
[HandleError(ExceptionType = typeof(ResourceNotFoundException), View = "NotFound")]
Public Class ProductController: Controller{
public ViewResult Item(string itemID)
{
try
{
Item item = ItemRepository.GetItem(itemID);
return View(item);
}
catch()
{
return View("NotFound");
}
}
}
Folder Structure
wwwroot/
Controllers/
Shared/
NotFound.aspx
NullError.aspx
SecurityError.aspx
SqlError.aspx
Views/
Option Four:
The last option would be that you build your own custom filter for things like ResourceNotFoundException and attach it to your controller class. This will do the exact same thing as above but with the added benefit of sending the error code down the line to the client as well.
Richard Dingwall talks about it on his blog.
Your mixing web forms and MVC concepts here. Turn custom errors off in the web.config. Then in the HandleError attribute optionally specify the type and view, by default error.aspx is searched for in views\CurrentController then views\shared. Whilst you can get 404 handling working with the HandleError filter you probably want to create a filter just for 404 handling, the reasons and how to are explained in detail here:
http://richarddingwall.name/2008/08/17/strategies-for-resource-based-404-errors-in-aspnet-mvc/

Resources