Introduce ProblemDescription

This commit is contained in:
Pranav K 2017-08-31 17:16:12 -07:00
parent 151cf44607
commit a7cc243942
6 changed files with 224 additions and 0 deletions

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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);
});
}
}
}