The community is working on translating this tutorial into Chinese, but it seems that no one has started the translation process for this article yet. If you can help us, then please click "More info".
Handling Exceptions
An error within a piece of software can be many things, but in more recent programming languages, they are commonly referred to as exceptions. An exception is often described as "an event during the execution of a program, that disrupts the normal flow" - in other words, something which was not expected and should be taken care of. This is called exception handling and it's a very important part of a modern piece of software, including a web application.
In the web world however, with a framework like ASP.NET Core, there are actually more than one type of error to deal with. There's exceptions, which we'll talk about in this article, and then there's the errors we inherit from the web/HTTP technologies. For instance, if the user tries to access a page which doesn't exist, a 404 error is returned to the visitor. A lot of webservers will have a default page for communicating this to the user, and if not, the browser will take care of it. However, the 404 error is a great example of an error which is not really an exception, that you want to handle gracefully within your application.
To the user/visitor, however, it won't make much of a difference what type of error or exception occurs - the only thing that matters is how you handle and recover from it, and how you present it to the user. So, in this article, we'll talk about exceptions and then we'll discuss the HTTP-based errors in the next article.
Exception handling
I won't go into too much details about exceptions in general, since that's more relevant in a C# tutorial, but let's briefly touch on the subject. First of all, if you already know that an exception might occur, e.g. because you're calling methods which may throw an exception, you should of course handle it locally, with a try..catch block:
public IActionResult Details(int id)
{
try
{
var product = DbHelper.GetProduct(id);
return View(product);
}
catch(Exception ex)
{
// Here you may want to log the exception and return an actual error page
return Content("Sorry, an error occurred!");
}
}
However, a lot of exceptions might be difficult to anticipate, so if you don't want to wrap all your code inside try..catch blocks, you need to handle exceptions at a higher level. Try opening the Startup.cs file found in your ASP.NET Core MVC project and have a look at the Configure() method. How it looks and what it does depends on the template you have used to create your project, but if you used the Empty template and you haven't changed it, you will likely find a few lines of code like these:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
....
They're basically telling your web application to use the special Developer Exception Page, as long as the application is running in development mode (more on that later). You will see the result of this when an exception is thrown, either because you use the throw keyword or because you do something which results in an exception:
public IActionResult Index()
{
string s = null;
return Content(s.Trim());
}
This will, obviously, result in a NullReferenceException - just try accessing the action in your browser and notice the result. Depending on the version of ASP.NET Core you're using, it will look something like this:
As you can see, ASP.NET generates a very detailed error page for you - from this, you should be able to pinpoint the problem (also when it's less obvious than in our example) and fix it. For security reasons, this amount of details should only be displayed to the developer(s) and NEVER to the user/visitor. Instead, when we're not in development mode, we should display a more user friendly error message to the user, probably with an apology, and then log the exception e.g. to a database.
The first part, where we display something user-friendly to the user, is easy. It can be done in many ways, but it all starts with the code we talked about before in the Configure() method of Startup.cs. We need to modify it so that it handles situations where the application is not in development mode, like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
....
With the help from the UseExceptionHandler() method, we can redirect the user to a specific path when an error occurs. In this case, we send them to "/Error", so we need a method to handle this path. One approach would be to either define an action on the HomeController called Error() or to create an ErrorController and define an Index() method. I have used the latter approach and it looks like this:
public class ErrorController : Controller
{
public IActionResult Index()
{
return Content("We're so sorry, but an error just occurred! We'll try to get it fixed ASAP!");
}
}
In a real world application, this should of course be replaced with a nice looking View, which can even use the same Layout as the rest of the page!
Exception details
We can now handle an unexpected exception more gracefully, but we still don't know what went wrong. Since we don't display any details to the user, we need to handle it before we tell the user that something went wrong. To access information about the exception, we can use the IExceptionHandlerPathFeature interface. It can be found in the Microsoft.AspNetCore.Diagnostics namespace, so you will need to import this where you handle the exception, e.g. in the ErrorController:
using Microsoft.AspNetCore.Diagnostics;
Now we can change the Index() action to actually handle the exception, before telling the user about it:
public class ErrorController : Controller
{
public IActionResult Index()
{
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if((exceptionHandlerPathFeature != null) && (exceptionHandlerPathFeature.Error != null))
{
// In our example, the ExceptionHelper.LogException() method will take care of
// logging the exception to the database and perhaps even alerting the webmaster
// Make sure that this method doesn't throw any exceptions or you might end
// in an endless loop!
ExceptionHelper.LogException(exceptionHandlerPathFeature.Error);
}
return Content("We're so sorry, but an error just occurred! It has been logged and we'll try to get it fixed ASAP!");
}
}
With all this in place, your application is now more robust and ready for the unanticipated exceptions which will likely occur.
Summary
In this article, we've learned how to deal with exceptions, whether they are anticipated or not. This is an important step in creating a robust web application and you should always have a strategy for this before your web application is released to the public.
Another thing you should consider is how to deal with the various HTTP status codes which your application will be returning to the user, e.g. the 404 code when a page is not found - we'll discuss this in the next article.