Application exception handling
.NET Core ASP.NET Core C# HTML MVC Razor Visual Studio

How to Trap and Handle Exceptions in .NET Core Applications

Welcome to today’s post.

In today’s post I will be showing you how to trap and handle exceptions in your .NET Core applications.

In a .NET Core there is built-in middleware that provides for the trapping and handling of exceptions that occur during the execution of the application or service.

In this post, I will show how to handle and encapsulate error messages and error properties within a Razor page and how to make your error page visible throughout your application. I will also show how to configure exception error handling within the application startup configuration.

In the first section, I will show how to configure the startup configuration to support exception handling.

Configuring Error Handling in the Application Startup

In the Configure() method within Startup.cs, you can conditionally setup exception handling using the IsDevelopment() method in the IHostingEnvironment class test as shown below:

public void Configure(IApplicationBuilder app, 
           IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDeveloperExceptionPage();
   }
   else
   {
      app.UseExceptionHandler("/Home/Error");
   }
	…
}

As a precaution, you should only enable the developer exception page when the application is running in the development environment. In production the developer exception page should NEVER be used.

In addition, to be able to re-use your error page within your application, you will need to display the error in your /View/Shared folder.

Defining an Error Page with a View Model

In this section I will show how we can define an error page as a view and model which can be included in a controller to provide basic error messages within other views within the application interface.

A simple error page (non-razor) is show below:

@model ErrorViewModel
@{
    ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
    <p>
        <strong>Request ID:</strong> <code>@Model.RequestId</code>
    </p>
}

</p>

The model definition for the error view is shown below:

using System;

namespace BookLoan.Models
{
    public class ErrorViewModel
    {
        public string RequestId { get; set; }

        public string ExceptionMessage { get; set; }

        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    }
}

In the next section, I will show how to implement a similar error view using a Razor page.

Defining an Error Page with a Razor Page

Another more extensible way of implementing an error handler is by creating a controller and scaffolded Razor page for the Error response. Then call the Error page from the controller with a user error message.

This is done as follows with the error page view:

@model BookLoan.Views.Common.ErrorModel;

@*@{
    ViewData["Title"] = "Error";
}*@

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
    <p>
        <strong>Request ID:</strong> <code>@Model.RequestId</code>
    </p>
}

@if (Model.ShowReferrer)
{
    <h3>Referrer</h3>
    <p>
        <a href="@Model.Referrer">@Model.Referrer</a>
    </p>
}

@if (@Model.ShowExceptionMessage)
{
    <h3>Exception Message</h3>
    <p>
        @Model.ExceptionMessage
    </p>
}

The code-behind for the error Razor page (generated as part of the scaffold) is shown here:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace BookLoan.Views.Common
{
    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public class ErrorModel : PageModel
    {
        public string RequestId { get; set; }
        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

        public string Referrer { get; set; }
        public bool ShowReferrer => !string.IsNullOrEmpty(Referrer);

        public string ExceptionMessage { get; set; }
        public bool ShowExceptionMessage => !string.IsNullOrEmpty(ExceptionMessage);

        public void OnGet()
        {
        }
    }
}

The controller method Error() that populates the above view with an error message and request identifier is shown below:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using BookLoan.Models;
using Microsoft.AspNetCore.Authorization;
using BookLoan.Views.Common;

namespace BookLoan.Controllers
{
    public class CommonController: Controller
    {
        [HttpGet]   
        public IActionResult Error(string errorMessage)
        {
            BookLoan.Views.Common.ErrorModel errorModel = new BookLoan.Views.Common.ErrorModel() 
            {
                ExceptionMessage = errorMessage,
                RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
            };
            ViewData["Title"] = "Error";
            return View(errorModel);
        }
    }
}

Model definition for the Error view is:

using System;

namespace BookLoan.Models
{
    public class ErrorViewModel
    {
        public string RequestId { get; set; }
        public string ExceptionMessage { get; set; }
        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    }
}

The class model definition ErrorViewModel for the error view is the same that we used for the view model in the previous section.

When the above error handling implementation for the Razor page with the view and controller is running, you will have an error page like this:

In the next section, I will show how to control the verbosity of error messages in your error message view.

Controlling Error Message Verbosity

To prevent verbose error messages for the custom error handler, check the environment as follows:

[HttpGet]
public async Task<ActionResult> Search(string BookGenre, 
   string SearchString)
{
   try
   {
      // Do some processing
   }
   catch (Exception ex)
   {
     if (Environment.GetEnvironmentVariable ("ASPNETCORE_ENVIRONMENT") == "Development")
     {
        return RedirectToAction("Error", "Common", new { errorMessage = ex.Message.ToString() } );
     }
     return RedirectToAction("Error", "Common", 
        new { errorMessage = 
          "Error running searching. Please contact support." 
     });
   }
}

To handle database exception pages to your middleware, use the UseDatabaseErrorPage() middleware method of IApplicationBuilder.

To use the Status Code Pages middleware to display a default error response use the UseStatusCodePages() middleware method of IApplicationBuilder.  

An example of a default error response is the 404 error:

Status Code: 404; Not Found

To redirect the error to a custom status code error page, use the UseStatusCodePagesWithRedirects() middleware method of IApplicationBuilder with a query string as shown:

app.UseStatusCodePagesWithRedirects("/StatusCode?code={0}");

In the next section, I will show to handle error status codes within the error page view.

Handling the Error Status Code within the Error Page

In this section, I will show how to display and format error status codes within the error page view so that the verbose errors that can potentially be a security issue are replaced with more user-friendly messages.

Below is the Razor page CSHTML markup for the error status code view:

@model BookLoan.Models.StatusCodeViewModel
@{
    ViewData["Title"] = "Status Code";
}

<h1 class="text-danger">Status Code: @Model.ErrorStatusCode</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
    <h3>Request ID</h3>
    <p>
        <code>@Model.RequestId</code>
    </p>
}

@if (Model.ShowOriginalURL)
{
    <h3>Original URL</h3>
    <p>
        <code>@Model.OriginalURL</code>
    </p>
}

Below is the Razor code behind:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace BookLoan.Views.Common
{
    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public class StatusCodeModel : PageModel
    {
        private readonly ILogger _logger;
        public string RequestId { get; set; }
        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
        public string ErrorStatusCode { get; set; }
        public string OriginalURL { get; set; }
        public bool ShowOriginalURL => !string.IsNullOrEmpty(OriginalURL);

        public StatusCodeModel(ILogger<StatusCodeModel> logger)
        {
            _logger = logger;
        }
        public void OnGet(string code)
        {
        }
    }
}

The controller code is shown below:

[HttpGet]
public IActionResult StatusCode(int? code = null)
{
   string referer = Request.Headers["Referer"].ToString();
   BookLoan.Models.StatusCodeViewModel statuscodeModel = new BookLoan.Models.StatusCodeViewModel()
   {
      OriginalURL = referer,
      ErrorStatusCode = "",
      ExceptionMessage = "An error has occurred",
      RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
   };
   if (code.HasValue)
   {
      statuscodeModel.ErrorStatusCode = code.ToString();
      ViewData["Title"] = "Error " + code.ToString();
      return View(statuscodeModel);
   }
   statuscodeModel.ExceptionMessage = "";
   return View(statuscodeModel);
}

The model definition for the status code is shown below:

using System;

namespace BookLoan.Models
{
    public class StatusCodeViewModel : ErrorViewModel
    {
        public string OriginalURL { get; set; }

        public bool ShowOriginalURL { get; set; }
        public string ErrorStatusCode { get; set; }
    }
}

When run, navigate to a non-existent URL. The following page is what you will see:

To summarize, you have seen how to trap exceptions globally and locally in your .NET Core applications.

That is all for today’s post.

I hope this post has been informative and useful for your applications.

Social media & sharing icons powered by UltimatelySocial