Exception Handling in ASP.Net Core Web API Application

Exception handling in the key feature in making an application robust and secure. Handling exceptions at different levels is always a tricky task. In previous versions of ASP.Net, we have multiple ways (Exception filter, Global.asax, OnException at Controller etc.,) to achieve exception handling. Fortunately ASP.Net Core made developers life easy by making global exception handling available through middleware. If default middleware doesn’t meet a project’s requirement, one can always create a custom middleware to handle exceptions.

NOTE: Updated this tutorial on 2/26/2017 with MSBuild based Dotnet SDK.

Create an ASP.Net Core Application using VS 2017. I am using following version of SDK which is based on MSBuild/CSProj (remember that previous versions of ASP.Net Core are based on Project.json/XProj.).

image

First add the following packages to project (remember everything in ASP.Net Core is modular, so specific API is available in specific nuget packages).

<ItemGroup>
  <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
  <PackageReference Include="Microsoft.AspNetCore" Version="1.0.3" />
  <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
  <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.2" />
  <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.1" />
  <PackageReference Include="Newtonsoft.Json" Version="10.0.1-beta1" />
</ItemGroup>

Create the following class.

public class Error
{
    public string Message { get; set; }
    public string Stacktrace { get; set; }
}

Now in Startup.cs, we will add following middleware in Configure method.

app.UseExceptionHandler(
     builder =>
     {
         builder.Run(
         async context =>
         {
             context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
             context.Response.ContentType = "application/json";
             var ex = context.Features.Get<IExceptionHandlerFeature>();
             if (ex != null)
             {
                 var err = JsonConvert.SerializeObject(new Error()
                 {
                     Stacktrace = ex.Error.StackTrace,
                     Message = ex.Error.Message
                 });
                 await context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes(err),0,err.Length).ConfigureAwait(false);
             }
         });
     }
);

In StudentController.cs, we will create a new endpoint to generate an exception.

[HttpGet("GenerateSampleError", Name = "GenerateSampleError")]
public IActionResult GenerateSampleError()
{
    int i = 0;
    var j = 1 / i;
    return new ObjectResult(j);
}

Lets run the solution and using postman tool execute the exception endpoint, we get following response.

image

UseExceptionHandler will capture all the exceptions in the application. Now what if we get exception in configure method itself. It can be handled with simple try…catch block. for demonstration, I am throwing an ArgumentNullException.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    try
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseExceptionHandler(
             builder =>
             {
                 builder.Run(
                 async context =>
                 {
                     context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                     context.Response.ContentType = "application/json";
                     var ex = context.Features.Get<IExceptionHandlerFeature>();
                     if (ex != null)
                     {
                         var err = JsonConvert.SerializeObject(new Error()
                         {
                             Stacktrace = ex.Error.StackTrace,
                             Message = ex.Error.Message
                         });
                         await context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes(err), 0, err.Length).ConfigureAwait(false);
                     }
                 });
             }
        );

        throw new ArgumentNullException("Sample", "This is sample exception");
        app.UseMvc();
    }
    catch (Exception ex)
    {
        app.Run(
          async context =>
          {
              context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
              context.Response.ContentType = "application/json";
              if (ex != null)
              {
                  var err = JsonConvert.SerializeObject(new Error()
                  {
                      Stacktrace = ex.StackTrace,
                      Message = ex.Message
                  });
                  await context.Response.Body.WriteAsync(Encoding.ASCII.GetBytes(err), 0, err.Length).ConfigureAwait(false);
              }
          });
    }
}

And when we execute any of the endpoint, we get following response.

image

Now what if we get an exception at the very early stage of application at Program.cs Main(), Startup.cs constructor and ConfigureServices methods. These exceptions can be logged using the standard try…catch block and can make the application up and running, but in my opinion one should not catch these exceptions instead let them break the application. The reason for my opinion is that all the services are registered at ConfigureServices method (and in fact application level configuration is setup in Startup.cs constructor and Program.cs Main method is the starting point of application), so typically if something breaks in these places, then there is no point in moving forward, because the services, configuration or host might be broken and without which application will not work as intended.

Having said that one can still have try….catch block to log the exception which occurs in these parts of the code and throw it back once logged. Please find below code.

public void ConfigureServices(IServiceCollection services)
{
    try
    {
        // Add framework services.
        services.AddApplicationInsightsTelemetry(Configuration);

        services.AddMvc();
        throw new ArgumentNullException("Sample", "This is sample exception");
        services.AddSingleton<IStudentOperations, StudentOperations>();
    }
    catch (Exception ex)
    {
        var err = JsonConvert.SerializeObject(new Error()
        {
            Stacktrace = ex.StackTrace,
            Message = ex.Message
        });
        System.IO.File.WriteAllText(@"exception.txt", err);
        throw;
    }
}

Above code will log the exception to a text file and make the application stop (because of throw). If we look at the the exception.txt file (it will be created at project root directory), we will have following content.

image

In the similar way we can log exception in Startup.cs Constructor and to that matter the same code can be used at Program.cs.

NOTE: The reason why I settled with text file is that, it is the least common log which we can trust to work for sure in case of disasters (especially in places like Program.cs, Startup.cs etc.,). Lets assume we want to log error to database which occurs in Main method of Program.cs, but the DB Service itself will not be initialized (it might be initialized in ConfigureServices of Startup.cs) by this point in Main method, and using it in turn will throw an exception. At this point, the least common available service would be the local file system. Hence used a text file to log the exception.

In next set of tutorials we will see how to server custom error pages for MVC application and error logging to database. Happy Coding and Stay Tuned!!!

You may also like...