ASP.NET Core 2.1 middlewares part 1: building a custom middleware

 

Introduction

As part of an ASP.NET Core 2.1 application, middleware makes up the processing chain (pipeline) of HTTP requests. They interact on the response and the HTTP request.

Each middleware can call the following middleware. They can perform operations before AND after their successor.

It is important to understand that the HTTP response is built only when the request is passed in this middleware suite.

This series of articles aims to describe how to build custom middlewares and how to unit test them.

So in a typical ASP.NET Core Startup, we simply use the framework default middlewares:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDeveloperExceptionPage();
   }
   app.UseMvc(); 
}

How to create a middleware

Creating a middleware is really very simple. The IApplicationBuilder interface provides three methods for doing this.

  • Run
  • Map
  • Use

I will just describe in this article the Use method because it allows to create a class we will “use” as a middleware, instead of Run and Map that require an inline declaration.

First, the class must contain an Invoke method that takes an HttpContext parameter and returns a Task. In addition, our class must have a constructor with at least one parameter to use the next middleware (the next method). This one is of type RequestDelegate. However, the constructor may have other parameters. These parameters will be passed to him when adding the middleware in the HTTP pipeline. This addition is done using the UseMiddleware method.

Example with a custom middleware that handle errors:

public class CustomExceptionMiddleware
{
   private readonly RequestDelegate _next;

   public CustomExceptionMiddleware(RequestDelegate next)
   {
      _next = next;
   }

   public async Task Invoke(HttpContext context)
   {
      try
      {
         await _next.Invoke(context);
      }
      catch (Exception ex)
      {
         await HandleExceptionAsync(context, ex);
      }
   }

   private async Task HandleExceptionAsync(HttpContext context, Exception exception)
   {
      var response = context.Response;
      var customException = exception as BaseCustomException;
      var statusCode = (int)HttpStatusCode.InternalServerError;
      var message = "Unexpected error";
      var description = "Unexpected error";

      if (null != customException)
      {
         message = customException.Message;
         description = customException.Description;
         statusCode = customException.Code;
      }

      response.ContentType = "application/json";
      response.StatusCode = statusCode;
      await response.WriteAsync(JsonConvert.SerializeObject(new CustomErrorResponse
      {
         Message = message,
         Description = description
      }));
   }
}

This middleware handle all errors and control the answer to the client: it sends a custom error message.

There are 2 cases :

First case: it’s a custom error that we know what to do with and what to send to the client BaseCustomException

Second case: it’s an unmanaged error that we don’t know what to do with, we will send a generic error message to the client.

Here is the complete implementation of custom errors, DTO and exception raising:

public class BaseCustomException : Exception
{
   private int _code;
   private string _description;

   public int Code
   {
      get => _code;
   }
   public string Description
   {
      get => _description;
   }

   public BaseCustomException(string message, string description, int code) : base(message)
   {
      _code = code;
      _description = description;
   }
}
public class CustomErrorResponse
{
   public string Message { get; set; }
   public string Description { get; set; }
}
public class NotFoundCustomException : BaseCustomException
{
   public NotFoundCustomException(string message, string description) : base(message, description, (int)HttpStatusCode.NotFound)
   {
   }
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
   [HttpGet]
   public ActionResult<IEnumerable<string>> Get()
   {
      throw new Exception();
      return new string[] { "value1", "value2" };
   }

   [HttpGet("{id}")]
   public ActionResult<string> Get(int id)
   {
      throw new NotFoundCustomException("No data found", $"Please check your parameters id: {id}");
      return "value";
   }
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDeveloperExceptionPage();
   }
   app.UseMiddleware<CustomExceptionMiddleware>();
   app.UseMvc();
}

Demo

Case 1: Custom error scenario

Case 2: Unhandled error scenario

Fabulous isn’t it ? 😉

Now let’s go to part 2

ASP.NET Core 2.1 middlewares part 2: Unit test a custom middleware

 

How to unit test a middleware ?

It’s very easy to unit test a middleware in ASP.NET Core.

There three important things to understand here.

First the Invoke method takes in parameter an HttpContext instance.

We all know how it’s painful to mock an HttpContext… We don’t need! ASP.NET Core provide a “fake” HttpContext named DefaultHttpContext. Feeling better now ? 🙂

Second thing to know, if you need to unit test the Response body property you have to initialize it tourslef like this:

var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();

and just before reading the content, reset the body’s stream position back to 0.

context.Response.Body.Seek(0, SeekOrigin.Begin);

And finally, the RequestDelegate object passed in parameter of the middleware’s constructor is simply a delegate, Action exactly.

We are ready to unit test our custom middleware

In these scenarii, we will use XUnit and FluentAssertion

Case 1: scenario with a custom error:

public class CustomExceptionMiddlewareTests
{
   [Fact]
   public async Task WhenACustomExceptionIsRaised_CustomExceptionMiddlewareShouldHandleItToCustomErrorResponseAndCorrectHttpStatus()
   {
      // Arrange
      var middleware = new CustomExceptionMiddleware((innerHttpContext) =>
      {
         throw new NotFoundCustomException("Test", "Test");
      });

      var context = new DefaultHttpContext();
      context.Response.Body = new MemoryStream();

      //Act
      await middleware.Invoke(context);

      context.Response.Body.Seek(0, SeekOrigin.Begin);
      var reader = new StreamReader(context.Response.Body);
      var streamText = reader.ReadToEnd();
      var objResponse = JsonConvert.DeserializeObject<CustomErrorResponse>(streamText);

      //Assert
      objResponse
      .Should()
      .BeEquivalentTo(new CustomErrorResponse { Message = "Test", Description = "Test" });

      context.Response.StatusCode
      .Should()
      .Be((int)HttpStatusCode.NotFound);
   }
}

Case 2: scenario with an unhandled error:

public class CustomExceptionMiddlewareTests
{
   [Fact]
   public async Task WhenAnUnExpectedExceptionIsRaised_CustomExceptionMiddlewareShouldHandleItToCustomErrorResponseAndInternalServerErrorHttpStatus()
   {
      // Arrange
      var middleware = new CustomExceptionMiddleware(next: (innerHttpContext) =>
      {
         throw new Exception("Test");
      });

      var context = new DefaultHttpContext();
      context.Response.Body = new MemoryStream();

      //Act
      await middleware.Invoke(context);

      context.Response.Body.Seek(0, SeekOrigin.Begin);
      var reader = new StreamReader(context.Response.Body);
      var streamText = reader.ReadToEnd();
      var objResponse = JsonConvert.DeserializeObject<CustomErrorResponse>(streamText);

     //Assert
     objResponse
     .Should()
     .BeEquivalentTo(new CustomErrorResponse { Message = "Unexpected error", Description = "Unexpected error" });

     context.Response.StatusCode
     .Should()
     .Be((int)HttpStatusCode.InternalServerError);
   }
}

 

Easy isn’t it ? 😉