I try to improve myself with .NET Web API now and I am trying to return a custom error in Swagger. But when returning this custom error, I can see the error is on which line. How can I do to prevent this?
public async Task<BookCreateDTO> CreateBook(BookCreateDTO bookCreateDto)
{
if (await _context.Books.AnyAsync(x => x.Name == bookCreateDto.Name))
{
throw new BookExistException("Book already exist");
}
var book= _mapper.Map<Book>(bookCreateDto);
_context.Books.Add(book);
await _context.SaveChangesAsync();
return book;
}
What should I do to see only this exception message in the Swagger response? Thank you for your help.
Exceptions should be exceptional: Don't throw exceptions for non-exceptional errors.
I don't recommend specifying your web-service's response DTO type in the C# action method return type because it limits your expressiveness (as you're discovering).
IActionResult or ActionResult<T> to document the default (i.e. HTTP 2xx) response type and then list error DTO types in [ProducesResponseType] attributes with their corresponding HTTP status codes.
DtoFoo, DtoBar, DtoQux", in-practice a well-designed web-service API should not exhibit that kind of response DTO polymorphism.
ModelState (with the Key, if possible) and let ASP.NET Core handle the rest for you with ProblemDetails.ProblemDetails - or it can show the DeveloperExceptionPage - or something else entirely.
[ProducesResponseType]: this is very useful when using Swagger/NSwag to generate online documentation and client libraries.POST /users with { accessLevel: 'superAdmin' }, for example. While you can exclude or restrict which properties of an object can be set by a request it just adds to your project's maintenance workload (as it's another non-local, manually-written, list or definition in your program you need to ensure is kept in-sync with everything else.User entity will have a Byte[] PasswordHash and Byte[] PasswordSalt properties (I hope...), and obviously those two properties must never be exposed; but in a User DTO for editing a user you might want different members, like NewPassword and ConfirmPassword - which don't map to DB columns at all.Customer object had inline Street address fields but now they're missing (because your Customer EF entity types' don't have street-address columns anymore, that's over in the CustomerAddress entity class).Customer objects then during the process of updating the design of the application you would have noticed builds breaking sooner (rather than inevitably later!) due to C# compile-time type-checking in your DTO-to-Entity (and Entity-to-DTO) mapping code - that's a benefit right there.Anyway, I've modified your posted code to follow the guidance above:
[ProducesResponseType] attributes.
BookCreateDTO twice (in [ProducesResponseType] as well as ActionResult<BookCreateDTO> - you should be able to remove either one of those without affecting Swagger output.[FromBody], just to be safe.BadRequest response, which is rendered as an IETF RFC 7807 response, aka ProblemDetails instead of throwing an exception and then hoping that you configured your ASP.NET Core pipeline (in Configure()) to handle it as a ProblemDetails instead of, say, invoking a debugger or using DeveloperExceptionPage.
conflictResult.StatusCode = 409; is overwritten.BookCreateDTO instance via AutoMapper and Ok() instead of serializing your Book entity object.[ProducesResponseType(typeof(BookCreateDTO), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
public async Task< ActionResult<BookCreateDTO> > CreateBook( [FromBody] BookCreateDTO bookCreateDto )
{
// Does a book with the same name exist? If so, then return HTTP 409 Conflict.
if( await _context.Books.AnyAsync(x => x.Name == bookCreateDto.Name) )
{
this.ModelState.Add( nameof(BookCreateDTO.Name), "Book already exists" );
BadRequestObjectResult conflictResult = this.BadRequest( this.ModelState );
// `BadRequestObjectResult` is HTTP 400 by default, change it to HTTP 409:
conflictResult.StatusCode = 409;
return conflictResult;
}
Book addedBook;
{
addedBook = this.mapper.Map<Book>( bookCreateDto );
_ = this.context.Books.Add( book );
_ = await this.context.SaveChangesAsync();
}
BookCreateDTO responseDto = this.mapper.Map<BookCreateDTO >( addedBook );
return this.Ok( responseDto );
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With