AutoWrapper
is a simple, yet customizable global HTTP
exception handler and response wrapper for ASP.NET Core APIs. It uses an ASP.NET Core middleware
to intercept 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 code specific requirements and let the wrapper automatically handle the HTTP
response. This can speedup the development time when building your APIs while enforcing own standards for your HTTP
responses.
- Exception handling
ModelState
validation error handling (support bothData Annotation
andFluentValidation
)- A configurable
API
exception - A consistent response format for
Result
andErrors
- A detailed
Result
response - A detailed
Error
response - A configurable
HTTP
StatusCodes
and messages - Add support for
Swagger
- Add Logging support for
Request
,Response
andExceptions
- A configurable middleware
options
to configure the wrapper. See Options section below for details. - Enable property name mappings for the default
ApiResponse
properties. - Add support for implementing your own user-defined
Response
andError
schema / object. - Add support for ignoring action methods that don't need to be wrapped using
[AutoWrapIgnore]
filter attribute. - Enable backwards compatibility support for
netcoreapp2.1
andnetcoreapp.2.2
.NET Core frameworks.
- Download and Install the latest
AutoWrapper.Core
from NuGet or via CLI:
PM> Install-Package AutoWrapper.Core -Version 3.0.0
- Declare the following namespace within
Startup.cs
using AutoWrapper;
- Register the
middleware
below within theConfigure()
method ofStartup.cs
"before" theUseRouting()
middleware
:
app.UseApiResponseAndExceptionWrapper();
That's simple! Here’s how the response is going to look like for the default ASP.NET Core API template “WeatherForecastController
” API:
{
"message": "Request successful.",
"isError": false,
"result": [
{
"date": "2019-09-16T23:37:51.5544349-05:00",
"temperatureC": 21,
"temperatureF": 69,
"summary": "Mild"
},
{
"date": "2019-09-17T23:37:51.554466-05:00",
"temperatureC": 28,
"temperatureF": 82,
"summary": "Cool"
},
{
"date": "2019-09-18T23:37:51.554467-05:00",
"temperatureC": 21,
"temperatureF": 69,
"summary": "Sweltering"
},
{
"date": "2019-09-19T23:37:51.5544676-05:00",
"temperatureC": 53,
"temperatureF": 127,
"summary": "Chilly"
},
{
"date": "2019-09-20T23:37:51.5544681-05:00",
"temperatureC": 22,
"temperatureF": 71,
"summary": "Bracing"
}
]
}
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:
[HttpPost]
public async Task<ApiResponse> Post([FromBody]CreateBandDTO band)
{
//Call a method to add a new record to the database
try
{
var result = await SampleData.AddNew(band);
return new ApiResponse("New record has been created to the database", result, 201);
}
catch (Exception ex)
{
//TO DO: Log ex
throw;
}
}
Running the code will give you the following result when successful:
{
"message": "New record has been created to the database",
"isError": false,
"result": 100
}
The ApiResponse
object has the following parameters that you can set:
ApiResponse(string message, object result = null, int statusCode = 200, string apiVersion = "1.0.0.0")
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:
throw new ApiException(ModelState.AllErrors());
throw new ApiException($"Record with id: {id} does not exist.", 400);
For example, let’s modify the POST
method with ModelState
validation:
[HttpPost]
public async Task<ApiResponse> Post([FromBody]CreateBandDTO band)
{
if (ModelState.IsValid)
{
//Call a method to add a new record to the database
try
{
var result = await SampleData.AddNew(band);
return new ApiResponse("New record has been created to the database", result, 201);
}
catch (Exception ex)
{
//TO DO: Log ex
throw;
}
}
else
throw new ApiException(ModelState.AllErrors());
}
Running the code will result to something like this when validation fails:
{
"isError": true,
"responseException": {
"exceptionMessage": "Request responded with validation error(s). Please correct the specified validation errors and try again.",
"validationErrors": [
{
"field": "Name",
"message": "The Name field is required."
}
]
}
}
See how the validationErrors
property is automatically populated with the violated fields
from your model.
The ApiException
object contains the following 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)
ApiException(object custom, int statusCode = 400)
Model
validations allows you to enforce pre-defined validation rules at a class
/property
level. You'd normally use this validation technique to keep a clear separation of concerns, so your validation code becomes much simpler to write, maintain, and test.
As you have already known, starting ASP.NET Core 2.1, it introduced the ApiController
attribute which performs automatic model state validation for 400 Bad Request
error. When the Controller
is decorated with ApiController
attribute, the framework will automatically register a ModelStateInvalidFilter
which runs on the OnActionExecuting
event. This checks for the Model State
validity and returns the response accordingly. This is a great feature, but since we want to return a custom response object instead of the 400 Bad Request
error, we will disable this feature in our case.
To disable the automatic model state validation, just add the following code at ConfigureServices()
method in Startup.cs
file:
public void ConfigureServices(IServiceCollection services) {
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
}
Use the AutoWrapperPropertyMap
attribute to map the AutoWrapper default property to something else. For example, let's say you want to change the name of the result
property to something else like data
, then you can simply define your own schema for mapping it like in the following:
public class MapResponseObject
{
[AutoWrapperPropertyMap(Prop.Result)]
public object Data { get; set; }
}
You can then pass the MapResponseObject
class to the AutoWrapper
middleware like this:
app.UseApiResponseAndExceptionWrapper<MapResponseObject>();
On successful requests, your response should now look something like this after mapping:
{
"message": "Request successful.",
"isError": false,
"data": {
"id": 7002,
"firstName": "Vianne",
"lastName": "Durano",
"dateOfBirth": "2018-11-01T00:00:00"
}
}
Notice that the default result
attribute is now replaced with the data
attribute.
Keep in mind that you are free to choose whatever property that you want to map. Here is the list of default properties that you can map:
[AutoWrapperPropertyMap(Prop.Version)]
[AutoWrapperPropertyMap(Prop.StatusCode)]
[AutoWrapperPropertyMap(Prop.Message)]
[AutoWrapperPropertyMap(Prop.IsError)]
[AutoWrapperPropertyMap(Prop.Result)]
[AutoWrapperPropertyMap(Prop.ResponseException)]
[AutoWrapperPropertyMap(Prop.ResponseException_ExceptionMessage)]
[AutoWrapperPropertyMap(Prop.ResponseException_Details)]
[AutoWrapperPropertyMap(Prop.ResponseException_ReferenceErrorCode)]
[AutoWrapperPropertyMap(Prop.ResponseException_ReferenceDocumentLink)]
[AutoWrapperPropertyMap(Prop.ResponseException_ValidationErrors)]
[AutoWrapperPropertyMap(Prop.ResponseException_ValidationErrors_Field)]
[AutoWrapperPropertyMap(Prop.ResponseException_ValidationErrors_Message)]
You can define your own Error
object and pass it to the ApiException()
method. For example, if you have the following Error
model with mapping configured:
public class MapResponseObject
{
[AutoWrapperPropertyMap(Prop.ResponseException)]
public object Error { get; set; }
}
public class Error
{
public string Message { get; set; }
public string Code { get; set; }
public InnerError InnerError { get; set; }
public Error(string message, string code, InnerError inner)
{
this.Message = message;
this.Code = code;
this.InnerError = inner;
}
}
public class InnerError
{
public string RequestId { get; set; }
public string Date { get; set; }
public InnerError(string reqId, string reqDate)
{
this.RequestId = reqId;
this.Date = reqDate;
}
}
You can then throw an error like this:
throw new ApiException(
new Error("An error blah.", "InvalidRange",
new InnerError("12345678", DateTime.Now.ToShortDateString())
));
The format of the output will now look like this:
{
"isError": true,
"error": {
"message": "An error blah.",
"code": "InvalidRange",
"innerError": {
"requestId": "12345678",
"date": "10/16/2019"
}
}
}
If mapping wont work for you and you need to add additional attributes to the default API
response schema, then you can use your own custom schema/model to achieve that by setting the UseCustomSchema
to true in AutoWrapperOptions
as shown in the following code below:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { UseCustomSchema = true });
Now let's say for example you wanted to have an attribute SentDate
and Pagination
object as part of your main API
response, you might want to define your API
response schema to something like this:
public class MyCustomApiResponse
{
public int Code { get; set; }
public string Message { get; set; }
public object Payload { get; set; }
public DateTime SentDate { get; set; }
public Pagination Pagination { get; set; }
public MyCustomApiResponse(DateTime sentDate, object payload = null, string message = "", int statusCode = 200, Pagination pagination = null)
{
this.Code = statusCode;
this.Message = message == string.Empty ? "Success" : message;
this.Payload = payload;
this.SentDate = sentDate;
this.Pagination = pagination;
}
public MyCustomApiResponse(DateTime sentDate, object payload = null, Pagination pagination = null)
{
this.Code = 200;
this.Message = "Success";
this.Payload = payload;
this.SentDate = sentDate;
this.Pagination = pagination;
}
public MyCustomApiResponse(object payload)
{
this.Code = 200;
this.Payload = payload;
}
}
public class Pagination
{
public int TotalItemsCount { get; set; }
public int PageSize { get; set; }
public int CurrentPage { get; set; }
public int TotalPages { get; set; }
}
To test the result, you can create a GET
method to something like this:
public async Task<MyCustomApiResponse> Get()
{
var data = await _personManager.GetAllAsync();
return new MyCustomApiResponse(DateTime.UtcNow, data,
new Pagination
{
CurrentPage = 1,
PageSize = 10,
TotalItemsCount = 200,
TotalPages = 20
});
}
Running the code should give you now the following response format:
{
"code": 200,
"message": "Success",
"payload": [
{
"id": 1,
"firstName": "Vianne Maverich",
"lastName": "Durano",
"dateOfBirth": "2018-11-01T00:00:00"
},
{
"id": 2,
"firstName": "Vynn Markus",
"lastName": "Durano",
"dateOfBirth": "2018-11-01T00:00:00"
},
{
"id": 3,
"firstName": "Mitch",
"lastName": "Durano",
"dateOfBirth": "2018-11-01T00:00:00"
}
],
"sentDate": "2019-10-17T02:26:32.5242353Z",
"pagination": {
"totalItemsCount": 200,
"pageSize": 10,
"currentPage": 1,
"totalPages": 20
}
}
That’s it. One thing to note here is that once you use your own schema for your API
response, you have the full ability to control how you would want to format your data, but at the same time losing some of the option configurations for the default API
Response. The good thing is you can still take advantage of the ApiException()
method to throw a user-defined error message.
The following properties are the available options that you can set:
BypassHTMLValidation
ReferenceLoopHandling
EnableResponseLogging
EnableExceptionLogging
IgnoreNullValue
UseCamelCaseNamingStrategy
UseCustomSchema
IsApiOnly
WrapWhenApiPathStartsWith
ApiVersion
ShowApiVersion
ShowStatusCode
IsDebug
if you want to show the API
version in the response, then you can do:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { ShowApiVersion = true });
The default API
version format is set to "1.0.0.0
"
If you wish to specify a different version format, then you can do:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { ShowApiVersion = true, ApiVersion = "2.0" });
if you want to show the StatusCode
in the response, then you can do:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { ShowStatusCode = true });
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
:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { IsDebug = true });
AutoWrapper
is meant to be used for ASP.NET Core API project templates only. If you are combining API Controllers
within your front-end projects like Angular, MVC, React, Blazor Server and other SPA frameworks that supports .NET Core, then use this property to enable it.
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { IsApiOnly = false} );
If you set the IsApiOnly
option to false
, you can also specify the segment of your API
path for validation. By default it was set to "/api"
. If you want to set it to something else, then you can do:
app.UseApiResponseAndExceptionWrapper( new AutoWrapperOptions {
IsApiOnly = false,
WrapWhenApiPathStartsWith = "/myapi"
});
This will activate the AutoWrapper
to intercept HTTP responses when a request contains the WrapWhenApiPathStartsWith
value.
Note that I would still recommend you to implement your
API Controllers
in a seperate project to value the separation of concerns and to avoid mixing route configurations for yourSPAs
andAPIs
.
You can use the [AutoWrapIgnore]
filter attribute for enpoints that you don't need to be wrapped.
For example:
[HttpGet]
[AutoWrapIgnore]
public async Task<IActionResult> Get()
{
var data = await _personManager.GetAllAsync();
return Ok(data);
}
or
[HttpGet]
[AutoWrapIgnore]
public async Task<IEnumerable<Person>> Get()
{
return await _personManager.GetAllAsync();
}
Another good thing about AutoWrapper
is that logging is already pre-configured. .NET Core apps has built-in logging mechanism by default, and any requests and responses that has been intercepted by the wrapper will be automatically logged (thanks to Dependency Injecton!). .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 automatically write the logs there for you.
You can turn off the default Logging by setting EnableResponseLogging
and EnableExceptionLogging
options to false
.
For example:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions {
EnableResponseLogging = false,
EnableExceptionLogging = false
});
Swagger provides an 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
omit any request with “/swagger
” in the URL
so you can still be able to navigate to the Swagger UI for your API documentation.
AutoWrapper
version 2.x also now supports both .NET Core 2.1 and 2.2. You just need to install the Nuget package Newtonsoft.json
first before AutoWrapper.Core
.
AutoWrapper.Server is simple library that enables you unwrap the Result
property of the AutoWrapper's ApiResponse
object in your C# .NET Client code. The goal is to deserialize the Result
object directly to your matching Model
without having you to create the ApiResponse schema.
For example:
[HttpGet]
public async Task<IEnumerable<PersonDTO>> Get()
{
var client = HttpClientFactory.Create();
var httpResponse = await client.GetAsync("https://localhost:5001/api/v1/persons");
IEnumerable<PersonDTO> persons = null;
if (httpResponse.IsSuccessStatusCode)
{
var jsonString = await httpResponse.Content.ReadAsStringAsync();
persons = Unwrapper.Unwrap<IEnumerable<PersonDTO>>(jsonString);
}
return persons;
}
For more information, see: AutoWrapper.Server
- AutoWrapper: Prettify Your ASP.NET Core APIs with Meaningful Responses
- AutoWrapper: Customizing the Default Response Output
- AutoWrapper.Server: Sample Usage
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.
Feel free to request an issue on github if you find bugs or request a new feature. Your valuable feedback is much appreciated to better improve this project. If you find this useful, please give it a star to show your support for this project.
Thank you!
- Vincent Maverick Durano - Blog
- ITninja04 - Github Profile
- 02/02/2020: AutoWrapper version
3.0.0
- added new options, bug fix and code cleanup. - 11/09/2019: AutoWrapper version
2.1.0
- added new options and features. - 11/05/2019: AutoWrapper version
2.0.2
- added UnAuthorize and BadRequest method response. - 10/17/2019: AutoWrapper version
2.0.1
- added new features. - 10/06/2019: AutoWrapper version
1.2.0
- refactor, cleanup and bugfixes for SPA support. - 10/04/2019: AutoWrapper version
1.1.0
- with newly added options. - 09/23/2019: AutoWrapper version
1.0.0
- offcial release. - 09/14/2019: AutoWrapper version
1.0.0-rc
- prerelease.
This project is licensed under the MIT License - see the LICENSE.md file for details