Preface
AutoWrapper is a simple wrapper that customizes global exception handlers and Core API responses. He uses Core middleware to intercept incoming HTTP requests and automatically wrap the final result in a unified format. The purpose is mainly to make us pay more attention to business-specific code requirements and let the wrapper automatically handle HTTP responses. This can speed up development time when building APIs, while trying our unified standards for HTTP responses.
Install
Download and install from NuGet or via CLI
PM> Install-Package
Register the following in the Configure method, but remember to put it before UseRouting
();
Start attribute mapping
By default, AutoWrapper will output the following format when a successful request is successful:
{ "message": "Request successful.", "isError": false, "result": [ { "id": 7002, "firstName": "Vianne", "lastName": "Durano", "dateOfBirth": "2018-11-01T00:00:00" } ] }
If we don't like the default attribute naming method, we can map to any name we need to specify through the AutoWrapperPropertyMap property. For example, can I change the name of the result attribute to data. As shown below
public class MapResponseObject { [AutoWrapperPropertyMap()] public object Data { get; set; } }
Then pass the MapResponseObject class to the AutpWrapper middleware
<MapResponseObject>();
After re-requesting through mapping, the impact format is now shown below
{ "message": "Request successful.", "isError": false, "data": { "id": 7002, "firstName": "Vianne", "lastName": "Durano", "dateOfBirth": "2018-11-01T00:00:00" } }
It can be seen from this that the result attribute has been replaced with the data attribute
By default, when an exception occurs in AutoWrapper, the following response format will be emitted.
{ "isError": true, "responseException": { "exceptionMessage": "Unhandled Exception occurred. Unable to process the request." } }
And if IsDebug is set in AutoWrapperOptions, similar information with stack trace information will be generated
{ "isError": true, "responseException": { "exceptionMessage": " Input string was not in a correct format.", "details": " at (ParsingStatus status, TypeCode type)\r\n at .ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)\r\n …" } }
If you want to change some APIError attribute names to other names, just add the following mapMapResponseObject in the following code
public class MapResponseObject { [AutoWrapperPropertyMap()] public object Error { get; set; } [AutoWrapperPropertyMap(Prop.ResponseException_ExceptionMessage)] public string Message { get; set; } [AutoWrapperPropertyMap(Prop.ResponseException_Details)] public string StackTrace { get; set; } }
Simulate errors with the following code
int num = Convert.ToInt32("10s");
The output after mapping is now as follows
{ "isError": true, "error": { "message": " Input string was not in a correct format.", "stackTrace": " at (ParsingStatus status, TypeCode type)\r\n at .ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)\r\n …" } }
Note that APIError now changes the default properties of the model based on properties defined in the MapResponseObject class.
We can freely choose to map any attribute. Below is a list of corresponding attributes for mapping attributes.
[AutoWrapperPropertyMap()] [AutoWrapperPropertyMap()] [AutoWrapperPropertyMap()] [AutoWrapperPropertyMap()] [AutoWrapperPropertyMap()] [AutoWrapperPropertyMap()] [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)]
Custom error architecture
AutoWrapper also provides an APIException object that can be used to define its own exception. If you want to throw your own exception message, you can simply do the following.
throw new ApiException("Error blah", 400, "511", "/error/511");
The default output format is as follows
{ "isError": true, "responseException": { "exceptionMessage": "Error blah", "referenceErrorCode": "511", "referenceDocumentLink": "/error/511" } }
Of course we can customize the error format
public class MapResponseObject { [AutoWrapperPropertyMap()] 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) { = message; = code; = inner; } } public class InnerError { public string RequestId { get; set; } public string Date { get; set; } public InnerError(string reqId, string reqDate) { = reqId; = reqDate; } }
Then we can raise our error through the following code
throw new ApiException( new Error("An error blah.", "InvalidRange", new InnerError("12345678", ()) ));
The output format is as follows
{ "isError": true, "error": { "message": "An error blah.", "code": "InvalidRange", "innerError": { "requestId": "12345678", "date": "10/16/2019" } } }
Use custom API response format
If the mapping cannot meet our needs. And we need to add other properties to the API response model, so we can now customize our own format class and implement it by setting UseCustomSchema to true. The code is as follows
(new AutoWrapperOptions { UseCustomSchema = true });
Now suppose we want to include a property SentDate and Pagination object in the response in the main API, we may want to define the API response model as follows
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) { = statusCode; = message == ? "Success" : message; = payload; = sentDate; = pagination; } public MyCustomApiResponse(DateTime sentDate, object payload = null, Pagination pagination = null) { = 200; = "Success"; = payload; = sentDate; = pagination; } public MyCustomApiResponse(object payload) { = 200; = payload; } } public class Pagination { public int TotalItemsCount { get; set; } public int PageSize { get; set; } public int CurrentPage { get; set; } public int TotalPages { get; set; } }
Test results through the following code snippet
public async Task<MyCustomApiResponse> Get() { var data = await _personManager.GetAllAsync(); return new MyCustomApiResponse(, data, new Pagination { CurrentPage = 1, PageSize = 10, TotalItemsCount = 200, TotalPages = 20 }); }
After running, the following format will be obtained
{ "code": 200, "message": "Success", "payload": [ { "id": 1, "firstName": "Vianne", "lastName": "Durano", "dateOfBirth": "2018-11-01T00:00:00" }, { "id": 2, "firstName": "Vynn", "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 } }
But from here we need to note that once we customize the API response, it means we have full control over how the data is formatted, while losing some of the option configurations for the default API response. But we can still use the ApiException() method to raise user-defined error messages
As shown below
[Route("{id:long}")] [HttpPut] public async Task<MyCustomApiResponse> Put(long id, [FromBody] PersonDTO dto) { if () { try { var person = _mapper.Map<Person>(dto); = id; if (await _personManager.UpdateAsync(person)) return new MyCustomApiResponse(, true, "Update successful."); else throw new ApiException($"Record with id: {id} does not exist.", 400); } catch (Exception ex) { _logger.Log(, ex, "Error when trying to update with ID:{@ID}", id); throw; } } else throw new ApiException(()); }
Now when model verification is performed, the default response format can be obtained
{ "isError": true, "responseException": { "exceptionMessage": "Request responded with validation error(s). Please correct the specified validation errors and try again.", "validationErrors": [ { "field": "FirstName", "message": "'First Name' must not be empty." } ] } }
Reference
/proudmonkey/AutoWrapper
This is the end of this article about the implementation of Core AutoWrapper's custom response output. For more related Core AutoWrapper's response output content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!