Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit a7cc243

Browse files
committed
Introduce ProblemDescription
1 parent 151cf44 commit a7cc243

File tree

6 files changed

+224
-0
lines changed

6 files changed

+224
-0
lines changed

src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,49 @@ public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState
14681468
return new BadRequestObjectResult(modelState);
14691469
}
14701470

1471+
/// <summary>
1472+
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
1473+
/// </summary>
1474+
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
1475+
[NonAction]
1476+
public virtual BadRequestObjectResult ValidationProblem(ValidationProblemDescription descriptor)
1477+
{
1478+
if (descriptor == null)
1479+
{
1480+
throw new ArgumentNullException(nameof(descriptor));
1481+
}
1482+
1483+
return new BadRequestObjectResult(descriptor);
1484+
}
1485+
1486+
/// <summary>
1487+
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
1488+
/// </summary>
1489+
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
1490+
[NonAction]
1491+
public virtual BadRequestObjectResult ValidationProblem(ModelStateDictionary modelStateDictionary)
1492+
{
1493+
if (modelStateDictionary == null)
1494+
{
1495+
throw new ArgumentNullException(nameof(modelStateDictionary));
1496+
}
1497+
1498+
var validationProblem = new ValidationProblemDescription(modelStateDictionary);
1499+
return new BadRequestObjectResult(validationProblem);
1500+
}
1501+
1502+
/// <summary>
1503+
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response
1504+
/// with validation errors from <see cref="ModelState"/>.
1505+
/// </summary>
1506+
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
1507+
[NonAction]
1508+
public virtual BadRequestObjectResult ValidationProblem()
1509+
{
1510+
var validationProblem = new ValidationProblemDescription(ModelState);
1511+
return new BadRequestObjectResult(validationProblem);
1512+
}
1513+
14711514
/// <summary>
14721515
/// Creates a <see cref="CreatedResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
14731516
/// </summary>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Microsoft.AspNetCore.Mvc
8+
{
9+
/// <summary>
10+
/// A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807.
11+
/// </summary>
12+
public class ProblemDescription
13+
{
14+
/// <summary>
15+
/// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when
16+
/// dereferenced, it provide human-readable documentation for the problem type
17+
/// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be
18+
/// "about:blank".
19+
/// </summary>
20+
public string Type { get; set; }
21+
22+
/// <summary>
23+
/// A short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence
24+
/// of the problem, except for purposes of localization(e.g., using proactive content negotiation;
25+
/// see[RFC7231], Section 3.4).
26+
/// </summary>
27+
public string Title { get; set; }
28+
29+
/// <summary>
30+
/// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.
31+
/// </summary>
32+
public int? Status { get; set; }
33+
34+
/// <summary>
35+
/// A human-readable explanation specific to this occurrence of the problem.
36+
/// </summary>
37+
public string Detail { get; set; }
38+
39+
/// <summary>
40+
/// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced.
41+
/// </summary>
42+
public string Instance { get; set; }
43+
}
44+
}

src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNetCore.Mvc.Core/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,7 @@
412412
<data name="UrlHelper_RelativePagePathIsNotSupported" xml:space="preserve">
413413
<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>
414414
</data>
415+
<data name="ValidationProblemDescription_Title" xml:space="preserve">
416+
<value>One or more validation errors occured.</value>
417+
</data>
415418
</root>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Microsoft.AspNetCore.Mvc.Core;
7+
using Microsoft.AspNetCore.Mvc.ModelBinding;
8+
9+
namespace Microsoft.AspNetCore.Mvc
10+
{
11+
/// <summary>
12+
/// A <see cref="ProblemDescription"/> for validation errors.
13+
/// </summary>
14+
public class ValidationProblemDescription : ProblemDescription
15+
{
16+
/// <summary>
17+
/// Intializes a new instance of <see cref="ValidationProblemDescription"/>.
18+
/// </summary>
19+
public ValidationProblemDescription()
20+
{
21+
Title = Resources.ValidationProblemDescription_Title;
22+
}
23+
24+
public ValidationProblemDescription(ModelStateDictionary modelState)
25+
: this()
26+
{
27+
if (modelState == null)
28+
{
29+
throw new ArgumentNullException(nameof(modelState));
30+
}
31+
32+
foreach (var keyModelStatePair in modelState)
33+
{
34+
var key = keyModelStatePair.Key;
35+
var errors = keyModelStatePair.Value.Errors;
36+
if (errors != null && errors.Count > 0)
37+
{
38+
if (errors.Count == 1)
39+
{
40+
var errorMessage = GetErrorMessage(errors[0]);
41+
Errors.Add(key, new[] { errorMessage });
42+
}
43+
else
44+
{
45+
var errorMessages = new string[errors.Count];
46+
for (var i = 0; i < errors.Count; i++)
47+
{
48+
errorMessages[i] = GetErrorMessage(errors[i]);
49+
}
50+
51+
Errors.Add(key, errorMessages);
52+
}
53+
}
54+
}
55+
56+
string GetErrorMessage(ModelError error)
57+
{
58+
return string.IsNullOrEmpty(error.ErrorMessage) ?
59+
Resources.SerializableError_DefaultError :
60+
error.ErrorMessage;
61+
}
62+
}
63+
64+
/// <summary>
65+
/// Gets or sets the validation errors associated with this instance of <see cref="ValidationProblemDescription"/>.
66+
/// </summary>
67+
public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>(StringComparer.Ordinal);
68+
}
69+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Mvc.ModelBinding;
5+
using Xunit;
6+
7+
namespace Microsoft.AspNetCore.Mvc
8+
{
9+
public class ValidationProblemDescriptionTest
10+
{
11+
[Fact]
12+
public void Constructor_SetsTitle()
13+
{
14+
// Arrange & Act
15+
var problemDescription = new ValidationProblemDescription();
16+
17+
// Assert
18+
Assert.Equal("One or more validation errors occured.", problemDescription.Title);
19+
Assert.Empty(problemDescription.Errors);
20+
}
21+
22+
[Fact]
23+
public void Constructor_SerializesErrorsFromModelStateDictionary()
24+
{
25+
// Arrange
26+
var modelStateDictionary = new ModelStateDictionary();
27+
modelStateDictionary.AddModelError("key1", "error1");
28+
modelStateDictionary.SetModelValue("key2", new object(), "value");
29+
modelStateDictionary.AddModelError("key3", "error2");
30+
modelStateDictionary.AddModelError("key3", "error3");
31+
32+
// Act
33+
var problemDescription = new ValidationProblemDescription(modelStateDictionary);
34+
35+
// Assert
36+
Assert.Equal("One or more validation errors occured.", problemDescription.Title);
37+
Assert.Collection(
38+
problemDescription.Errors,
39+
item =>
40+
{
41+
Assert.Equal("key1", item.Key);
42+
Assert.Equal(new[] { "error1" }, item.Value);
43+
},
44+
item =>
45+
{
46+
Assert.Equal("key3", item.Key);
47+
Assert.Equal(new[] { "error2", "error3" }, item.Value);
48+
});
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)