AutoWrapper - Prettify Your ASP.NET Core APIs With Meaningful Responses

In this article, you will learn about AutoWrapper - prettify your ASP.NET Core APIs with meaningful responses.

Introduction

 
When building APIs for “real” application projects, many developers forgot about the importance of providing a meaningful response to their consumers. There are a few reasons why this can happen; could be that their development time is limited, they don't have a standard format for HTTP response or simply they just don't care about the response for as long as the API will return needed data to the consumer. Well, API’s are not just about passing JSON back and forth over HTTP, but also how you present meaningful responses to the developers who consume it.
 
As someone once told…
“A good API design is a UX for developers who consume it.”
 
As API developers who value consumers, we want to give meaningful and consistent API responses to them.
 
ASP.NET Core gives us the ability to create REST APIs in just a snap; however, they do not provide a consistent response for successful requests and errors out of the box. If you are taking a RESTful approach to your API’s, then you will be utilizing HTTP verbs such as GET, POST, PUT and DELETE. Each of this action may return different types depending on how your endpoint is designed. Your POST, PUT and DELETE endpoints may return a data or not at all. Your GET endpoint may return a string, a List<T>, an IEnumerable of some type or even an object. On the other hand, if your API throws an error, it will return an object or worst an HTML string stating the cause of the error. The differences among all of these responses make it difficult to consume the API because the consumer needs to know the type and structure of the data that is being returned in each case. Both the client code and the service code become difficult to manage.
 
Last year, I created a couple of Nuget packages for managing exceptions and response consistency using a custom object wrapper for restful APIs.
 
I was amazed that both packages had hundreds of downloads now, and were used by other open-source projects, such as the Blazor Boilerplate.
 
While I consider both packages a success, still there are a few glitches with them and so I have decided to create a new package to refactor the code base, apply bug fixes and add new features to it.
 
In this post, we will take a look at how we can beautify our ASP.NET Core API responses using AutoWrapper.
 

The Default ASP.NET Core API Response

 
When you create a new ASP.NET Core API template, Visual Studio will scaffold all the necessary files and dependencies to help you get started building RESTful APIs. The generated template includes a “WeatherForecastController” to simulate a simple GET request using static data, as shown in the code below.
  1. [ApiController]  
  2. [Route("[controller]")]  
  3. public class WeatherForecastController : ControllerBase  
  4. {  
  5.     private static readonly string[] Summaries = new[]  
  6.     {  
  7.             "Freezing""Bracing""Chilly""Cool""Mild""Warm""Balmy""Hot""Sweltering""Scorching"  
  8.         };  
  9.   
  10.     private readonly ILogger<WeatherForecastController> _logger;  
  11.   
  12.     public WeatherForecastController(ILogger<WeatherForecastController> logger)  
  13.     {  
  14.         _logger = logger;  
  15.     }  
  16.   
  17.     [HttpGet]  
  18.     public IEnumerable<WeatherForecast> Get()  
  19.     {  
  20.         var rng = new Random();  
  21.         return Enumerable.Range(1, 5).Select(index => new WeatherForecast  
  22.         {  
  23.             Date = DateTime.Now.AddDays(index),  
  24.             TemperatureC = rng.Next(-20, 55),  
  25.             Summary = Summaries[rng.Next(Summaries.Length)]  
  26.         })  
  27.         .ToArray();  
  28.     }  
  29. }  
 When you run the application, you will be presented with the following output in JSON format.
  1. [  
  2.     {  
  3.         "date""2019-09-16T13:08:39.5994786-05:00",  
  4.         "temperatureC": -8,  
  5.         "temperatureF": 18,  
  6.         "summary""Bracing"  
  7.     },  
  8.     {  
  9.         "date""2019-09-17T13:08:39.5995153-05:00",  
  10.         "temperatureC": 54,  
  11.         "temperatureF": 129,  
  12.         "summary""Cool"  
  13.     },  
  14.     {  
  15.         "date""2019-09-18T13:08:39.5995162-05:00",  
  16.         "temperatureC": 33,  
  17.         "temperatureF": 91,  
  18.         "summary""Bracing"  
  19.     },  
  20.     {  
  21.         "date""2019-09-19T13:08:39.5995166-05:00",  
  22.         "temperatureC": 38,  
  23.         "temperatureF": 100,  
  24.         "summary""Balmy"  
  25.     },  
  26.     {  
  27.         "date""2019-09-20T13:08:39.599517-05:00",  
  28.         "temperatureC": -3,  
  29.         "temperatureF": 27,  
  30.         "summary""Sweltering"  
  31.     }  
  32. ]  
That’s great and the API works just like what we expect, except that it doesn’t give us a meaningful response. We understand that the data is the very important part of the response, however, spitting out just the data as the JSON response isn’t really that helpful at all, especially when there’s an unexpected behavior that happens between each request.
 
For example, the following code simulates an unexpected error that could happen in your code:
  1. //This is just an idiot example to trigger an error  
  2. int num = Convert.ToInt32("a");  
The code above tries to convert a string that contains non-numeric values into an integer type, which will cause an error at runtime. The response output is going to look something like this,
 
AutoWrapper - Prettify Your ASP.NET Core APIs With Meaningful Responses
Figure 1: Unhandled Exception
 
Yuck!
 
Can you imagine the disappointment of the developers who will consume your API seeing this format from the response? Well at least the stack trace information is somewhat useful because it gives you the idea about the cause of the error, but that should be handled and never let your API consumers see that information for security risks. Stack trace information is helpful during the development stage and debugging. In production, detailed errors like this should be handled, log them somewhere for analysis and return a meaningful response back to the consumer.
 
Another scenario is when you are trying to access an API endpoint that doesn’t exist gives you nothing in return other than the famous 404 (Not Found) Http Status Code.
When working with REST APIs, it is important to handle exceptions and return consistent responses for all the requests that are processed by your API regardless of success or failure. This makes it a lot easier to consume the API, without requiring complex code on the client.
 

AutoWrapper.Core to the Rescue

 
AutoWrapper takes care of the incoming HTTP requests and automatically wraps the responses for you by providing a consistent response format for both successful and error results. The goal is to let you focus on your business specific requirements and let the wrapper handles the HTTP response. Imagine the time you save from developing your APIs.
 
AutoWrapper is a project fork based from VMD.RESTApiResponseWrapper.Core which is designed to support .NET Core 3.x and above. The implementation of this package was refactored to provide a more convenient way to use the middleware with added flexibilities.
 
AutoWrapper has the following main features,
  • Exception handling
  • ModelState validation error handling (support both Data Annotation and FluentValidation)
  • A configurable API exception
  • A consistent response format for Result and Errors
  • A detailed Result response
  • A detailed Error response
  • A configurable HTTP StatusCodes and messages
  • Add support for Swagger
  • Add Logging support for Request, Response, and Exceptions
  • Add options in the middleware to set ApiVersion and IsDebug properties

TL, DR. Show Me the Code

 
With just a few steps, you can turn your API Controller to return something meaningful response without doing much development effort on your part. All you have to do is:
  1. Download and Install the latest AutoWrapper.Core from NuGet or via CLI,

    PM> Install-Package AutoWrapper.Core -Version 1.0.1-rc

    Note
    This is a prerelease version at the moment and will be released officially once .NET Core 3 is out.
  1. Declare the following namespace within Startup.cs
    1. using AutoWrapper;  
  1. Register the middleware below within the Configure() method of Startup.cs "before" the UseRouting() middleware:
    1. app.UseApiResponseAndExceptionWrapper();  
    The default API version format is set to "1.0.0.0". If you wish to specify a different version format for your API, then you can do:
    1. app.UseApiResponseAndExceptionWrapper(new ApiResponseOptions { ApiVersion = "2.0" });  
That's simple! Now try to build and run the ASP.NET Core API default application again. Based on our example, here’s how the response is going to look like for the “WeatherForecastController” API,
  1. {  
  2.     "version""1.0.0.0",  
  3.     "statusCode": 200,  
  4.     "message""Request successful.",  
  5.     "isError"false,  
  6.     "result": [  
  7.         {  
  8.             "date""2019-09-16T23:37:51.5544349-05:00",  
  9.             "temperatureC": 21,  
  10.             "temperatureF": 69,  
  11.             "summary""Mild"  
  12.         },  
  13.         {  
  14.             "date""2019-09-17T23:37:51.554466-05:00",  
  15.             "temperatureC": 28,  
  16.             "temperatureF": 82,  
  17.             "summary""Cool"  
  18.         },  
  19.         {  
  20.             "date""2019-09-18T23:37:51.554467-05:00",  
  21.             "temperatureC": 21,  
  22.             "temperatureF": 69,  
  23.             "summary""Sweltering"  
  24.         },  
  25.         {  
  26.             "date""2019-09-19T23:37:51.5544676-05:00",  
  27.             "temperatureC": 53,  
  28.             "temperatureF": 127,  
  29.             "summary""Chilly"  
  30.         },  
  31.         {  
  32.             "date""2019-09-20T23:37:51.5544681-05:00",  
  33.             "temperatureC": 22,  
  34.             "temperatureF": 71,  
  35.             "summary""Bracing"  
  36.         }  
  37.     ]  
  38. }  
Sweet!
 
If you noticed, the output now contains a few properties in the response such as the version, statusCode, message, isError and the actual data being contained in the result property.
 
Another good thing about AutoWrapper is that logging is already preconfigured. .NET Core apps has built-in logging mechanism by default and any requests and responses that have been intercepted by the wrapper will be automatically logged. For this example, it will show something like this in your Visual Studio console window,
 
AutoWrapper - Prettify Your ASP.NET Core APIs With Meaningful Responses
Figure 2: Visual Studio Console logs
 
.NET Core supports a logging API that works with a variety of built-in and third-party logging providers. Depending on what supported .NET Core logging provider you use and how you configure the location to log the data (e.g text file, Cloud, etc. ), AutoWrapper will write the logs there for you automatically. 
 
Here’s another example of an output when you try to point to a URL that doesn’t exist,
  1. {  
  2.     "version""1.0.0.0",  
  3.     "statusCode": 404,  
  4.     "isError"true,  
  5.     "responseException": {  
  6.         "exceptionMessage""Request not found. The specified uri does not exist.",  
  7.         "details"null,  
  8.         "referenceErrorCode"null,  
  9.         "referenceDocumentLink"null,  
  10.         "validationErrors"null  
  11.     }  
  12. }  
Now, noticed how the response object was changed? The StatusCode was automatically set to 404. The result property was automatically omitted when any unexpected error or exception has occurred, and display the responseException property instead for showing the error messages and extra information.
 
Keep in mind that any errors or exceptions will also be logged. For example, if we run the following code again,
  1. //This is just an idiot example to trigger an error  
  2. int num = Convert.ToInt32("a");  
It will now give you the following response.
  1. {  
  2.     "version""1.0.0.0",  
  3.     "statusCode": 500,  
  4.     "isError"true,  
  5.     "responseException": {  
  6.         "exceptionMessage""Unhandled Exception occured. Unable to process the request.",  
  7.         "details"null,  
  8.         "referenceErrorCode"null,  
  9.         "referenceDocumentLink"null,  
  10.         "validationErrors"null  
  11.     }  
  12. }  
And the console window will display something like this.
 
AutoWrapper - Prettify Your ASP.NET Core APIs With Meaningful Responses
Figure 3: Visual Studio Console logs
 
By default, AutoWrapper suppresses stack trace information. If you want to see the actual details of the error from the response during the development stage, then simply set the AutoWrapperOptions IsDebug to True.
  1. app.UseApiResponseAndExceptionWrapper( new AutoWrapperOptions { IsDebug = true });  
Now, when you run the application again to trigger an exception, it will now show something like this.
  1.     "version""1.0.0.0",  
  2.     "statusCode": 500,  
  3.     "isError"true,  
  4.     "responseException": {  
  5.         "exceptionMessage"" Input string was not in a correct format.",  
  6.         "details": "   at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)\r\n   at System.Number.ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)\r\n   at System.Convert.ToInt32(String value)\r\n   at AutoWrapperDemo.Controllers.WeatherForecastController.Get() in . . . . . . .,  
  7.         "referenceErrorCode"null,  
  8.         "referenceDocumentLink"null,  
  9.         "validationErrors"null  
  10.     }  
  11. }  
Noticed that the real exception message and its details are now shown.
 

Defining Your Own Custom Message

 
To display a custom message in your response, use the ApiResponse object from AutoWrapper.Wrappers namespace. For example, if you want to display a message when a successful POST has been made, then you can do something like this,
  1. [HttpPost]  
  2. public async Task<ApiResponse> Post([FromBody]CreateBandDTO band)  
  3. {  
  4.     //Call a method to add a new record to the database  
  5.     try  
  6.     {  
  7.         var result = await SampleData.AddNew(band);  
  8.         return new ApiResponse("New record has been created to the database", result, 201);  
  9.     }  
  10.     catch (Exception ex)  
  11.     {  
  12.         //TO DO: Log ex  
  13.         throw;  
  14.     }  
  15. }  
Running the code will give you the following result when successful:
  1. {  
  2.     "version""1.0.0.0",  
  3.     "statusCode": 201,  
  4.     "message""New record has been created to the database",  
  5.     "isError"false,  
  6.     "result": 100  
  7. }  
The ApiResponse object has the following parameters that you can set,
  1. ApiResponse(string message, object result = nullint statusCode = 200, string apiVersion = "1.0.0.0")  

Defining Your Own Api Exception

 
AutoWrapper also provides an ApiException object that you can use to define your own exception. For example, if you want to throw your own exception message, you could simply do,
 
For capturing ModelState validation errors
  1. throw new ApiException(ModelState.AllErrors());  
For throwing your own exception message
  1. throw new ApiException($"Record with id: {id} does not exist.", 400);  
For example, let’s modify the POST method with ModelState validation,
  1. [HttpPost]  
  2. public async Task<ApiResponse> Post([FromBody]CreateBandDTO band)  
  3. {  
  4.     if (ModelState.IsValid)  
  5.     {  
  6.         //Call a method to add a new record to the database  
  7.         try  
  8.         {  
  9.             var result = await SampleData.AddNew(band);  
  10.             return new ApiResponse("New record has been created to the database", result, 201);  
  11.         }  
  12.         catch (Exception ex)  
  13.         {  
  14.             //TO DO: Log ex  
  15.             throw;  
  16.         }  
  17.     }  
  18.     else  
  19.         throw new ApiException(ModelState.AllErrors());  
  20. }  
  21. Running the code will result to something like this when validation fails:  
  22. {  
  23.     "version""1.0.0.0",  
  24.     "statusCode": 400,  
  25.     "isError"true,  
  26.     "responseException": {  
  27.         "exceptionMessage""Request responded with validation error(s). Please correct the specified validation errors and try again.",  
  28.         "details"null,  
  29.         "referenceErrorCode"null,  
  30.         "referenceDocumentLink"null,  
  31.         "validationErrors": [  
  32.             {  
  33.                 "field""Name",  
  34.                 "message""The Name field is required."  
  35.             }  
  36.         ]  
  37.     }  
  38. }  
See how the validationErrors property is automatically populated with the violated fields from your model.
 
The ApiException object contains the following three overload constructors that you can use to define an exception,
  • ApiException(string message, int statusCode = 500, string errorCode = "", string refLink = "")
  • ApiException(IEnumerable<ValidationError> errors, int statusCode = 400)
  • ApiException(System.Exception ex, int statusCode = 500)

Support for Swagger

 
Swagger provides advance documentation for your APIs where it allows developers to reference the details of your API endpoints and test them when necessary. This is very helpful especially when your API is public and you expect many developers to use it.
 
AutoWrapper omits any request with “/swagger” in the URL so you can still be able to navigate to the Swagger UI for your API documentation.
 

Summary

 
In this article, we’ve learned how to integrate and use the core features of AutoWrapper in your ASP.NET Core application.
 
I’m pretty sure there are still lots of things to improve in this project, so feel free to try it out and let me know your thoughts. Comments and suggestions are welcome, please drop a message and I’d be happy to answer any queries as I can.
 
UPDATE - AutoWrapper version 1.0.0 is now officially released with some added option configurations to it. 
 
Source code can be found here.
 
References