Introduce ProblemDescription
This commit is contained in:
parent
151cf44607
commit
a7cc243942
|
|
@ -1468,6 +1468,49 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
return new BadRequestObjectResult(modelState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
|
||||
/// </summary>
|
||||
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
|
||||
[NonAction]
|
||||
public virtual BadRequestObjectResult ValidationProblem(ValidationProblemDescription descriptor)
|
||||
{
|
||||
if (descriptor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(descriptor));
|
||||
}
|
||||
|
||||
return new BadRequestObjectResult(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
|
||||
/// </summary>
|
||||
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
|
||||
[NonAction]
|
||||
public virtual BadRequestObjectResult ValidationProblem(ModelStateDictionary modelStateDictionary)
|
||||
{
|
||||
if (modelStateDictionary == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelStateDictionary));
|
||||
}
|
||||
|
||||
var validationProblem = new ValidationProblemDescription(modelStateDictionary);
|
||||
return new BadRequestObjectResult(validationProblem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="BadRequestObjectResult"/> 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>
|
||||
[NonAction]
|
||||
public virtual BadRequestObjectResult ValidationProblem()
|
||||
{
|
||||
var validationProblem = new ValidationProblemDescription(ModelState);
|
||||
return new BadRequestObjectResult(validationProblem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="CreatedResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807.
|
||||
/// </summary>
|
||||
public class ProblemDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when
|
||||
/// dereferenced, it provide human-readable documentation for the problem type
|
||||
/// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be
|
||||
/// "about:blank".
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence
|
||||
/// of the problem, except for purposes of localization(e.g., using proactive content negotiation;
|
||||
/// see[RFC7231], Section 3.4).
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.
|
||||
/// </summary>
|
||||
public int? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A human-readable explanation specific to this occurrence of the problem.
|
||||
/// </summary>
|
||||
public string Detail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced.
|
||||
/// </summary>
|
||||
public string Instance { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1326,6 +1326,20 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// One or more validation errors occured.
|
||||
/// </summary>
|
||||
internal static string ValidationProblemDescription_Title
|
||||
{
|
||||
get => GetString("ValidationProblemDescription_Title");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One or more validation errors occured.
|
||||
/// </summary>
|
||||
internal static string FormatValidationProblemDescription_Title()
|
||||
=> GetString("ValidationProblemDescription_Title");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -412,4 +412,7 @@
|
|||
<data name="UrlHelper_RelativePagePathIsNotSupported" xml:space="preserve">
|
||||
<value>The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.</value>
|
||||
</data>
|
||||
<data name="ValidationProblemDescription_Title" xml:space="preserve">
|
||||
<value>One or more validation errors occured.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ProblemDescription"/> for validation errors.
|
||||
/// </summary>
|
||||
public class ValidationProblemDescription : ProblemDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Intializes a new instance of <see cref="ValidationProblemDescription"/>.
|
||||
/// </summary>
|
||||
public ValidationProblemDescription()
|
||||
{
|
||||
Title = Resources.ValidationProblemDescription_Title;
|
||||
}
|
||||
|
||||
public ValidationProblemDescription(ModelStateDictionary modelState)
|
||||
: this()
|
||||
{
|
||||
if (modelState == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelState));
|
||||
}
|
||||
|
||||
foreach (var keyModelStatePair in modelState)
|
||||
{
|
||||
var key = keyModelStatePair.Key;
|
||||
var errors = keyModelStatePair.Value.Errors;
|
||||
if (errors != null && errors.Count > 0)
|
||||
{
|
||||
if (errors.Count == 1)
|
||||
{
|
||||
var errorMessage = GetErrorMessage(errors[0]);
|
||||
Errors.Add(key, new[] { errorMessage });
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessages = new string[errors.Count];
|
||||
for (var i = 0; i < errors.Count; i++)
|
||||
{
|
||||
errorMessages[i] = GetErrorMessage(errors[i]);
|
||||
}
|
||||
|
||||
Errors.Add(key, errorMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string GetErrorMessage(ModelError error)
|
||||
{
|
||||
return string.IsNullOrEmpty(error.ErrorMessage) ?
|
||||
Resources.SerializableError_DefaultError :
|
||||
error.ErrorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the validation errors associated with this instance of <see cref="ValidationProblemDescription"/>.
|
||||
/// </summary>
|
||||
public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>(StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
public class ValidationProblemDescriptionTest
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsTitle()
|
||||
{
|
||||
// Arrange & Act
|
||||
var problemDescription = new ValidationProblemDescription();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("One or more validation errors occured.", problemDescription.Title);
|
||||
Assert.Empty(problemDescription.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SerializesErrorsFromModelStateDictionary()
|
||||
{
|
||||
// Arrange
|
||||
var modelStateDictionary = new ModelStateDictionary();
|
||||
modelStateDictionary.AddModelError("key1", "error1");
|
||||
modelStateDictionary.SetModelValue("key2", new object(), "value");
|
||||
modelStateDictionary.AddModelError("key3", "error2");
|
||||
modelStateDictionary.AddModelError("key3", "error3");
|
||||
|
||||
// Act
|
||||
var problemDescription = new ValidationProblemDescription(modelStateDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("One or more validation errors occured.", problemDescription.Title);
|
||||
Assert.Collection(
|
||||
problemDescription.Errors,
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("key1", item.Key);
|
||||
Assert.Equal(new[] { "error1" }, item.Value);
|
||||
},
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("key3", item.Key);
|
||||
Assert.Equal(new[] { "error2", "error3" }, item.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue