What are your opinions on how to structure consistent api responses in .net core api.
I'm looking to implement a consistent json api response across all action methods. ideally a no fuss pattern that isn't over engineered.
Does anyone have a good pattern / preference?
Successful responses return the payload in a field called data with a status object with no errors. if there are error messages the payload returns a status object with an error code and a message or messages.
I'll demonstrate what i mean with a few request / responses to get a person.
happy path
Request:
GET https://www.somedomain.com/api/person/1 HTTP/1.1
Response:
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 ...other http headers ommitted { "status": { "error_code": 0, "error_message": null }, "data": { "User": { "Id": 1, "Address": { "Street": "100 fake street", "Country": "USA" }, "Name": "Donnie Darko" }, "last_updated": "2019-05-10T21:57:00.000Z" } }
Unhappy path
Request:
GET https://www.somedomain.com/api/person/donnie HTTP/1.1
Response:
HTTP/1.1 400 Bad Request Content-Type: application/json; charset=utf-8 { "status": { "error_code": 221, "error_message": "Id must be numeric", } }
I'd like to structure my responses similar to this.
I found this article that implements something similar, though i would probably tweek it a bit e.g. passing the http status code as a field is not required.
here is the article:
https://www.devtrends.co.uk/blog/handling-errors-in-asp.net-core-web-api
Does anyone have a good pattern / preference?
code example used in the article:
public class ApiResponse { public int StatusCode { get; } [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string Message { get; } public ApiResponse(int statusCode, string message = null) { StatusCode = statusCode; Message = message ?? GetDefaultMessageForStatusCode(statusCode); } private static string GetDefaultMessageForStatusCode(int statusCode) { switch (statusCode) { ... case 404: return "Resource not found"; case 500: return "An unhandled error occurred"; default: return null; } } } public class ApiOkResponse : ApiResponse { public object Result { get; } public ApiOkResponse(object result) :base(200) { Result = result; } } public class ApiBadRequestResponse : ApiResponse { public IEnumerable<string> Errors { get; } public ApiBadRequestResponse(ModelStateDictionary modelState) : base(400) { if (modelState.IsValid) { throw new ArgumentException("ModelState must be invalid", nameof(modelState)); } Errors = modelState.SelectMany(x => x.Value.Errors) .Select(x => x.ErrorMessage).ToArray(); } } // finally it being used in a controller [HttpGet("product")] public async Task<IActionResult> GetProduct(GetProductRequest request) { if (!ModelState.IsValid) { return BadRequest(new ApiBadRequestResponse(ModelState)); } var model = await _db.Get(...); if (model == null) { return NotFound(new ApiResponse(404, $"Product not found with id {request.ProductId}")); } return Ok(new ApiOkResponse(model)); }
0 comments:
Post a Comment