Implementation of ASP. NET Core AutoWrapper Custom Response Output

  • 2021-11-14 05:20:30
  • OfStack

Preface

AutoWrapper is a simple customizable global exception handler and wrapper for the ASP. NET Core API response. He uses ASP. NET, Core, middleware to intercept incoming HTTP requests, and wraps the final result automatically in the Uniform 1 format. The main purpose is to focus more on business-specific code requirements and to allow the wrapper to automatically process HTTP responses. This speeds up development time when building the API and tries our 1 standard for the HTTP response.

Installation

AutoWrapper. Core Download and install from NuGet or through CLI


PM> Install-Package AutoWrapper.Core 

Register the following in the Startup. cs Configure method, but remember to put it before UseRouting


app.UseApiResponseAndExceptionWrapper(); 

Start attribute mapping

By default, AutoWrapper will output the following format when a successful request succeeds:


{
  "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, we can map it to any name we need to specify through the AutoWrapperPropertyMap attribute. For example, we can change the name of the result attribute to data. As shown below


public class MapResponseObject 
{
  [AutoWrapperPropertyMap(Prop.Result)]
  public object Data { get; set; }
}

Then pass the MapResponseObject class to AutpWrapper middleware


app.UseApiResponseAndExceptionWrapper<MapResponseObject>(); 

After re-requesting through mapping, the impact format is now as follows


{
  "message": "Request successful.",
  "isError": false,
  "data": {
    "id": 7002,
    "firstName": "Vianne",
    "lastName": "Durano",
    "dateOfBirth": "2018-11-01T00:00:00"
  }
}

You can see that the result attribute has been replaced with the data attribute

By default, AutoWrapper will spit out the following response format when an exception occurs


{
  "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 System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)\r\n  at System.Number.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 mapping MapResponseObject in the following code


public class MapResponseObject 
{
  [AutoWrapperPropertyMap(Prop.ResponseException)]
  public object Error { get; set; }

  [AutoWrapperPropertyMap(Prop.ResponseException_ExceptionMessage)]
  public string Message { get; set; }

  [AutoWrapperPropertyMap(Prop.ResponseException_Details)]
  public string StackTrace { get; set; }
}

Simulate the error with the following code


int num = Convert.ToInt32("10s"); 

Now the mapped output is as follows


app.UseApiResponseAndExceptionWrapper(); 

0

Note that APIError now changes the default properties of the model based on the properties defined in the MapResponseObject class.

We are free to choose to map any attribute. The following is a list of mapping attributes


app.UseApiResponseAndExceptionWrapper(); 

1

Custom error schema

AutoWrapper also provides an object that APIException can use to define its own exception. If you want to throw your own exception message, you can simply do the following


app.UseApiResponseAndExceptionWrapper(); 

2

The default output format is as follows


{
  "isError": true,
  "responseException": {
    "exceptionMessage": "Error blah",
    "referenceErrorCode": "511",
    "referenceDocumentLink": "http://blah.com/error/511"
  }
}

Of course, we can customize the error format


app.UseApiResponseAndExceptionWrapper(); 

4

Then we can raise our error with the following code


app.UseApiResponseAndExceptionWrapper(); 

5

The output format is as follows


app.UseApiResponseAndExceptionWrapper(); 

6

Use a custom API response format

If the mapping doesn't meet our needs. And we need to add other attributes to the API response model, we can now customize our own format class by setting UseCustomSchema to true, as follows


app.UseApiResponseAndExceptionWrapper(); 

7

Now assuming that we want to include 1 attribute SentDate and Pagination objects in the response in the main API, we might want to define the API response model to the following format


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; }
}

Test the results with the following code fragment


app.UseApiResponseAndExceptionWrapper(); 

9

After running, you will get the following influence format


{
  "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 note from here that once we customize the API response, it means that we have complete control over how the data is formatted, and we have lost some option configuration of the default API response. However, 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 (ModelState.IsValid)
  {
    try
    {
      var person = _mapper.Map<Person>(dto);
      person.ID = id;

      if (await _personManager.UpdateAsync(person))
        return new MyCustomApiResponse(DateTime.UtcNow, true, "Update successful.");
      else
        throw new ApiException($"Record with id: {id} does not exist.", 400);
    }
    catch (Exception ex)
    {
      _logger.Log(LogLevel.Error, ex, "Error when trying to update with ID:{@ID}", id);
      throw;
    }
  }
  else
    throw new ApiException(ModelState.AllErrors());
}

You can now get the default response format when you validate the model


{
  "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

https://github.com/proudmonkey/AutoWrapper


Related articles: