-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Add helper methods on ControllerBase to return ProblemDetails #12298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
using Microsoft.AspNetCore.Mvc.Routing; | ||
using Microsoft.AspNetCore.Routing; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Net.Http.Headers; | ||
|
||
namespace Microsoft.AspNetCore.Mvc | ||
|
@@ -31,6 +32,7 @@ public abstract class ControllerBase | |
private IModelBinderFactory _modelBinderFactory; | ||
private IObjectModelValidator _objectValidator; | ||
private IUrlHelper _url; | ||
private ProblemDetailsFactory _problemDetailsFactory; | ||
|
||
/// <summary> | ||
/// Gets the <see cref="Http.HttpContext"/> for the executing action. | ||
|
@@ -189,6 +191,28 @@ public IObjectModelValidator ObjectValidator | |
} | ||
} | ||
|
||
public ProblemDetailsFactory ProblemDetailsFactory | ||
{ | ||
get | ||
{ | ||
if (_problemDetailsFactory == null) | ||
{ | ||
_problemDetailsFactory = HttpContext?.RequestServices?.GetRequiredService<ProblemDetailsFactory>(); | ||
} | ||
|
||
return _problemDetailsFactory; | ||
} | ||
set | ||
{ | ||
if (value == null) | ||
{ | ||
throw new ArgumentNullException(nameof(value)); | ||
} | ||
|
||
_problemDetailsFactory = value; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action. | ||
/// </summary> | ||
|
@@ -1821,6 +1845,34 @@ public virtual ConflictObjectResult Conflict([ActionResultObjectValue] object er | |
public virtual ConflictObjectResult Conflict([ActionResultObjectValue] ModelStateDictionary modelState) | ||
=> new ConflictObjectResult(modelState); | ||
|
||
/// <summary> | ||
/// Creates an <see cref="ObjectResult"/> that produces a <see cref="ProblemDetails"/> response. | ||
/// </summary> | ||
/// <param name="statusCode">The value for <see cref="ProblemDetails.Status" />..</param> | ||
/// <param name="detail">The value for <see cref="ProblemDetails.Detail" />.</param> | ||
/// <param name="instance">The value for <see cref="ProblemDetails.Instance" />.</param> | ||
/// <param name="title">The value for <see cref="ProblemDetails.Title" />.</param> | ||
/// <param name="type">The value for <see cref="ProblemDetails.Type" />.</param> | ||
/// <returns>The created <see cref="ObjectResult"/> for the response.</returns> | ||
[NonAction] | ||
public virtual ObjectResult Problem( | ||
string detail = null, | ||
string instance = null, | ||
int? statusCode = null, | ||
string title = null, | ||
string type = null) | ||
pranavkm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
var problemDetails = ProblemDetailsFactory.CreateProblemDetails( | ||
HttpContext, | ||
statusCode: statusCode ?? 500, | ||
title: title, | ||
type: type, | ||
detail: detail, | ||
instance: instance); | ||
|
||
return new ObjectResult(problemDetails); | ||
} | ||
|
||
/// <summary> | ||
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response. | ||
/// </summary> | ||
|
@@ -1837,31 +1889,64 @@ public virtual ActionResult ValidationProblem([ActionResultObjectValue] Validati | |
} | ||
|
||
/// <summary> | ||
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response. | ||
/// Creates an <see cref="ActionResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response | ||
/// with validation errors from <paramref name="modelStateDictionary"/>. | ||
/// </summary> | ||
/// <param name="modelStateDictionary">The <see cref="ModelStateDictionary"/>.</param> | ||
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns> | ||
[NonAction] | ||
public virtual ActionResult ValidationProblem([ActionResultObjectValue] ModelStateDictionary modelStateDictionary) | ||
{ | ||
if (modelStateDictionary == null) | ||
{ | ||
throw new ArgumentNullException(nameof(modelStateDictionary)); | ||
} | ||
=> ValidationProblem(detail: null, modelStateDictionary: modelStateDictionary); | ||
|
||
var validationProblem = new ValidationProblemDetails(modelStateDictionary); | ||
return new BadRequestObjectResult(validationProblem); | ||
} | ||
|
||
/// <summary> | ||
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response | ||
/// Creates an <see cref="ActionResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response | ||
/// with validation errors from <see cref="ModelState"/>. | ||
/// </summary> | ||
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns> | ||
/// <returns>The created <see cref="ActionResult"/> for the response.</returns> | ||
[NonAction] | ||
public virtual ActionResult ValidationProblem() | ||
=> ValidationProblem(ModelState); | ||
|
||
/// <summary> | ||
/// Creates an <see cref="ActionResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response | ||
/// with a <see cref="ValidationProblemDetails"/> value. | ||
/// </summary> | ||
/// <param name="detail">The value for <see cref="ProblemDetails.Detail" />.</param> | ||
/// <param name="instance">The value for <see cref="ProblemDetails.Instance" />.</param> | ||
/// <param name="statusCode">The status code.</param> | ||
/// <param name="title">The value for <see cref="ProblemDetails.Title" />.</param> | ||
/// <param name="type">The value for <see cref="ProblemDetails.Type" />.</param> | ||
/// <param name="modelStateDictionary">The <see cref="ModelStateDictionary"/>. | ||
/// When <see langword="null"/> uses <see cref="ModelState"/>.</param> | ||
/// <returns>The created <see cref="ActionResult"/> for the response.</returns> | ||
[NonAction] | ||
public virtual ActionResult ValidationProblem( | ||
string detail = null, | ||
string instance = null, | ||
int? statusCode = null, | ||
string title = null, | ||
string type = null, | ||
[ActionResultObjectValue] ModelStateDictionary modelStateDictionary = null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this as a parameter? is there really a use case for passing it in explicitly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have this for |
||
{ | ||
var validationProblem = new ValidationProblemDetails(ModelState); | ||
return new BadRequestObjectResult(validationProblem); | ||
modelStateDictionary ??= ModelState; | ||
|
||
var validationProblem = ProblemDetailsFactory.CreateValidationProblemDetails( | ||
HttpContext, | ||
modelStateDictionary, | ||
statusCode: statusCode, | ||
title: title, | ||
type: type, | ||
detail: detail, | ||
instance: instance); | ||
|
||
if (validationProblem.Status == 400) | ||
{ | ||
// For compatibility with 2.x, continue producing BadRequestObjectResult instances if the status code is 400. | ||
return new BadRequestObjectResult(validationProblem); | ||
} | ||
|
||
return new ObjectResult(validationProblem); | ||
} | ||
|
||
/// <summary> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could end up being
null
which would lead to a NRE below.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's expected to only ever be null in test scenarios. Basically following the same pattern as https://github.com/aspnet/AspNetCore/pull/12298/files#diff-8aa2822b0a420b6f8a04153ca4312519R172
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to check if it's null and throw a better exception then? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@khellang I'd rather keep the code comparable to the other properties for now. I did venture at using nullable types at one point and that forces some cleaning up some of these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not saying you should change this property, I'm saying you might want to guard against
null
at the call site. Haven't checked usage of the other properties, but I'd consider those potential bugs as well if not properly guarded againstnull
.