ASP.NET Core – Exception Handling

Introduction

In this article, we are going to discuss ASP.NET CORE Exception Handling. This article can be used by the beginner, intermediate and professional.

Exception Handling

Exception handling is a very important feature of any programming language. Exception handling helps us to identify any unhandled issue in the code.

try-catch/Finally block can be used to handle exceptions, but it has some drawbacks like

  1. We need to specify try/catch/finally block throughout the application.
  2. If we missed, try/catch/finally block in any code that would be unhandled.

What would be the solution to that? The answer would be centralized error handling that will be able to handle all the errors of the project in a single place.

There are many ways available to do centralized exception handling in MVC 5, but the most popular method was Exception Filter.

Now next question is how you will achieve it in ASP.NET CORE.

The answer would be: Below are the few approaches available to handle exceptions in the Core.Net

  1. Custom Exception Handler page
  2. Exception Handler Lambda
  3. Exception Filter

We will discuss the “Custom Exception Handle Page” approach in this article and will learn other approaches in the subsequence articles.

Let's start,

Custom Exception Handler page

Follow the below steps to understand Custom Handler Exception for Development and production environment.

Step 1: Create Asp.net Core MVC application. 

Custom Exception Handler - ASP.Net Core

Click on next, Provide project name, Project location, and solution name.

Step 2 : Click on next again and select “Target Framework”.

Custom Exception Handler - ASP.Net Core

Click on the “Create” button to create a project.

Step 3: Please see the launchSetting.json file to check the environment pointing to. “Development” is set by default.

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:61272",
      "sslPort": 44392
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "MvcCoreDemo": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Step 4: Open the StartUp.cs file and notice the “Configure” method.

 using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MvcCoreDemo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

In the above code, you have seen that we have used middleware based on the environment. I mean “app.UseDeveloperExceptionPage()” for Development and use custom page(“app.UseExceptionHandler("/Home/Error")) for another environment.

Step 5: We will add some code to raise the error in the HomeController->Index action method.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MvcCoreDemo.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace MvcCoreDemo.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            throw new Exception("Error occared");
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Now execute the application and notice the output,

Custom Exception Handler - ASP.Net Core

As an application is pointed to the “development” environment, “app.UseDeveloperExceptionPage()” code will execute and the above error page will appear.

Step 6: Change the Environment to production and execute the application. We will expect to run “app.UseExceptionHandler("/Home/Error")” code and see error page.

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:61272",
      "sslPort": 44392
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    },
    "MvcCoreDemo": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    }
  }
}

Execute the application and see the error message.

Custom Exception Handler - ASP.Net Core

Now assume that you want to see more details about the production environment. This is not a real-world scenario for production but you might need to see more details in the development environment.
Let's change ErrorViewModel.cs, Home controller, and Error. cshtml files as per the below,

using System;

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

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

        public string ErrorMessage { get; set; }

        public string Source { get; set; }
        public string StackTrace { get; set; }
        public string ErrorPath { get; set; }
        public string InnerException { get; set; }
    }
}

HomeController

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MvcCoreDemo.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace MvcCoreDemo.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            throw new Exception("Error occared");
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            var execeptionHandlerPathFeture = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            return View(
                new ErrorViewModel
                {
                    RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
                    ErrorMessage = execeptionHandlerPathFeture.Error.Message,
                    Source = execeptionHandlerPathFeture.Error.Source,
                    ErrorPath = execeptionHandlerPathFeture.Path,
                    StackTrace = execeptionHandlerPathFeture.Error.StackTrace,
                    InnerException = Convert.ToString(execeptionHandlerPathFeture.Error.InnerException)
                }
                )  ;
        }
    }
}

Error.cshtml

@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>
    <strong>Error Message :</strong> <code>@Model.ErrorMessage</code>
</p>
<p>
    <strong>Source :</strong> <code>@Model.Source</code>
</p>
<p>
    <strong>ErrorPath :</strong> <code>@Model.ErrorPath</code>
</p>           
<p>
    <strong>StackTrace :</strong> <code>@Model.StackTrace</code>
</p>

Output

Custom Exception Handler - ASP.Net Core

You can see additional details added like stack trace, Error message, etc.

That’s all in this approach. We will learn other approaches in the upcoming article.