Skip to content

Commit 4cceeb1

Browse files
Added InternalServerError and InternalServerError<TValue> to TypedResults (#53656)
* Added InternalServerError and InternalServerError<TValue> to TypedResults. * Added unit tests for TypedResults.InternalServerError() and TypedResults.InternalServerError<TValue>(). * Added InternalServerError to Results. * Added overload for ExceptionDispatchInfo and updated current overloads to avoid Roslyn Analyzer complaint. * Updated test coverage of new features. * Removed InternalServerError overload for exception. --------- Co-authored-by: Onur Micoogullari <[email protected]>
1 parent b50ad97 commit 4cceeb1

10 files changed

+514
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Reflection;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Http.Metadata;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Microsoft.AspNetCore.Http.HttpResults;
8+
9+
/// <summary>
10+
/// An <see cref="IResult"/> that on execution will write an object to the response
11+
/// with Internal Server Error (500) status code.
12+
/// </summary>
13+
public sealed class InternalServerError : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="InternalServerError"/> class with the values
17+
/// provided.
18+
/// </summary>
19+
internal InternalServerError()
20+
{
21+
}
22+
23+
/// <summary>
24+
/// Gets the HTTP status code: <see cref="StatusCodes.Status500InternalServerError"/>
25+
/// </summary>
26+
public int StatusCode => StatusCodes.Status500InternalServerError;
27+
28+
int? IStatusCodeHttpResult.StatusCode => StatusCode;
29+
30+
/// <inheritdoc/>
31+
public Task ExecuteAsync(HttpContext httpContext)
32+
{
33+
ArgumentNullException.ThrowIfNull(httpContext);
34+
35+
// Creating the logger with a string to preserve the category after the refactoring.
36+
var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
37+
var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.InternalServerErrorObjectResult");
38+
39+
HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode);
40+
httpContext.Response.StatusCode = StatusCode;
41+
42+
return Task.CompletedTask;
43+
}
44+
45+
/// <inheritdoc/>
46+
static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder)
47+
{
48+
ArgumentNullException.ThrowIfNull(method);
49+
ArgumentNullException.ThrowIfNull(builder);
50+
51+
builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status500InternalServerError, typeof(void)));
52+
}
53+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Reflection;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Http.Metadata;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace Microsoft.AspNetCore.Http.HttpResults;
11+
12+
/// <summary>
13+
/// An <see cref="IResult"/> that on execution will write an object to the response
14+
/// with Internal Server Error (500) status code.
15+
/// </summary>
16+
/// <typeparam name="TValue">The type of error object that will be JSON serialized to the response body.</typeparam>
17+
public sealed class InternalServerError<TValue> : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult, IValueHttpResult, IValueHttpResult<TValue>
18+
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="InternalServerError"/> class with the values
21+
/// provided.
22+
/// </summary>
23+
/// <param name="error">The error content to format in the entity body.</param>
24+
internal InternalServerError(TValue? error)
25+
{
26+
Value = error;
27+
HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
28+
}
29+
30+
/// <summary>
31+
/// Gets the object result.
32+
/// </summary>
33+
public TValue? Value { get; }
34+
35+
object? IValueHttpResult.Value => Value;
36+
37+
/// <summary>
38+
/// Gets the HTTP status code: <see cref="StatusCodes.Status500InternalServerError"/>
39+
/// </summary>
40+
public int StatusCode => StatusCodes.Status500InternalServerError;
41+
42+
int? IStatusCodeHttpResult.StatusCode => StatusCode;
43+
44+
/// <inheritdoc/>
45+
public Task ExecuteAsync(HttpContext httpContext)
46+
{
47+
ArgumentNullException.ThrowIfNull(httpContext);
48+
49+
// Creating the logger with a string to preserve the category after the refactoring.
50+
var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
51+
var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.InternalServerErrorObjectResult");
52+
53+
HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode);
54+
httpContext.Response.StatusCode = StatusCode;
55+
56+
return HttpResultsHelper.WriteResultAsJsonAsync(
57+
httpContext,
58+
logger: logger,
59+
Value);
60+
}
61+
62+
/// <inheritdoc/>
63+
static void IEndpointMetadataProvider.PopulateMetadata(MethodInfo method, EndpointBuilder builder)
64+
{
65+
ArgumentNullException.ThrowIfNull(method);
66+
ArgumentNullException.ThrowIfNull(builder);
67+
68+
builder.Metadata.Add(new ProducesResponseTypeMetadata(StatusCodes.Status500InternalServerError, typeof(TValue), new[] { "application/json" }));
69+
}
70+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,12 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Http.HttpResults.InternalServerError
3+
Microsoft.AspNetCore.Http.HttpResults.InternalServerError.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
4+
Microsoft.AspNetCore.Http.HttpResults.InternalServerError.StatusCode.get -> int
5+
Microsoft.AspNetCore.Http.HttpResults.InternalServerError<TValue>
6+
Microsoft.AspNetCore.Http.HttpResults.InternalServerError<TValue>.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
7+
Microsoft.AspNetCore.Http.HttpResults.InternalServerError<TValue>.StatusCode.get -> int
8+
Microsoft.AspNetCore.Http.HttpResults.InternalServerError<TValue>.Value.get -> TValue?
9+
static Microsoft.AspNetCore.Http.Results.InternalServerError() -> Microsoft.AspNetCore.Http.IResult!
10+
static Microsoft.AspNetCore.Http.Results.InternalServerError<TValue>(TValue? error) -> Microsoft.AspNetCore.Http.IResult!
11+
static Microsoft.AspNetCore.Http.TypedResults.InternalServerError() -> Microsoft.AspNetCore.Http.HttpResults.InternalServerError!
12+
static Microsoft.AspNetCore.Http.TypedResults.InternalServerError<TValue>(TValue? error) -> Microsoft.AspNetCore.Http.HttpResults.InternalServerError<TValue>!

src/Http/Http.Results/src/Results.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,21 @@ public static IResult UnprocessableEntity(object? error = null)
690690
public static IResult UnprocessableEntity<TValue>(TValue? error)
691691
=> error is null ? TypedResults.UnprocessableEntity() : TypedResults.UnprocessableEntity(error);
692692

693+
/// <summary>
694+
/// Produces a <see cref="StatusCodes.Status500InternalServerError"/> response.
695+
/// </summary>
696+
/// <returns>The created <see cref="IResult"/> for the response.</returns>
697+
public static IResult InternalServerError()
698+
=> InternalServerError<object>(null);
699+
700+
/// <summary>
701+
/// Produces a <see cref="StatusCodes.Status500InternalServerError"/> response.
702+
/// </summary>
703+
/// <param name="error">An error object to be included in the HTTP response body.</param>
704+
/// <returns>The created <see cref="IResult"/> for the response.</returns>
705+
public static IResult InternalServerError<TValue>(TValue? error)
706+
=> error is null ? TypedResults.InternalServerError() : TypedResults.InternalServerError(error);
707+
693708
/// <summary>
694709
/// Produces a <see cref="ProblemDetails"/> response.
695710
/// </summary>

src/Http/Http.Results/src/ResultsCache.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ internal static partial class ResultsCache
1414
public static NoContent NoContent { get; } = new();
1515
public static Ok Ok { get; } = new();
1616
public static UnprocessableEntity UnprocessableEntity { get; } = new();
17+
public static InternalServerError InternalServerError { get; } = new();
1718
}

src/Http/Http.Results/src/TypedResults.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,20 @@ public static StatusCodeHttpResult StatusCode(int statusCode)
734734
/// <returns>The created <see cref="HttpResults.UnprocessableEntity{TValue}"/> for the response.</returns>
735735
public static UnprocessableEntity<TValue> UnprocessableEntity<TValue>(TValue? error) => new(error);
736736

737+
/// <summary>
738+
/// Produces a <see cref="StatusCodes.Status500InternalServerError"/> response.
739+
/// </summary>
740+
/// <returns>The created <see cref="HttpResults.InternalServerError"/> for the response.</returns>
741+
public static InternalServerError InternalServerError() => ResultsCache.InternalServerError;
742+
743+
/// <summary>
744+
/// Produces a <see cref="StatusCodes.Status500InternalServerError"/> response.
745+
/// </summary>
746+
/// <typeparam name="TValue">The type of error object that will be JSON serialized to the response body.</typeparam>
747+
/// <param name="error">The value to be included in the HTTP response body.</param>
748+
/// <returns>The created <see cref="HttpResults.InternalServerError{TValue}"/> for the response.</returns>
749+
public static InternalServerError<TValue> InternalServerError<TValue>(TValue? error) => new(error);
750+
737751
/// <summary>
738752
/// Produces a <see cref="ProblemDetails"/> response.
739753
/// </summary>
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Http.HttpResults;
5+
6+
using System.Reflection;
7+
using System.Text;
8+
using Microsoft.AspNetCore.Builder;
9+
using Microsoft.AspNetCore.Http.Metadata;
10+
using Microsoft.AspNetCore.Mvc;
11+
using Microsoft.AspNetCore.Routing;
12+
using Microsoft.AspNetCore.Routing.Patterns;
13+
using Microsoft.Extensions.DependencyInjection;
14+
using Microsoft.Extensions.Logging;
15+
using Microsoft.Extensions.Logging.Abstractions;
16+
using Newtonsoft.Json.Linq;
17+
18+
public class InternalServerErrorOfTResultTests
19+
{
20+
[Fact]
21+
public void InternalServerErrorObjectResult_SetsStatusCodeAndValue()
22+
{
23+
// Arrange & Act
24+
var obj = new object();
25+
var internalServerErrorObjectResult = new InternalServerError<object>(obj);
26+
27+
// Assert
28+
Assert.Equal(StatusCodes.Status500InternalServerError, internalServerErrorObjectResult.StatusCode);
29+
Assert.Equal(obj, internalServerErrorObjectResult.Value);
30+
}
31+
32+
[Fact]
33+
public void InternalServerErrorObjectResult_ProblemDetails_SetsStatusCodeAndValue()
34+
{
35+
// Arrange & Act
36+
var obj = new HttpValidationProblemDetails();
37+
var result = new InternalServerError<HttpValidationProblemDetails>(obj);
38+
39+
// Assert
40+
Assert.Equal(StatusCodes.Status500InternalServerError, result.StatusCode);
41+
Assert.Equal(StatusCodes.Status500InternalServerError, obj.Status);
42+
Assert.Equal(obj, result.Value);
43+
}
44+
45+
[Fact]
46+
public async Task InternalServerErrorObjectResult_ExecuteAsync_SetsStatusCode()
47+
{
48+
// Arrange
49+
var result = new InternalServerError<string>("Hello");
50+
var httpContext = new DefaultHttpContext()
51+
{
52+
RequestServices = CreateServices(),
53+
};
54+
55+
// Act
56+
await result.ExecuteAsync(httpContext);
57+
58+
// Assert
59+
Assert.Equal(StatusCodes.Status500InternalServerError, httpContext.Response.StatusCode);
60+
}
61+
62+
[Fact]
63+
public async Task InternalServerErrorObjectResult_ExecuteResultAsync_FormatsData()
64+
{
65+
// Arrange
66+
var result = new InternalServerError<string>("Hello");
67+
var stream = new MemoryStream();
68+
var httpContext = new DefaultHttpContext()
69+
{
70+
RequestServices = CreateServices(),
71+
Response =
72+
{
73+
Body = stream,
74+
},
75+
};
76+
77+
// Act
78+
await result.ExecuteAsync(httpContext);
79+
80+
// Assert
81+
Assert.Equal("\"Hello\"", Encoding.UTF8.GetString(stream.ToArray()));
82+
}
83+
84+
[Fact]
85+
public async Task InternalServerErrorObjectResult_ExecuteResultAsync_UsesStatusCodeFromResultTypeForProblemDetails()
86+
{
87+
// Arrange
88+
var details = new ProblemDetails { Status = StatusCodes.Status422UnprocessableEntity, };
89+
var result = new InternalServerError<ProblemDetails>(details);
90+
91+
var httpContext = new DefaultHttpContext()
92+
{
93+
RequestServices = CreateServices(),
94+
};
95+
96+
// Act
97+
await result.ExecuteAsync(httpContext);
98+
99+
// Assert
100+
Assert.Equal(StatusCodes.Status422UnprocessableEntity, details.Status.Value);
101+
Assert.Equal(StatusCodes.Status500InternalServerError, result.StatusCode);
102+
Assert.Equal(StatusCodes.Status500InternalServerError, httpContext.Response.StatusCode);
103+
}
104+
105+
[Fact]
106+
public void PopulateMetadata_AddsResponseTypeMetadata()
107+
{
108+
// Arrange
109+
InternalServerError<Todo> MyApi() { throw new NotImplementedException(); }
110+
var metadata = new List<object>();
111+
var builder = new RouteEndpointBuilder(requestDelegate: null, RoutePatternFactory.Parse("/"), order: 0);
112+
113+
// Act
114+
PopulateMetadata<InternalServerError<Todo>>(((Delegate)MyApi).GetMethodInfo(), builder);
115+
116+
// Assert
117+
var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
118+
Assert.Equal(StatusCodes.Status500InternalServerError, producesResponseTypeMetadata.StatusCode);
119+
Assert.Equal(typeof(Todo), producesResponseTypeMetadata.Type);
120+
Assert.Single(producesResponseTypeMetadata.ContentTypes, "application/json");
121+
}
122+
123+
[Fact]
124+
public void ExecuteAsync_ThrowsArgumentNullException_WhenHttpContextIsNull()
125+
{
126+
// Arrange
127+
var result = new InternalServerError<object>(null);
128+
HttpContext httpContext = null;
129+
130+
// Act & Assert
131+
Assert.ThrowsAsync<ArgumentNullException>("httpContext", () => result.ExecuteAsync(httpContext));
132+
}
133+
134+
[Fact]
135+
public void PopulateMetadata_ThrowsArgumentNullException_WhenMethodOrBuilderAreNull()
136+
{
137+
// Act & Assert
138+
Assert.Throws<ArgumentNullException>("method", () => PopulateMetadata<InternalServerError<object>>(null, new RouteEndpointBuilder(requestDelegate: null, RoutePatternFactory.Parse("/"), order: 0)));
139+
Assert.Throws<ArgumentNullException>("builder", () => PopulateMetadata<InternalServerError<object>>(((Delegate)PopulateMetadata_ThrowsArgumentNullException_WhenMethodOrBuilderAreNull).GetMethodInfo(), null));
140+
}
141+
142+
[Fact]
143+
public void InternalServerErrorObjectResult_Implements_IStatusCodeHttpResult_Correctly()
144+
{
145+
// Act & Assert
146+
var result = Assert.IsAssignableFrom<IStatusCodeHttpResult>(new InternalServerError<string>(null));
147+
Assert.Equal(StatusCodes.Status500InternalServerError, result.StatusCode);
148+
}
149+
150+
[Fact]
151+
public void InternalServerErrorObjectResult_Implements_IValueHttpResult_Correctly()
152+
{
153+
// Arrange
154+
var value = "Foo";
155+
156+
// Act & Assert
157+
var result = Assert.IsAssignableFrom<IValueHttpResult>(new InternalServerError<string>(value));
158+
Assert.IsType<string>(result.Value);
159+
Assert.Equal(value, result.Value);
160+
}
161+
162+
[Fact]
163+
public void InternalServerErrorObjectResult_Implements_IValueHttpResultOfT_Correctly()
164+
{
165+
// Arrange & Act
166+
var value = "Foo";
167+
168+
// Assert
169+
var result = Assert.IsAssignableFrom<IValueHttpResult<string>>(new InternalServerError<string>(value));
170+
Assert.IsType<string>(result.Value);
171+
Assert.Equal(value, result.Value);
172+
}
173+
174+
private static void PopulateMetadata<TResult>(MethodInfo method, EndpointBuilder builder)
175+
where TResult : IEndpointMetadataProvider => TResult.PopulateMetadata(method, builder);
176+
177+
private record Todo(int Id, string Title);
178+
179+
private static IServiceProvider CreateServices()
180+
{
181+
var services = new ServiceCollection();
182+
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
183+
return services.BuildServiceProvider();
184+
}
185+
}

0 commit comments

Comments
 (0)