The new version of AutoWrapper v4.0.0-rc comes with a few enhancements and bug fixes. Version 4 is still in RC prerelease state to allow testing it and ensuring that it won't break any existing functionalities. The main feature enhancement for this release is the support of ProblemDetails for defining exception format. This feature was originally requested by Sondre Bjellås via Issue #7. The implementation of this feature is a simplified version of the Hellang.Middleware.ProblemDetails middleware by Kristian Hellang. Kudos to Kristian for providing such a great middleware!
What Problem Details Is?
Taken from Microsoft documentation:
ProblemDetails is a machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807.
Enabling Problem Details
- Install the latest version of
AutoWrapper
.
Install-Package AutoWrapper.Core -Version 4.0.0-rc
- Then set
UseApiProblemDetailsException
totrue
:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions {
UseApiProblemDetailsException = true
});
To see the error details during development, you can set the IsDebug
option to true
. That's it!
Sample Tests
Here are a few examples how ProblemDetails error is going to look like:
404 Not Found
Request:
[HttpGet("notfound")]
public IActionResult ActionNotFound()
{
return NotFound("No record found");
}
Response:
{
"isError": true,
"type": "https://httpstatuses.com/404",
"title": "Not Found",
"status": 404,
"detail": "No record found",
"instance": null,
"extensions": {}
}
Another example.
Request:
throw new ApiProblemDetailsException($"Record with id: {id} does not exist.", Status404NotFound);
Response:
{
"isError": true,
"type": "https://httpstatuses.com/404",
"title": "Record with id: 1222 does not exist.",
"status": 404,
"detail": null,
"instance": null,
"extensions": {}
}
400 Bad Request
Request:
[HttpGet("badreq")]
public IActionResult ActionBadRequest()
{
return BadRequest("Something is null.");
}
Response:
{
"isError": true,
"type": "https://httpstatuses.com/400",
"title": "Bad Request",
"status": 400,
"detail": "Something is null.",
"instance": null,
"extensions": {}
}
422 Unprocessable Entity
Request:
[HttpPost]
public async Task<ApiResponse> Post([FromBody] CreatePersonRequest dto)
{
if (!ModelState.IsValid)
{
throw new ApiProblemDetailsException(ModelState);
}
}
Response:
{
"isError": true,
"type": "https://httpstatuses.com/422",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Your request parameters didn't validate.",
"instance": null,
"extensions": {},
"validationErrors": [
{
"name": "LastName",
"reason": "'Last Name' must not be empty."
},
{
"name": "FirstName",
"reason": "'First Name' must not be empty."
},
{
"name": "DateOfBirth",
"reason": "'Date Of Birth' must not be empty."
}
]
}
500 Internal Server Error
Request:
[HttpGet("unhandled")]
public IActionResult ActionError()
{
throw new Exception("Unhanled error occured.");
}
Response:
{
"isError": true,
"type": "https://httpstatuses.com/500",
"title": "Internal Server Error",
"status": 500,
"detail": "Unhanled error occured.",
"instance": null,
"extensions": {},
"errors": {
"message": "Unhanled error occured.",
"type": "Exception",
"source": "AutoWrapper_v3._1",
"raw": "at AutoWrapper_v3._1.API.v1.TestsController.ActionError() in ..removed for brevity..."
}
}
Another example.
Request:
[HttpGet("invalidcast")]
public IActionResult ActionInvalidCast()
{
try { int num = Convert.ToInt32("123a"); return Ok(); }
catch { throw; }
}
Response:
{
"isError": true,
"type": "https://httpstatuses.com/500",
"title": "Internal Server Error",
"status": 500,
"detail": "Input string was not in a correct format.",
"instance": null,
"extensions": {},
"errors": {
"message": "Input string was not in a correct format.",
"type": "FormatException",
"source": "System.Private.CoreLib",
"raw": "at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)..removed for brevity..."
}
}
Custom
You could also throw your own custom defined object that derives from Microsoft.AspNetCore.Mvc.ProblemDetails
class. Consider the following example taken from Hellang.Middleware.ProblemDetails middleware sample:
Request:
[HttpGet("problem/error")]
public IActionResult Result()
{
var problem = new OutOfCreditProblemDetails
{
Type = "https://example.com/probs/out-of-credit",
Title = "You do not have enough credit.",
Detail = "Your current balance is 30, but that costs 50.",
Instance = "/account/12345/msgs/abc",
Balance = 30.0m,
Accounts = { "/account/12345", "/account/67890" }
};
return BadRequest(problem);
}
public class OutOfCreditProblemDetails : Microsoft.AspNetCore.Mvc.ProblemDetails
{
public OutOfCreditProblemDetails()
{
Accounts = new List<string>();
}
public decimal Balance { get; set; }
public ICollection<string> Accounts { get; }
}
Response:
{
"balance": 30.0,
"accounts": [
"/account/12345",
"/account/67890"
],
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"status": 400,
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/msgs/abc",
"extensions": {}
}
Fixes and Enhancements
- Added a new
Option
calledUseCustomExceptionFormat
to enable using your own custom exception format or usingProblemDetails
error format. This means that if you don't want to use theApiException
orApiProblemDetailsException
formats built-in toAutoWrapper
, then you can simply setUseCustomExceptionFormat = true
. For example, if you want to use Hellang.Middleware.ProblemDetails middleware to take advantage of its detailed features, you can define theAutoWrapper
like in the following:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions {
UseCustomExceptionFormat = true
})
.UseProblemDetails();
- [Issue #22] Changed the
FormatRequestAsync()
method implementation to useCopyAsync()
instead ofReadAsync()
. Also added validation check to only allow reading the request body from HttpPOST
,PUT
andPATCH
verbs. - [Issue #26] Changed
ExceptionMessage
datatype fromstring
toobject
so it can support childJSON
attributes. For example:
{
"isError": true,
"responseException": {
"exceptionMessage": {
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
"title": "Unsupported Media Type",
"status": 415,
"traceId": "|e96629a6-4b34e7a7f65431af."
}
}
}
- [Issue #33] Ignored wrapping the response when
StatusCode
is204 NoContent
. - [Issue #32] Added output validation to avoid re-enconding the
JSON
value. - [Issue #32] Added output validation for
number
,decimal
andboolean
types to avoid automatically converting them to string. For example:
[HttpGet("number")]
public int SpitNumber(int schiffnr)
{
return 29;
}
- Starting this version, only .NET Core 3.0 and above will be supported to take advantage of C# new features
- Refactor code and clean up.
Big thanks to everyone who have supported it! I truly appreciate it. :)
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 on this project!