ASP.NET MVC Global Error Handling: A Detailed Guide for Beginners
Handling errors gracefully in a web application is crucial to ensure user satisfaction and maintain system integrity. ASP.NET MVC offers several mechanisms to handle errors both within a single controller or action and globally across the entire application, regardless of the specific controller or action being invoked. This guide will delve into the nuances of setting up global error handling in ASP.NET MVC, helping you to build more robust applications.
1. Understanding Error Types
Before delving into the specifics of global error handling, it’s vital to understand the types of errors that may occur in an ASP.NET MVC application.
- Unhandled Exceptions: These occur when an exception is thrown and not caught by any try-catch block.
- HTTP Errors: These errors are caused by HTTP request failures, such as 404 (Not Found), 405 (Method Not Allowed), and 500 (Internal Server Error) etc.
- Custom Errors: These are application-specific errors that arise from logic or data handling issues.
2. Basic Configuration: Web.config
The web.config
file serves as a global configuration center for your application, including settings for error handling.
a. Custom Errors Configuration
You can configure the application to show a custom error page by modifying the <customErrors>
section within the web.config
:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error/GenericError">
<error statusCode="404" redirect="~/Error/NotFound"/>
<error statusCode="500" redirect="~/Error/ServerError"/>
</customErrors>
</system.web>
mode="On"
: Shows custom errors to all users.mode="RemoteOnly"
: Shows custom errors to remote users and detailed errors to local administrators (default).mode="Off"
: Shows detailed errors to all users.
b. HTTP Errors Configuration
For HTTP-level errors, the httpErrors
section in the web.config
can be utilized:
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<remove statusCode="500" />
<error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL" />
<error statusCode="500" path="/Error/ServerError" responseMode="ExecuteURL" />
</httpErrors>
</system.webServer>
errorMode="Custom"
: Specifies that custom responses are provided for errors.responseMode="ExecuteURL"
: The specified path is executed as a URL; for ASP.NET MVC, this is commonly used.
3. Using Filters
ASP.NET MVC uses action filters, result filters, and exception filters to handle errors at the controller level. Global error handling can also be achieved by registering these filters globally in the Global.asax
file.
a. Registering Filters Globally
In the Global.asax
file, use the RegisterGlobalFilters
method to add an exception filter globally:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
public static class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new CustomHandleErrorAttribute());
}
}
b. Custom Exception Filter
You can create a custom exception filter that inherits from HandleErrorAttribute
to handle errors globally:
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
Exception exception = filterContext.Exception;
// Log the exception
LogException(exception);
filterContext.ExceptionHandled = true; // Marks the exception as handled
// Redirect to error view
if (exception != null)
{
filterContext.Result = new ViewResult()
{
ViewName = "GenericError"
};
}
base.OnException(filterContext);
}
private void LogException(Exception ex)
{
// Implement your logging logic here
// For example: writing to a file, database, or external logging service
File.AppendAllText("ErrorLog.txt", DateTime.Now.ToString() + " - " + ex.Message + Environment.NewLine);
}
}
HandleErrorAttribute
: A built-in filter to handle exceptions.CustomHandleErrorAttribute
: A custom implementation that logs exceptions and redirects users to an error page.
4. Application-Level Error Handling
The Application_Error
method in the Global.asax
file acts as a global handler for unhandled exceptions.
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
if (exception != null)
{
// Log the exception
LogException(exception);
// Clear the error from server
Server.ClearError();
// Redirect to error page
Response.Redirect("/Error/GenericError");
}
}
private void LogException(Exception ex)
{
// Implement your logging logic here
File.AppendAllText("ErrorLog.txt", DateTime.Now.ToString() + " - " + ex.Message + Environment.NewLine);
}
Server.GetLastError()
: Retrieves the last unhandled exception.Server.ClearError()
: Clears the exception from the server, preventing ASP.NET from bubbling it up to the browser.Response.Redirect()
: Redirects to a custom error page.
5. Implementing a Custom Error Controller
Create an ErrorController
to handle specific error views. This approach keeps your error handling logic centralized and organized.
public class ErrorController : Controller
{
// GET: Error/GenericError
public ActionResult GenericError()
{
return View();
}
// GET: Error/NotFound
public ActionResult NotFound()
{
Response.StatusCode = 404;
return View("NotFound");
}
// GET: Error/ServerError
public ActionResult ServerError()
{
Response.StatusCode = 500;
return View("ServerError");
}
}
- GenericError: Handles generic errors.
- NotFound: Handles 404 errors.
- ServerError: Handles 500 errors.
6. Creating Error Views
Create corresponding views for each error action in the Error
controller. These views will be displayed to users when an error occurs.
GenericError.cshtml
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<h1>An error occurred</h1>
<p>We apologize for the inconvenience. Our team is working to resolve the issue.</p>
<p>Please try reloading the page or contacting support if the problem persists.</p>
</body>
</html>
NotFound.cshtml
<!DOCTYPE html>
<html>
<head>
<title>Page Not Found</title>
</head>
<body>
<h1>404 Error: Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
<p>Please check the URL and try again.</p>
</body>
</html>
ServerError.cshtml
<!DOCTYPE html>
<html>
<head>
<title>Server Error</title>
</head>
<body>
<h1>500 Error: Server Error</h1>
<p>An unexpected error has occurred on our server.</p>
<p>Please try reloading the page or contacting support if the problem persists.</p>
</body>
</html>
7. Testing Error Handling
Testing your error handling setup is crucial to ensure everything is working as expected.
a. Simulate Unhandled Exceptions
You can simulate an unhandled exception in a controller action to test your global exception handling:
public class HomeController : Controller
{
public ActionResult Index()
{
throw new Exception("Simulated Exception");
return View();
}
}
Accessing this action should redirect you to the GenericError
page, demonstrating that the exception was caught by Application_Error
and handled appropriately.
b. Simulate HTTP Errors
To test HTTP errors, you can deliberately call a non-existent route or pass an unsupported method.
public class HomeController : Controller
{
[HttpGet] // Only handles GET requests
public ActionResult Detail(string id)
{
// Assume the id is required
if (string.IsNullOrEmpty(id))
{
throw new HttpException(400, "Bad Request");
}
return View();
}
}
Accessing this action with a missing id
should result in a 400 Bad Request, which is caught by your custom error handling setup, redirecting to a relevant error page.
8. Advanced Considerations
a. Logging
Implement a robust logging mechanism to capture detailed information about exceptions. This can include stack traces, user information, and timestamp. Libraries like NLog, log4net, or Serilog can be used for advanced logging.
b. Email Notifications
Set up email notifications to alert the development team or administrators when critical exceptions occur. This can be achieved by adding email sending logic in your global error handling methods.
c. Error Pages for Different Environments
Consider showing different error messages or levels of detail based on the environment (development, staging, production). This can be controlled via configuration settings.
d. AJAX Error Handling
For AJAX requests, it’s important to handle errors gracefully without redirecting the entire page. The error filter can be adjusted to return JSON responses for AJAX requests:
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
Exception exception = filterContext.Exception;
if (exception != null && filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new { success = false, message = "An unexpected error occurred" }
};
filterContext.ExceptionHandled = true;
}
base.OnException(filterContext);
}
}
e. Custom Response Status Codes
Ensure that appropriate HTTP response status codes are returned for different types of errors to help search engines and clients understand why a request failed.
Conclusion
Implementing global error handling in ASP.NET MVC is essential for creating reliable and user-friendly web applications. By leveraging the web.config
, custom filters, and the Application_Error
method, you can effectively manage different types of errors across your application. Additionally, creating a dedicated Error
controller and implementing detailed logging and notification mechanisms will further enhance your error handling strategy.
By following this guide, you should be well-equipped to handle errors gracefully in your ASP.NET MVC applications, providing a more robust and user-friendly experience.