Feel like sharing?

Challenge

Default error handling for .NET Core MVC and Razor applications provide a simple way to redirect exceptions to a custom error page:

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

For an expanded explanation, check out Microsoft’s documentation page.

Error pages are great, but there are a few places where a redirect is not appropriate. Specifically, if you are implementing AJAX calls using either native XmlHttpRequest or using Razor’s Unobtrusive AJAX tied directly in to your HTML forms.

For those not familiar with Razor Unobtrusive AJAX, I recommend LearnRazorPages.com tutorial: Using jQuery Unobtrusive AJAX in ASP.NET Core Razor Pages.

Sometimes we forget about partial updates and how well the Microsoft stack inherently helps build Single Page Applications without the need for overly complex client side frameworks

When the end-user is posting data back to the server, if something goes, there are time when we don’t want to redirect away from the current page and form. In most modern client applications a more appropriate response is some kind of pop-up or notification on the current page. Redirecting the user to an error page and losing the user’s context is not good UX.

So our challenge then is to build on top of .NET Core’s exception handling capabilities but allow for additional support of AJAX requests.

Solution

The answer for adding AJAX support is by introducing our own ErrorHandlingMiddleware to the application and interrogating the HTTP request. If an AJAX call is being made, the request is going to have a header X-REQUESTED-WITH. By checking for the existence of this header and a specific value, we can determine how the request is made and then respond appropriately.

using System;
using System.Net;
using System.Runtime.Serialization.Formatters;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace WebApp
{
    public class MyErrorHandlingMiddleware
    {
        private readonly RequestDelegate next;

        public MyErrorHandlingMiddleware(RequestDelegate next) 
        { 
            this.next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }

        private static Task HandleExceptionAsync(HttpContext context, Exception ex)
        {
            var code = HttpStatusCode.InternalServerError; // 500 if unexpected

            // only change the result if this was an XmlHttp request, 
            // otherwise let the global Exceptionhandler move the page along.
            if (!string.IsNullOrEmpty(context.Request.Headers["x-requested-with"]))
            {
                if (context.Request.Headers["x-requested-with"][0]
                    .ToLower() == "xmlhttprequest")
                {               
                    var result = JsonConvert.SerializeObject(new {error = ex.Message});
                    context.Response.ContentType = "application/json";
                    context.Response.StatusCode = (int) code;
                    return context.Response.WriteAsync(result);
                }
            }

            context.Response.Redirect("/errors/500");
            return Task.FromResult<object>(null);

        }
    }
}

After adding the above code to your application, don’t forget to register it in Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TelemetryConfiguration telemetryConfiguration)
{
      if (env.IsDevelopment())
          app.UseDeveloperExceptionPage();
      else
      {
          app.UseExceptionHandler("/errors/500");
          app.UseHsts();
      }
      app.UseMiddleware<MyErrorHandlingMiddleware>();
}

Now if an exception is thrown in your application, the above middleware detects the type of request, and renders a Json result if it’s an AJAX request instead of redirecting to the error page.

Feel like sharing?

Last modified: May 10, 2020

Author