diff --git a/src/Http/Http.Results/src/AcceptedAtRouteResult.cs b/src/Http/Http.Results/src/AcceptedAtRouteHttpResult.cs
similarity index 53%
rename from src/Http/Http.Results/src/AcceptedAtRouteResult.cs
rename to src/Http/Http.Results/src/AcceptedAtRouteHttpResult.cs
index 1958f7d402fd..0e45e84164ce 100644
--- a/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
+++ b/src/Http/Http.Results/src/AcceptedAtRouteHttpResult.cs
@@ -1,41 +1,54 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+namespace Microsoft.AspNetCore.Http;
+
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class AcceptedAtRouteResult : ObjectResult
+///
+/// An that on execution will write an object to the response
+/// with status code Accepted (202) and Location header.
+/// Targets a registered route.
+///
+public sealed class AcceptedAtRouteHttpResult : IResult
{
///
- /// Initializes a new instance of the class with the values
+ /// Initializes a new instance of the class with the values
/// provided.
///
/// The route data to use for generating the URL.
/// The value to format in the entity body.
- public AcceptedAtRouteResult(object? routeValues, object? value)
+ internal AcceptedAtRouteHttpResult(object? routeValues, object? value)
: this(routeName: null, routeValues: routeValues, value: value)
{
}
///
- /// Initializes a new instance of the class with the values
+ /// Initializes a new instance of the class with the values
/// provided.
///
/// The name of the route to use for generating the URL.
/// The route data to use for generating the URL.
/// The value to format in the entity body.
- public AcceptedAtRouteResult(
+ internal AcceptedAtRouteHttpResult(
string? routeName,
object? routeValues,
object? value)
- : base(value, StatusCodes.Status202Accepted)
{
+ Value = value;
RouteName = routeName;
RouteValues = new RouteValueDictionary(routeValues);
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
}
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; }
+
///
/// Gets the name of the route to use for generating the URL.
///
@@ -46,12 +59,17 @@ public AcceptedAtRouteResult(
///
public RouteValueDictionary RouteValues { get; }
- ///
- protected override void ConfigureResponseHeaders(HttpContext context)
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status202Accepted;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
{
- var linkGenerator = context.RequestServices.GetRequiredService();
+ var linkGenerator = httpContext.RequestServices.GetRequiredService();
var url = linkGenerator.GetUriByAddress(
- context,
+ httpContext,
RouteName,
RouteValues,
fragment: FragmentString.Empty);
@@ -61,6 +79,11 @@ protected override void ConfigureResponseHeaders(HttpContext context)
throw new InvalidOperationException("No route matches the supplied values.");
}
- context.Response.Headers.Location = url;
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.AcceptedAtRouteResult");
+
+ httpContext.Response.Headers.Location = url;
+ return HttpResultsHelper.WriteResultAsJsonAsync(httpContext, logger, Value, StatusCode);
}
}
diff --git a/src/Http/Http.Results/src/AcceptedHttpResult.cs b/src/Http/Http.Results/src/AcceptedHttpResult.cs
new file mode 100644
index 000000000000..d0b6026e0c56
--- /dev/null
+++ b/src/Http/Http.Results/src/AcceptedHttpResult.cs
@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response
+/// with status code Accepted (202) and Location header.
+/// Targets a registered route.
+///
+public sealed class AcceptedHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The location at which the status of requested content can be monitored.
+ /// The value to format in the entity body.
+ internal AcceptedHttpResult(string? location, object? value)
+ {
+ Value = value;
+ Location = location;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The location at which the status of requested content can be monitored.
+ /// The value to format in the entity body.
+ internal AcceptedHttpResult(Uri locationUri, object? value)
+ {
+ Value = value;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+
+ if (locationUri == null)
+ {
+ throw new ArgumentNullException(nameof(locationUri));
+ }
+
+ if (locationUri.IsAbsoluteUri)
+ {
+ Location = locationUri.AbsoluteUri;
+ }
+ else
+ {
+ Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
+ }
+ }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status202Accepted;
+
+ ///
+ /// Gets the location at which the status of the requested content can be monitored.
+ ///
+ public string? Location { get; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ if (!string.IsNullOrEmpty(Location))
+ {
+ httpContext.Response.Headers.Location = Location;
+ }
+
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.AcceptedResult");
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger,
+ Value,
+ StatusCode);
+ }
+}
diff --git a/src/Http/Http.Results/src/AcceptedResult.cs b/src/Http/Http.Results/src/AcceptedResult.cs
deleted file mode 100644
index 0f836f4db67b..000000000000
--- a/src/Http/Http.Results/src/AcceptedResult.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class AcceptedResult : ObjectResult
-{
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- public AcceptedResult()
- : base(value: null, StatusCodes.Status202Accepted)
- {
- }
-
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The location at which the status of requested content can be monitored.
- /// The value to format in the entity body.
- public AcceptedResult(string? location, object? value)
- : base(value, StatusCodes.Status202Accepted)
- {
- Location = location;
- }
-
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The location at which the status of requested content can be monitored.
- /// The value to format in the entity body.
- public AcceptedResult(Uri locationUri, object? value)
- : base(value, StatusCodes.Status202Accepted)
- {
- if (locationUri == null)
- {
- throw new ArgumentNullException(nameof(locationUri));
- }
-
- if (locationUri.IsAbsoluteUri)
- {
- Location = locationUri.AbsoluteUri;
- }
- else
- {
- Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
- }
- }
-
- ///
- /// Gets or sets the location at which the status of the requested content can be monitored.
- ///
- public string? Location { get; set; }
-
- ///
- protected override void ConfigureResponseHeaders(HttpContext context)
- {
- if (!string.IsNullOrEmpty(Location))
- {
- context.Response.Headers.Location = Location;
- }
- }
-}
diff --git a/src/Http/Http.Results/src/BadRequestObjectHttpResult.cs b/src/Http/Http.Results/src/BadRequestObjectHttpResult.cs
new file mode 100644
index 000000000000..dc9ad6e4fb2f
--- /dev/null
+++ b/src/Http/Http.Results/src/BadRequestObjectHttpResult.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response
+/// with Bad Request (400) status code.
+///
+public sealed class BadRequestObjectHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The error content to format in the entity body.
+ internal BadRequestObjectHttpResult(object? error)
+ {
+ Value = error;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; internal init; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status400BadRequest;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.BadRequestObjectResult");
+
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger: logger,
+ Value,
+ StatusCode);
+ }
+}
diff --git a/src/Http/Http.Results/src/BadRequestObjectResult.cs b/src/Http/Http.Results/src/BadRequestObjectResult.cs
deleted file mode 100644
index 7f58b7c9d6bc..000000000000
--- a/src/Http/Http.Results/src/BadRequestObjectResult.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class BadRequestObjectResult : ObjectResult
-{
- public BadRequestObjectResult(object? error)
- : base(error, StatusCodes.Status400BadRequest)
- {
- }
-}
diff --git a/src/Http/Http.Results/src/ChallengeResult.cs b/src/Http/Http.Results/src/ChallengeHttpResult.cs
similarity index 62%
rename from src/Http/Http.Results/src/ChallengeResult.cs
rename to src/Http/Http.Results/src/ChallengeHttpResult.cs
index a0aa35ae28fe..cff6c06937dd 100644
--- a/src/Http/Http.Results/src/ChallengeResult.cs
+++ b/src/Http/Http.Results/src/ChallengeHttpResult.cs
@@ -6,84 +6,93 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result;
+namespace Microsoft.AspNetCore.Http;
///
/// An that on execution invokes .
///
-internal sealed partial class ChallengeResult : IResult
+public sealed partial class ChallengeHttpResult : IResult
{
///
- /// Initializes a new instance of .
+ /// Initializes a new instance of .
///
- public ChallengeResult()
+ internal ChallengeHttpResult()
: this(Array.Empty())
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication scheme.
///
/// The authentication scheme to challenge.
- public ChallengeResult(string authenticationScheme)
+ internal ChallengeHttpResult(string authenticationScheme)
: this(new[] { authenticationScheme })
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication schemes.
///
/// The authentication schemes to challenge.
- public ChallengeResult(IList authenticationSchemes)
+ internal ChallengeHttpResult(IList authenticationSchemes)
: this(authenticationSchemes, properties: null)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified .
///
/// used to perform the authentication
/// challenge.
- public ChallengeResult(AuthenticationProperties? properties)
+ internal ChallengeHttpResult(AuthenticationProperties? properties)
: this(Array.Empty(), properties)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication scheme and .
///
/// The authentication schemes to challenge.
/// used to perform the authentication
/// challenge.
- public ChallengeResult(string authenticationScheme, AuthenticationProperties? properties)
+ internal ChallengeHttpResult(string authenticationScheme, AuthenticationProperties? properties)
: this(new[] { authenticationScheme }, properties)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication schemes and .
///
/// The authentication scheme to challenge.
/// used to perform the authentication
/// challenge.
- public ChallengeResult(IList authenticationSchemes, AuthenticationProperties? properties)
+ internal ChallengeHttpResult(IList authenticationSchemes, AuthenticationProperties? properties)
{
- AuthenticationSchemes = authenticationSchemes;
+ AuthenticationSchemes = authenticationSchemes.AsReadOnly();
Properties = properties;
}
- public IList AuthenticationSchemes { get; init; } = Array.Empty();
+ ///
+ /// Gets the authentication schemes that are challenged.
+ ///
+ public IReadOnlyList AuthenticationSchemes { get; internal init; } = Array.Empty();
- public AuthenticationProperties? Properties { get; init; }
+ ///
+ /// Gets the used to perform the sign-out operation.
+ ///
+ public AuthenticationProperties? Properties { get; internal init; }
+ ///
public async Task ExecuteAsync(HttpContext httpContext)
{
- var logger = httpContext.RequestServices.GetRequiredService>();
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ChallengeResult");
Log.ChallengeResultExecuting(logger, AuthenticationSchemes);
@@ -102,7 +111,7 @@ public async Task ExecuteAsync(HttpContext httpContext)
private static partial class Log
{
- public static void ChallengeResultExecuting(ILogger logger, IList authenticationSchemes)
+ public static void ChallengeResultExecuting(ILogger logger, IReadOnlyList authenticationSchemes)
{
if (logger.IsEnabled(LogLevel.Information))
{
diff --git a/src/Http/Http.Results/src/ConflictObjectHttpResult.cs b/src/Http/Http.Results/src/ConflictObjectHttpResult.cs
new file mode 100644
index 000000000000..782775395bfc
--- /dev/null
+++ b/src/Http/Http.Results/src/ConflictObjectHttpResult.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response
+/// with Conflict (409) status code.
+///
+public sealed class ConflictObjectHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The error content to format in the entity body.
+ internal ConflictObjectHttpResult(object? error)
+ {
+ Value = error;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; internal init; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status409Conflict;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ConflictObjectResult");
+
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger: logger,
+ Value,
+ StatusCode);
+ }
+}
diff --git a/src/Http/Http.Results/src/ConflictObjectResult.cs b/src/Http/Http.Results/src/ConflictObjectResult.cs
deleted file mode 100644
index 68308b14d2eb..000000000000
--- a/src/Http/Http.Results/src/ConflictObjectResult.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class ConflictObjectResult : ObjectResult
-{
- public ConflictObjectResult(object? error) :
- base(error, StatusCodes.Status409Conflict)
- {
- }
-}
diff --git a/src/Http/Http.Results/src/ContentHttpResult.cs b/src/Http/Http.Results/src/ContentHttpResult.cs
new file mode 100644
index 000000000000..a50675408e2d
--- /dev/null
+++ b/src/Http/Http.Results/src/ContentHttpResult.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that when executed
+/// will produce a response with content.
+///
+public sealed partial class ContentHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values.
+ ///
+ /// The value to format in the entity body.
+ /// The Content-Type header for the response
+ internal ContentHttpResult(string? content, string? contentType)
+ : this(content, contentType, statusCode: null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values
+ ///
+ /// The value to format in the entity body.
+ /// The HTTP status code of the response.
+ /// The Content-Type header for the response
+ internal ContentHttpResult(string? content, string? contentType, int? statusCode)
+ {
+ Content = content;
+ StatusCode = statusCode;
+ ContentType = contentType;
+ }
+
+ ///
+ /// Gets or set the content representing the body of the response.
+ ///
+ public string? Content { get; internal init; }
+
+ ///
+ /// Gets or sets the Content-Type header for the response.
+ ///
+ public string? ContentType { get; internal init; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int? StatusCode { get; internal init; }
+
+ ///
+ /// Writes the content to the HTTP response.
+ ///
+ /// The for the current request.
+ /// A task that represents the asynchronous execute operation.
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ContentResult");
+
+ return HttpResultsHelper.WriteResultAsContentAsync(httpContext, logger, Content, StatusCode, ContentType);
+ }
+}
diff --git a/src/Http/Http.Results/src/ContentResult.cs b/src/Http/Http.Results/src/ContentResult.cs
deleted file mode 100644
index 4630b5945633..000000000000
--- a/src/Http/Http.Results/src/ContentResult.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Text;
-using Microsoft.AspNetCore.Internal;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed partial class ContentResult : IResult
-{
- private const string DefaultContentType = "text/plain; charset=utf-8";
- private static readonly Encoding DefaultEncoding = Encoding.UTF8;
-
- ///
- /// Gets or set the content representing the body of the response.
- ///
- public string? Content { get; init; }
-
- ///
- /// Gets or sets the Content-Type header for the response.
- ///
- public string? ContentType { get; init; }
-
- ///
- /// Gets or sets the HTTP status code.
- ///
- public int? StatusCode { get; init; }
-
- ///
- /// Writes the content to the HTTP response.
- ///
- /// The for the current request.
- /// A task that represents the asynchronous execute operation.
- public async Task ExecuteAsync(HttpContext httpContext)
- {
- var response = httpContext.Response;
-
- ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
- ContentType,
- response.ContentType,
- (DefaultContentType, DefaultEncoding),
- ResponseContentTypeHelper.GetEncoding,
- out var resolvedContentType,
- out var resolvedContentTypeEncoding);
-
- response.ContentType = resolvedContentType;
-
- if (StatusCode != null)
- {
- response.StatusCode = StatusCode.Value;
- }
-
- var logger = httpContext.RequestServices.GetRequiredService>();
-
- Log.ContentResultExecuting(logger, resolvedContentType);
-
- if (Content != null)
- {
- response.ContentLength = resolvedContentTypeEncoding.GetByteCount(Content);
- await response.WriteAsync(Content, resolvedContentTypeEncoding);
- }
- }
-
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Information,
- "Executing ContentResult with HTTP Response ContentType of {ContentType}",
- EventName = "ContentResultExecuting")]
- internal static partial void ContentResultExecuting(ILogger logger, string contentType);
- }
-}
diff --git a/src/Http/Http.Results/src/CreatedAtRouteHttpResult.cs b/src/Http/Http.Results/src/CreatedAtRouteHttpResult.cs
new file mode 100644
index 000000000000..f59593be8505
--- /dev/null
+++ b/src/Http/Http.Results/src/CreatedAtRouteHttpResult.cs
@@ -0,0 +1,92 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response
+/// with status code Created (201) and Location header.
+/// Targets a registered route.
+///
+public sealed class CreatedAtRouteHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The route data to use for generating the URL.
+ /// The value to format in the entity body.
+ internal CreatedAtRouteHttpResult(object? routeValues, object? value)
+ : this(routeName: null, routeValues: routeValues, value: value)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The name of the route to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The value to format in the entity body.
+ internal CreatedAtRouteHttpResult(
+ string? routeName,
+ object? routeValues,
+ object? value)
+ {
+ Value = value;
+ RouteName = routeName;
+ RouteValues = new RouteValueDictionary(routeValues);
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; }
+
+ ///
+ /// Gets the name of the route to use for generating the URL.
+ ///
+ public string? RouteName { get; }
+
+ ///
+ /// Gets the route data to use for generating the URL.
+ ///
+ public RouteValueDictionary? RouteValues { get; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status201Created;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var linkGenerator = httpContext.RequestServices.GetRequiredService();
+ var url = linkGenerator.GetUriByRouteValues(
+ httpContext,
+ RouteName,
+ RouteValues,
+ fragment: FragmentString.Empty);
+
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new InvalidOperationException("No route matches the supplied values.");
+ }
+
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.CreatedAtRouteResult");
+
+ httpContext.Response.Headers.Location = url;
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger,
+ Value,
+ StatusCode);
+ }
+}
diff --git a/src/Http/Http.Results/src/CreatedAtRouteResult.cs b/src/Http/Http.Results/src/CreatedAtRouteResult.cs
deleted file mode 100644
index 10b2fe9ab776..000000000000
--- a/src/Http/Http.Results/src/CreatedAtRouteResult.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.AspNetCore.Routing;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class CreatedAtRouteResult : ObjectResult
-{
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The route data to use for generating the URL.
- /// The value to format in the entity body.
- public CreatedAtRouteResult(object? routeValues, object? value)
- : this(routeName: null, routeValues: routeValues, value: value)
- {
- }
-
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The name of the route to use for generating the URL.
- /// The route data to use for generating the URL.
- /// The value to format in the entity body.
- public CreatedAtRouteResult(
- string? routeName,
- object? routeValues,
- object? value)
- : base(value, StatusCodes.Status201Created)
- {
- RouteName = routeName;
- RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
- }
-
- ///
- /// Gets or sets the name of the route to use for generating the URL.
- ///
- public string? RouteName { get; set; }
-
- ///
- /// Gets or sets the route data to use for generating the URL.
- ///
- public RouteValueDictionary? RouteValues { get; set; }
-
- ///
- protected override void ConfigureResponseHeaders(HttpContext context)
- {
- var linkGenerator = context.RequestServices.GetRequiredService();
- var url = linkGenerator.GetUriByRouteValues(
- context,
- RouteName,
- RouteValues,
- fragment: FragmentString.Empty);
-
- if (string.IsNullOrEmpty(url))
- {
- throw new InvalidOperationException("No route matches the supplied values.");
- }
-
- context.Response.Headers.Location = url;
- }
-}
diff --git a/src/Http/Http.Results/src/CreatedHttpResult.cs b/src/Http/Http.Results/src/CreatedHttpResult.cs
new file mode 100644
index 000000000000..4106cacd7fec
--- /dev/null
+++ b/src/Http/Http.Results/src/CreatedHttpResult.cs
@@ -0,0 +1,84 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response
+/// with status code Created (201) and Location header.
+///
+public sealed class CreatedHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The location at which the content has been created.
+ /// The value to format in the entity body.
+ internal CreatedHttpResult(string location, object? value)
+ {
+ Value = value;
+ Location = location;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The location at which the content has been created.
+ /// The value to format in the entity body.
+ internal CreatedHttpResult(Uri locationUri, object? value)
+ {
+ Value = value;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+
+ if (locationUri == null)
+ {
+ throw new ArgumentNullException(nameof(locationUri));
+ }
+
+ if (locationUri.IsAbsoluteUri)
+ {
+ Location = locationUri.AbsoluteUri;
+ }
+ else
+ {
+ Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
+ }
+ }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status201Created;
+
+ ///
+ public string? Location { get; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ if (!string.IsNullOrEmpty(Location))
+ {
+ httpContext.Response.Headers.Location = Location;
+ }
+
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.CreatedResult");
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger,
+ Value,
+ StatusCode);
+ }
+}
diff --git a/src/Http/Http.Results/src/CreatedResult.cs b/src/Http/Http.Results/src/CreatedResult.cs
deleted file mode 100644
index 78cc9a361bc0..000000000000
--- a/src/Http/Http.Results/src/CreatedResult.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class CreatedResult : ObjectResult
-{
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The location at which the content has been created.
- /// The value to format in the entity body.
- public CreatedResult(string location, object? value)
- : base(value, StatusCodes.Status201Created)
- {
- Location = location;
- }
-
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The location at which the content has been created.
- /// The value to format in the entity body.
- public CreatedResult(Uri location, object? value)
- : base(value, StatusCodes.Status201Created)
- {
- if (location == null)
- {
- throw new ArgumentNullException(nameof(location));
- }
-
- if (location.IsAbsoluteUri)
- {
- Location = location.AbsoluteUri;
- }
- else
- {
- Location = location.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
- }
- }
-
- ///
- /// Gets or sets the location at which the content has been created.
- ///
- public string Location { get; init; }
-
- ///
- protected override void ConfigureResponseHeaders(HttpContext context)
- {
- context.Response.Headers.Location = Location;
- }
-}
diff --git a/src/Http/Http.Results/src/EmptyResult.cs b/src/Http/Http.Results/src/EmptyHttpResult.cs
similarity index 61%
rename from src/Http/Http.Results/src/EmptyResult.cs
rename to src/Http/Http.Results/src/EmptyHttpResult.cs
index 739fa5783a34..9e9b25c166dd 100644
--- a/src/Http/Http.Results/src/EmptyResult.cs
+++ b/src/Http/Http.Results/src/EmptyHttpResult.cs
@@ -7,14 +7,18 @@ namespace Microsoft.AspNetCore.Http;
/// Represents an that when executed will
/// do nothing.
///
-internal sealed class EmptyResult : IResult
+public sealed class EmptyHttpResult : IResult
{
- internal static readonly EmptyResult Instance = new();
-
- private EmptyResult()
+ private EmptyHttpResult()
{
}
+ ///
+ /// Gets an instance of .
+ ///
+ public static EmptyHttpResult Instance { get; } = new();
+
+ ///
public Task ExecuteAsync(HttpContext httpContext)
{
return Task.CompletedTask;
diff --git a/src/Http/Http.Results/src/FileContentHttpResult.cs b/src/Http/Http.Results/src/FileContentHttpResult.cs
new file mode 100644
index 000000000000..56cd17d13944
--- /dev/null
+++ b/src/Http/Http.Results/src/FileContentHttpResult.cs
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http;
+
+///
+/// Represents an that when executed will
+/// write a file from the content to the response.
+///
+public sealed partial class FileContentHttpResult : IResult
+{
+ ///
+ /// Creates a new instance with
+ /// the provided and the
+ /// provided .
+ ///
+ /// The bytes that represent the file contents.
+ /// The Content-Type of the file.
+ internal FileContentHttpResult(ReadOnlyMemory fileContents, string? contentType)
+ : this(fileContents, contentType, fileDownloadName: null)
+ {
+ }
+
+ ///
+ /// Creates a new instance with
+ /// the provided , the provided
+ /// and the provided .
+ ///
+ /// The bytes that represent the file contents.
+ /// The Content-Type header of the response.
+ /// The suggested file name.
+ internal FileContentHttpResult(
+ ReadOnlyMemory fileContents,
+ string? contentType,
+ string? fileDownloadName)
+ : this(fileContents, contentType, fileDownloadName, enableRangeProcessing: false)
+ {
+ }
+
+ ///
+ /// Creates a new instance with the provided values.
+ ///
+ /// The bytes that represent the file contents.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ internal FileContentHttpResult(
+ ReadOnlyMemory fileContents,
+ string? contentType,
+ string? fileDownloadName,
+ bool enableRangeProcessing,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null)
+ {
+ FileContents = fileContents;
+ FileLength = fileContents.Length;
+ ContentType = contentType ?? "application/octet-stream";
+ FileDownloadName = fileDownloadName;
+ EnableRangeProcessing = enableRangeProcessing;
+ LastModified = lastModified;
+ EntityTag = entityTag;
+ }
+
+ ///
+ /// Gets the Content-Type header for the response.
+ ///
+ public string ContentType { get; internal set; }
+
+ ///
+ /// Gets the file name that will be used in the Content-Disposition header of the response.
+ ///
+ public string? FileDownloadName { get; internal set; }
+
+ ///
+ /// Gets the last modified information associated with the file result.
+ ///
+ public DateTimeOffset? LastModified { get; internal set; }
+
+ ///
+ /// Gets the etag associated with the file result.
+ ///
+ public EntityTagHeaderValue? EntityTag { get; internal init; }
+
+ ///
+ /// Gets the value that enables range processing for the file result.
+ ///
+ public bool EnableRangeProcessing { get; internal init; }
+
+ ///
+ /// Gets or sets the file length information .
+ ///
+ public long? FileLength { get; internal set; }
+
+ ///
+ /// Gets or sets the file contents.
+ ///
+ public ReadOnlyMemory FileContents { get; internal init; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.FileContentResult");
+
+ var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore(
+ httpContext,
+ logger,
+ FileDownloadName,
+ FileLength,
+ ContentType,
+ EnableRangeProcessing,
+ LastModified,
+ EntityTag);
+
+ return completed ?
+ Task.CompletedTask :
+ FileResultHelper.WriteFileAsync(httpContext, FileContents, range, rangeLength);
+ }
+}
diff --git a/src/Http/Http.Results/src/FileContentResult.cs b/src/Http/Http.Results/src/FileContentResult.cs
deleted file mode 100644
index f4a81e92a3c4..000000000000
--- a/src/Http/Http.Results/src/FileContentResult.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.AspNetCore.Internal;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed partial class FileContentResult : FileResult
-{
- ///
- /// Creates a new instance with
- /// the provided and the
- /// provided .
- ///
- /// The bytes that represent the file contents.
- /// The Content-Type header of the response.
- public FileContentResult(ReadOnlyMemory fileContents, string? contentType)
- : base(contentType)
- {
- FileContents = fileContents;
- FileLength = fileContents.Length;
- }
-
- ///
- /// Gets or sets the file contents.
- ///
- public ReadOnlyMemory FileContents { get; init; }
-
- protected override ILogger GetLogger(HttpContext httpContext)
- {
- return httpContext.RequestServices.GetRequiredService>();
- }
-
- protected override Task ExecuteCoreAsync(HttpContext httpContext, RangeItemHeaderValue? range, long rangeLength)
- {
- return FileResultHelper.WriteFileAsync(httpContext, FileContents, range, rangeLength);
- }
-}
diff --git a/src/Http/Http.Results/src/FileResult.cs b/src/Http/Http.Results/src/FileResult.cs
deleted file mode 100644
index a65bd86c82d3..000000000000
--- a/src/Http/Http.Results/src/FileResult.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.AspNetCore.Internal;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal abstract class FileResult : FileResultBase, IResult
-{
- public FileResult(string? contentType)
- : base(contentType)
- {
- }
-
- protected abstract ILogger GetLogger(HttpContext httpContext);
-
- protected abstract Task ExecuteCoreAsync(HttpContext httpContext, RangeItemHeaderValue? range, long rangeLength);
-
- public virtual Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = GetLogger(httpContext);
-
- Log.ExecutingFileResult(logger, this);
-
- var fileResultInfo = new FileResultInfo
- {
- ContentType = ContentType,
- EnableRangeProcessing = EnableRangeProcessing,
- EntityTag = EntityTag,
- FileDownloadName = FileDownloadName,
- LastModified = LastModified,
- };
-
- var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
- httpContext,
- fileResultInfo,
- FileLength,
- EnableRangeProcessing,
- LastModified,
- EntityTag,
- logger);
-
- if (!serveBody)
- {
- return Task.CompletedTask;
- }
-
- if (range != null && rangeLength == 0)
- {
- return Task.CompletedTask;
- }
-
- if (range != null)
- {
- FileResultHelper.Log.WritingRangeToBody(logger);
- }
-
- return ExecuteCoreAsync(httpContext, range, rangeLength);
- }
-}
diff --git a/src/Http/Http.Results/src/FileResultBase.cs b/src/Http/Http.Results/src/FileResultBase.cs
deleted file mode 100644
index a5cdeb88f9de..000000000000
--- a/src/Http/Http.Results/src/FileResultBase.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics.CodeAnalysis;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal abstract partial class FileResultBase
-{
- private string? _fileDownloadName;
-
- ///
- /// Creates a new instance with
- /// the provided .
- ///
- /// The Content-Type header of the response.
- protected FileResultBase(string? contentType)
- {
- ContentType = contentType ?? "application/octet-stream";
- }
-
- ///
- /// Gets the Content-Type header for the response.
- ///
- public string ContentType { get; }
-
- ///
- /// Gets the file name that will be used in the Content-Disposition header of the response.
- ///
- [AllowNull]
- public string FileDownloadName
- {
- get { return _fileDownloadName ?? string.Empty; }
- init { _fileDownloadName = value; }
- }
-
- ///
- /// Gets or sets the last modified information associated with the .
- ///
- public DateTimeOffset? LastModified { get; set; }
-
- ///
- /// Gets or sets the etag associated with the .
- ///
- public EntityTagHeaderValue? EntityTag { get; init; }
-
- ///
- /// Gets or sets the value that enables range processing for the .
- ///
- public bool EnableRangeProcessing { get; init; }
-
- public long? FileLength { get; set; }
-
- protected static partial class Log
- {
- public static void ExecutingFileResult(ILogger logger, FileResultBase fileResult)
- {
- if (logger.IsEnabled(LogLevel.Information))
- {
- var fileResultType = fileResult.GetType().Name;
- ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
- }
- }
-
- public static void ExecutingFileResult(ILogger logger, FileResultBase fileResult, string fileName)
- {
- if (logger.IsEnabled(LogLevel.Information))
- {
- var fileResultType = fileResult.GetType().Name;
- ExecutingFileResult(logger, fileResultType, fileName, fileResult.FileDownloadName);
- }
- }
-
- [LoggerMessage(1, LogLevel.Information,
- "Executing {FileResultType}, sending file with download name '{FileDownloadName}'.",
- EventName = "ExecutingFileResultWithNoFileName",
- SkipEnabledCheck = true)]
- private static partial void ExecutingFileResultWithNoFileName(ILogger logger, string fileResultType, string fileDownloadName);
-
- [LoggerMessage(2, LogLevel.Information,
- "Executing {FileResultType}, sending file '{FileDownloadPath}' with download name '{FileDownloadName}'.",
- EventName = "ExecutingFileResult",
- SkipEnabledCheck = true)]
- private static partial void ExecutingFileResult(ILogger logger, string fileResultType, string fileDownloadPath, string fileDownloadName);
- }
-}
diff --git a/src/Http/Http.Results/src/FileStreamHttpResult.cs b/src/Http/Http.Results/src/FileStreamHttpResult.cs
new file mode 100644
index 000000000000..4a2273c535f0
--- /dev/null
+++ b/src/Http/Http.Results/src/FileStreamHttpResult.cs
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http;
+
+///
+/// Represents an that when executed will
+/// write a file from a stream to the response.
+///
+public sealed class FileStreamHttpResult : IResult
+{
+ ///
+ /// Creates a new instance with
+ /// the provided and the
+ /// provided .
+ ///
+ /// The stream with the file.
+ /// The Content-Type of the file.
+ internal FileStreamHttpResult(Stream fileStream, string? contentType)
+ : this(fileStream, contentType, fileDownloadName: null)
+ {
+ }
+
+ ///
+ /// Creates a new instance with
+ /// the provided , the provided
+ /// and the provided .
+ ///
+ /// The stream with the file.
+ /// The Content-Type header of the response.
+ /// The suggested file name.
+ internal FileStreamHttpResult(
+ Stream fileStream,
+ string? contentType,
+ string? fileDownloadName)
+ : this(fileStream, contentType, fileDownloadName, enableRangeProcessing: false)
+ {
+ }
+
+ ///
+ /// Creates a new instance with the provided values.
+ ///
+ /// The stream with the file.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ internal FileStreamHttpResult(
+ Stream fileStream,
+ string? contentType,
+ string? fileDownloadName,
+ bool enableRangeProcessing,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null)
+ {
+ if (fileStream == null)
+ {
+ throw new ArgumentNullException(nameof(fileStream));
+ }
+
+ FileStream = fileStream;
+ if (fileStream.CanSeek)
+ {
+ FileLength = fileStream.Length;
+ }
+
+ ContentType = contentType ?? "application/octet-stream";
+ FileDownloadName = fileDownloadName;
+ EnableRangeProcessing = enableRangeProcessing;
+ LastModified = lastModified;
+ EntityTag = entityTag;
+ }
+
+ ///
+ /// Gets the Content-Type header for the response.
+ ///
+ public string ContentType { get; internal set; }
+
+ ///
+ /// Gets the file name that will be used in the Content-Disposition header of the response.
+ ///
+ public string? FileDownloadName { get; internal set; }
+
+ ///
+ /// Gets the last modified information associated with the file result.
+ ///
+ public DateTimeOffset? LastModified { get; internal set; }
+
+ ///
+ /// Gets the etag associated with the file result.
+ ///
+ public EntityTagHeaderValue? EntityTag { get; internal init; }
+
+ ///
+ /// Gets the value that enables range processing for the file result.
+ ///
+ public bool EnableRangeProcessing { get; internal init; }
+
+ ///
+ /// Gets or sets the file length information .
+ ///
+ public long? FileLength { get; internal set; }
+
+ ///
+ /// Gets or sets the stream with the file that will be sent back as the response.
+ ///
+ public Stream FileStream { get; }
+
+ ///
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.FileStreamResult");
+
+ await using (FileStream)
+ {
+ var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore(
+ httpContext,
+ logger,
+ FileDownloadName,
+ FileLength,
+ ContentType,
+ EnableRangeProcessing,
+ LastModified,
+ EntityTag);
+
+ if (!completed)
+ {
+ await FileResultHelper.WriteFileAsync(httpContext, FileStream, range, rangeLength);
+ }
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/FileStreamResult.cs b/src/Http/Http.Results/src/FileStreamResult.cs
deleted file mode 100644
index ce196d18a009..000000000000
--- a/src/Http/Http.Results/src/FileStreamResult.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.AspNetCore.Internal;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-///
-/// Represents an that when executed will
-/// write a file from a stream to the response.
-///
-internal sealed class FileStreamResult : FileResult, IResult
-{
- ///
- /// Creates a new instance with
- /// the provided and the
- /// provided .
- ///
- /// The stream with the file.
- /// The Content-Type header of the response.
- public FileStreamResult(Stream fileStream, string? contentType)
- : base(contentType)
- {
- if (fileStream == null)
- {
- throw new ArgumentNullException(nameof(fileStream));
- }
-
- FileStream = fileStream;
- if (fileStream.CanSeek)
- {
- FileLength = fileStream.Length;
- }
- }
-
- ///
- /// Gets or sets the stream with the file that will be sent back as the response.
- ///
- public Stream FileStream { get; }
-
- protected override ILogger GetLogger(HttpContext httpContext)
- {
- return httpContext.RequestServices.GetRequiredService>();
- }
-
- public override async Task ExecuteAsync(HttpContext httpContext)
- {
- await using (FileStream)
- {
- await base.ExecuteAsync(httpContext);
- }
- }
-
- protected override Task ExecuteCoreAsync(HttpContext context, RangeItemHeaderValue? range, long rangeLength)
- {
- return FileResultHelper.WriteFileAsync(context, FileStream, range, rangeLength);
- }
-}
diff --git a/src/Http/Http.Results/src/ForbidResult.cs b/src/Http/Http.Results/src/ForbidHttpResult.cs
similarity index 63%
rename from src/Http/Http.Results/src/ForbidResult.cs
rename to src/Http/Http.Results/src/ForbidHttpResult.cs
index fe66fce56ac0..f9d8d9c27897 100644
--- a/src/Http/Http.Results/src/ForbidResult.cs
+++ b/src/Http/Http.Results/src/ForbidHttpResult.cs
@@ -6,88 +6,93 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result;
+namespace Microsoft.AspNetCore.Http;
-internal sealed partial class ForbidResult : IResult
+///
+/// An that on execution invokes .
+///
+public sealed partial class ForbidHttpResult : IResult
{
///
- /// Initializes a new instance of .
+ /// Initializes a new instance of .
///
- public ForbidResult()
+ internal ForbidHttpResult()
: this(Array.Empty())
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication scheme.
///
/// The authentication scheme to challenge.
- public ForbidResult(string authenticationScheme)
+ internal ForbidHttpResult(string authenticationScheme)
: this(new[] { authenticationScheme })
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication schemes.
///
/// The authentication schemes to challenge.
- public ForbidResult(IList authenticationSchemes)
+ internal ForbidHttpResult(IList authenticationSchemes)
: this(authenticationSchemes, properties: null)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified .
///
/// used to perform the authentication
/// challenge.
- public ForbidResult(AuthenticationProperties? properties)
+ internal ForbidHttpResult(AuthenticationProperties? properties)
: this(Array.Empty(), properties)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication scheme and .
///
/// The authentication schemes to challenge.
/// used to perform the authentication
/// challenge.
- public ForbidResult(string authenticationScheme, AuthenticationProperties? properties)
+ internal ForbidHttpResult(string authenticationScheme, AuthenticationProperties? properties)
: this(new[] { authenticationScheme }, properties)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication schemes and .
///
/// The authentication scheme to challenge.
/// used to perform the authentication
/// challenge.
- public ForbidResult(IList authenticationSchemes, AuthenticationProperties? properties)
+ internal ForbidHttpResult(IList authenticationSchemes, AuthenticationProperties? properties)
{
- AuthenticationSchemes = authenticationSchemes;
+ AuthenticationSchemes = authenticationSchemes.AsReadOnly();
Properties = properties;
}
///
- /// Gets or sets the authentication schemes that are challenged.
+ /// Gets the authentication schemes that are challenged.
///
- public IList AuthenticationSchemes { get; init; }
+ public IReadOnlyList AuthenticationSchemes { get; internal init; }
///
- /// Gets or sets the used to perform the authentication challenge.
+ /// Gets the used to perform the authentication challenge.
///
- public AuthenticationProperties? Properties { get; init; }
+ public AuthenticationProperties? Properties { get; internal init; }
///
public async Task ExecuteAsync(HttpContext httpContext)
{
- var logger = httpContext.RequestServices.GetRequiredService>();
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ForbidResult");
Log.ForbidResultExecuting(logger, AuthenticationSchemes);
@@ -106,7 +111,7 @@ public async Task ExecuteAsync(HttpContext httpContext)
private static partial class Log
{
- public static void ForbidResultExecuting(ILogger logger, IList authenticationSchemes)
+ public static void ForbidResultExecuting(ILogger logger, IReadOnlyList authenticationSchemes)
{
if (logger.IsEnabled(LogLevel.Information))
{
diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs
new file mode 100644
index 000000000000..f447e1d9539e
--- /dev/null
+++ b/src/Http/Http.Results/src/HttpResultsHelper.cs
@@ -0,0 +1,215 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+using System.Text.Json;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http;
+internal static partial class HttpResultsHelper
+{
+ private const string DefaultContentType = "text/plain; charset=utf-8";
+ private static readonly Encoding DefaultEncoding = Encoding.UTF8;
+
+ public static Task WriteResultAsJsonAsync(
+ HttpContext httpContext,
+ ILogger logger,
+ object? value,
+ int? statusCode,
+ string? contentType = null,
+ JsonSerializerOptions? jsonSerializerOptions = null)
+ {
+ Log.WritingResultAsJson(logger, value, statusCode);
+
+ if (statusCode is { } code)
+ {
+ httpContext.Response.StatusCode = code;
+ }
+
+ if (value is null)
+ {
+ return Task.CompletedTask;
+ }
+
+ return httpContext.Response.WriteAsJsonAsync(
+ value,
+ value.GetType(),
+ options: jsonSerializerOptions,
+ contentType: contentType);
+ }
+
+ public static Task WriteResultAsContentAsync(
+ HttpContext httpContext,
+ ILogger logger,
+ string? content,
+ int? statusCode,
+ string? contentType = null)
+ {
+ var response = httpContext.Response;
+ ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
+ contentType,
+ response.ContentType,
+ (DefaultContentType, DefaultEncoding),
+ ResponseContentTypeHelper.GetEncoding,
+ out var resolvedContentType,
+ out var resolvedContentTypeEncoding);
+
+ response.ContentType = resolvedContentType;
+
+ if (statusCode is { } code)
+ {
+ response.StatusCode = code;
+ }
+
+ Log.WritingResultAsContent(logger, resolvedContentType);
+
+ if (content != null)
+ {
+ response.ContentLength = resolvedContentTypeEncoding.GetByteCount(content);
+ return response.WriteAsync(content, resolvedContentTypeEncoding);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public static (RangeItemHeaderValue? range, long rangeLength, bool completed) WriteResultAsFileCore(
+ HttpContext httpContext,
+ ILogger logger,
+ string? fileDownloadName,
+ long? fileLength,
+ string contentType,
+ bool enableRangeProcessing,
+ DateTimeOffset? lastModified,
+ EntityTagHeaderValue? entityTag)
+ {
+ var completed = false;
+ fileDownloadName ??= string.Empty;
+
+ Log.WritingResultAsFile(logger, fileDownloadName);
+
+ var fileResultInfo = new FileResultInfo
+ {
+ ContentType = contentType,
+ EnableRangeProcessing = enableRangeProcessing,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ LastModified = lastModified,
+ };
+
+ var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+ httpContext,
+ fileResultInfo,
+ fileLength,
+ enableRangeProcessing,
+ lastModified,
+ entityTag,
+ logger);
+
+ if (range != null)
+ {
+ FileResultHelper.Log.WritingRangeToBody(logger);
+ }
+
+ if (!serveBody)
+ {
+ completed = true;
+ }
+
+ if (range != null && rangeLength == 0)
+ {
+ completed = true;
+ }
+
+ return (range, rangeLength, completed);
+ }
+
+ public static void ApplyProblemDetailsDefaultsIfNeeded(object? value, int? statusCode)
+ {
+ if (value is ProblemDetails problemDetails)
+ {
+ ApplyProblemDetailsDefaults(problemDetails, statusCode);
+ }
+ }
+
+ public static void ApplyProblemDetailsDefaults(ProblemDetails problemDetails, int? statusCode)
+ {
+ // We allow StatusCode to be specified either on ProblemDetails or on the ObjectResult and use it to configure the other.
+ // This lets users write return Conflict(new Problem("some description"))
+ // or return Problem("some-problem", 422) and have the response have consistent fields.
+ if (problemDetails.Status is null)
+ {
+ if (statusCode is not null)
+ {
+ problemDetails.Status = statusCode;
+ }
+ else
+ {
+ problemDetails.Status = problemDetails is HttpValidationProblemDetails ?
+ StatusCodes.Status400BadRequest :
+ StatusCodes.Status500InternalServerError;
+ }
+ }
+
+ if (ProblemDetailsDefaults.Defaults.TryGetValue(problemDetails.Status.Value, out var defaults))
+ {
+ problemDetails.Title ??= defaults.Title;
+ problemDetails.Type ??= defaults.Type;
+ }
+ }
+
+ internal static partial class Log
+ {
+ public static void WritingResultAsJson(ILogger logger, object? value, int? statusCode)
+ {
+ if (logger.IsEnabled(LogLevel.Information))
+ {
+ if (value is null)
+ {
+ WritingResultAsJsonWithoutValue(logger, statusCode ?? StatusCodes.Status200OK);
+ }
+ else
+ {
+ var valueType = value.GetType().FullName!;
+ WritingResultAsJson(logger, type: valueType, statusCode ?? StatusCodes.Status200OK);
+ }
+ }
+ }
+ public static void WritingResultAsFile(ILogger logger, string fileDownloadName)
+ {
+ if (logger.IsEnabled(LogLevel.Information))
+ {
+ WritingResultAsFileWithNoFileName(logger, fileDownloadName: fileDownloadName);
+ }
+ }
+
+ [LoggerMessage(1, LogLevel.Information,
+ "Setting HTTP status code {StatusCode}.",
+ EventName = "WritingResultAsStatusCode")]
+ public static partial void WritingResultAsStatusCode(ILogger logger, int statusCode);
+
+ [LoggerMessage(2, LogLevel.Information,
+ "Write content with HTTP Response ContentType of {ContentType}",
+ EventName = "WritingResultAsContent")]
+ public static partial void WritingResultAsContent(ILogger logger, string contentType);
+
+ [LoggerMessage(3, LogLevel.Information, "Writing value of type '{Type}' as Json with status code '{StatusCode}'.",
+ EventName = "WritingResultAsJson",
+ SkipEnabledCheck = true)]
+ private static partial void WritingResultAsJson(ILogger logger, string type, int statusCode);
+
+ [LoggerMessage(4, LogLevel.Information, "Setting the status code '{StatusCode}' without value.",
+ EventName = "WritingResultAsJsonWithoutValue",
+ SkipEnabledCheck = true)]
+ private static partial void WritingResultAsJsonWithoutValue(ILogger logger, int statusCode);
+
+ [LoggerMessage(5, LogLevel.Information,
+ "Sending file with download name '{FileDownloadName}'.",
+ EventName = "WritingResultAsFileWithNoFileName",
+ SkipEnabledCheck = true)]
+ private static partial void WritingResultAsFileWithNoFileName(ILogger logger, string fileDownloadName);
+ }
+}
diff --git a/src/Http/Http.Results/src/JsonHttpResult.cs b/src/Http/Http.Results/src/JsonHttpResult.cs
new file mode 100644
index 000000000000..1e7800a32bdb
--- /dev/null
+++ b/src/Http/Http.Results/src/JsonHttpResult.cs
@@ -0,0 +1,99 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.Json;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http;
+
+///
+/// An action result which formats the given object as JSON.
+///
+public sealed class JsonHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values.
+ ///
+ /// The value to format in the entity body.
+ /// The serializer settings.
+ internal JsonHttpResult(object? value, JsonSerializerOptions? jsonSerializerOptions)
+ : this(value, statusCode: null, contentType: null, jsonSerializerOptions: jsonSerializerOptions)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values.
+ ///
+ /// The value to format in the entity body.
+ /// The HTTP status code of the response.
+ /// The serializer settings.
+ internal JsonHttpResult(object? value, int? statusCode, JsonSerializerOptions? jsonSerializerOptions)
+ : this(value, statusCode: statusCode, contentType: null, jsonSerializerOptions: jsonSerializerOptions)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values.
+ ///
+ /// The value to format in the entity body.
+ /// The value for the Content-Type header
+ /// The serializer settings.
+ internal JsonHttpResult(object? value, string? contentType, JsonSerializerOptions? jsonSerializerOptions)
+ : this(value, statusCode: null, contentType: contentType, jsonSerializerOptions: jsonSerializerOptions)
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values.
+ ///
+ /// The value to format in the entity body.
+ /// The HTTP status code of the response.
+ /// The serializer settings.
+ /// The value for the Content-Type header
+ internal JsonHttpResult(object? value, int? statusCode, string? contentType, JsonSerializerOptions? jsonSerializerOptions)
+ {
+ Value = value;
+ StatusCode = statusCode;
+ JsonSerializerOptions = jsonSerializerOptions;
+ ContentType = contentType;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ /// Gets or sets the serializer settings.
+ ///
+ public JsonSerializerOptions? JsonSerializerOptions { get; internal init; }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; }
+
+ ///
+ /// Gets the value for the Content-Type header.
+ ///
+ public string? ContentType { get; internal set; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int? StatusCode { get; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.JsonResult");
+
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger,
+ Value,
+ StatusCode,
+ ContentType,
+ JsonSerializerOptions);
+ }
+}
diff --git a/src/Http/Http.Results/src/JsonResult.cs b/src/Http/Http.Results/src/JsonResult.cs
deleted file mode 100644
index d2dd5920d5f3..000000000000
--- a/src/Http/Http.Results/src/JsonResult.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Text.Json;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-///
-/// An action result which formats the given object as JSON.
-///
-internal sealed partial class JsonResult : IResult
-{
- ///
- /// Gets or sets the representing the Content-Type header of the response.
- ///
- public string? ContentType { get; init; }
-
- ///
- /// Gets or sets the serializer settings.
- ///
- /// When using System.Text.Json, this should be an instance of
- ///
- ///
- /// When using Newtonsoft.Json, this should be an instance of JsonSerializerSettings.
- ///
- ///
- public JsonSerializerOptions? JsonSerializerOptions { get; init; }
-
- ///
- /// Gets or sets the HTTP status code.
- ///
- public int? StatusCode { get; init; }
-
- ///
- /// Gets or sets the value to be formatted.
- ///
- public object? Value { get; init; }
-
- ///
- /// Write the result as JSON to the HTTP response.
- ///
- /// The for the current request.
- /// A task that represents the asynchronous execute operation.
- Task IResult.ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService>();
- Log.JsonResultExecuting(logger, Value);
-
- if (StatusCode is int statusCode)
- {
- httpContext.Response.StatusCode = statusCode;
- }
-
- return httpContext.Response.WriteAsJsonAsync(Value, JsonSerializerOptions, ContentType);
- }
-
- private static partial class Log
- {
- public static void JsonResultExecuting(ILogger logger, object? value)
- {
- if (logger.IsEnabled(LogLevel.Information))
- {
- var type = value == null ? "null" : value.GetType().FullName!;
- JsonResultExecuting(logger, type);
- }
- }
-
- [LoggerMessage(1, LogLevel.Information,
- "Executing JsonResult, writing value of type '{Type}'.",
- EventName = "JsonResultExecuting",
- SkipEnabledCheck = true)]
- private static partial void JsonResultExecuting(ILogger logger, string type);
- }
-}
diff --git a/src/Http/Http.Results/src/LocalRedirectResult.cs b/src/Http/Http.Results/src/LocalRedirectResult.cs
deleted file mode 100644
index 0cec3ab1529e..000000000000
--- a/src/Http/Http.Results/src/LocalRedirectResult.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.AspNetCore.Internal;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-///
-/// An that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
-/// or Permanent Redirect (308) response with a Location header to the supplied local URL.
-///
-internal sealed partial class LocalRedirectResult : IResult
-{
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The local URL to redirect to.
- public LocalRedirectResult(string localUrl)
- : this(localUrl, permanent: false)
- {
- }
-
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The local URL to redirect to.
- /// Specifies whether the redirect should be permanent (301) or temporary (302).
- public LocalRedirectResult(string localUrl, bool permanent)
- : this(localUrl, permanent, preserveMethod: false)
- {
- }
-
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The local URL to redirect to.
- /// Specifies whether the redirect should be permanent (301) or temporary (302).
- /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request's method.
- public LocalRedirectResult(string localUrl, bool permanent, bool preserveMethod)
- {
- if (string.IsNullOrEmpty(localUrl))
- {
- throw new ArgumentException("Argument cannot be null or empty", nameof(localUrl));
- }
-
- Permanent = permanent;
- PreserveMethod = preserveMethod;
- Url = localUrl;
- }
-
- ///
- /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false.
- ///
- public bool Permanent { get; }
-
- ///
- /// Gets or sets an indication that the redirect preserves the initial request method.
- ///
- public bool PreserveMethod { get; }
-
- ///
- /// Gets or sets the local URL to redirect to.
- ///
- public string Url { get; }
-
- ///
- public Task ExecuteAsync(HttpContext httpContext)
- {
- if (!SharedUrlHelper.IsLocalUrl(Url))
- {
- throw new InvalidOperationException("The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.");
- }
-
- var destinationUrl = SharedUrlHelper.Content(httpContext, Url);
-
- // IsLocalUrl is called to handle URLs starting with '~/'.
- var logger = httpContext.RequestServices.GetRequiredService>();
-
- Log.LocalRedirectResultExecuting(logger, destinationUrl);
-
- if (PreserveMethod)
- {
- httpContext.Response.StatusCode = Permanent
- ? StatusCodes.Status308PermanentRedirect
- : StatusCodes.Status307TemporaryRedirect;
- httpContext.Response.Headers.Location = destinationUrl;
- }
- else
- {
- httpContext.Response.Redirect(destinationUrl, Permanent);
- }
-
- return Task.CompletedTask;
- }
-
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Information,
- "Executing LocalRedirectResult, redirecting to {Destination}.",
- EventName = "LocalRedirectResultExecuting")]
- public static partial void LocalRedirectResultExecuting(ILogger logger, string destination);
- }
-}
diff --git a/src/Http/Http.Results/src/NoContentHttpResult.cs b/src/Http/Http.Results/src/NoContentHttpResult.cs
new file mode 100644
index 000000000000..0339cc3f1f38
--- /dev/null
+++ b/src/Http/Http.Results/src/NoContentHttpResult.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// Represents an that when executed will
+/// produce an HTTP response with the No Content (204) status code.
+///
+public class NoContentHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal NoContentHttpResult()
+ {
+ }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status204NoContent;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.NoContentResult");
+
+ HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode);
+
+ httpContext.Response.StatusCode = StatusCode;
+
+ return Task.CompletedTask;
+ }
+}
diff --git a/src/Http/Http.Results/src/NoContentResult.cs b/src/Http/Http.Results/src/NoContentResult.cs
deleted file mode 100644
index 582484c406c0..000000000000
--- a/src/Http/Http.Results/src/NoContentResult.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal class NoContentResult : StatusCodeResult
-{
- public NoContentResult() : base(StatusCodes.Status204NoContent)
- {
- }
-}
diff --git a/src/Http/Http.Results/src/NotFoundObjectHttpResult.cs b/src/Http/Http.Results/src/NotFoundObjectHttpResult.cs
new file mode 100644
index 000000000000..45220b7d0483
--- /dev/null
+++ b/src/Http/Http.Results/src/NotFoundObjectHttpResult.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response
+/// with Not Found (404) status code.
+///
+public sealed class NotFoundObjectHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values.
+ ///
+ /// The value to format in the entity body.
+ internal NotFoundObjectHttpResult(object? value)
+ {
+ Value = value;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; internal init; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status404NotFound;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.NotFoundObjectResult");
+
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger: logger,
+ Value,
+ StatusCode);
+ }
+}
diff --git a/src/Http/Http.Results/src/NotFoundObjectResult.cs b/src/Http/Http.Results/src/NotFoundObjectResult.cs
deleted file mode 100644
index 5ce0e6083bc9..000000000000
--- a/src/Http/Http.Results/src/NotFoundObjectResult.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class NotFoundObjectResult : ObjectResult
-{
- public NotFoundObjectResult(object? value)
- : base(value, StatusCodes.Status404NotFound)
- {
- }
-}
diff --git a/src/Http/Http.Results/src/ObjectHttpResult.cs b/src/Http/Http.Results/src/ObjectHttpResult.cs
new file mode 100644
index 000000000000..252ab122a1da
--- /dev/null
+++ b/src/Http/Http.Results/src/ObjectHttpResult.cs
@@ -0,0 +1,80 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response.
+///
+internal sealed class ObjectHttpResult : IResult
+{
+ ///
+ /// Creates a new instance
+ /// with the provided .
+ ///
+ internal ObjectHttpResult(object? value)
+ : this(value, null)
+ {
+ }
+
+ ///
+ /// Creates a new instance with the provided
+ /// , .
+ ///
+ internal ObjectHttpResult(object? value, int? statusCode)
+ : this(value, statusCode, contentType: null)
+ {
+ }
+
+ ///
+ /// Creates a new instance with the provided
+ /// , and .
+ ///
+ internal ObjectHttpResult(object? value, int? statusCode, string? contentType)
+ {
+ Value = value;
+
+ if (value is ProblemDetails problemDetails)
+ {
+ HttpResultsHelper.ApplyProblemDetailsDefaults(problemDetails, statusCode);
+ statusCode ??= problemDetails.Status;
+ }
+
+ StatusCode = statusCode;
+ ContentType = contentType;
+ }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; internal init; }
+
+ ///
+ /// Gets or sets the value for the Content-Type header.
+ ///
+ public string? ContentType { get; internal init; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int? StatusCode { get; internal init; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ObjectResult");
+
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger: logger,
+ Value,
+ StatusCode,
+ ContentType);
+ }
+}
diff --git a/src/Http/Http.Results/src/ObjectResult.cs b/src/Http/Http.Results/src/ObjectResult.cs
deleted file mode 100644
index 3204d866b558..000000000000
--- a/src/Http/Http.Results/src/ObjectResult.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.AspNetCore.Http.Extensions;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal partial class ObjectResult : IResult
-{
- ///
- /// Creates a new instance with the provided .
- ///
- public ObjectResult(object? value)
- {
- Value = value;
- }
-
- ///
- /// Creates a new instance with the provided .
- ///
- public ObjectResult(object? value, int? statusCode)
- {
- Value = value;
- StatusCode = statusCode;
- }
-
- ///
- /// The object result.
- ///
- public object? Value { get; }
-
- ///
- /// Gets the HTTP status code.
- ///
- public int? StatusCode { get; set; }
-
- ///
- /// Gets the value for the Content-Type header.
- ///
- public string? ContentType { get; set; }
-
- public Task ExecuteAsync(HttpContext httpContext)
- {
- var loggerFactory = httpContext.RequestServices.GetRequiredService();
- var logger = loggerFactory.CreateLogger(GetType());
- Log.ObjectResultExecuting(logger, Value, StatusCode);
-
- if (Value is ProblemDetails problemDetails)
- {
- ApplyProblemDetailsDefaults(problemDetails);
- }
-
- if (StatusCode is { } statusCode)
- {
- httpContext.Response.StatusCode = statusCode;
- }
-
- ConfigureResponseHeaders(httpContext);
-
- if (Value is null)
- {
- return Task.CompletedTask;
- }
-
- OnFormatting(httpContext);
- return httpContext.Response.WriteAsJsonAsync(Value, Value.GetType(), options: null, contentType: ContentType);
- }
-
- protected virtual void OnFormatting(HttpContext httpContext)
- {
- }
-
- protected virtual void ConfigureResponseHeaders(HttpContext httpContext)
- {
- }
-
- private void ApplyProblemDetailsDefaults(ProblemDetails problemDetails)
- {
- // We allow StatusCode to be specified either on ProblemDetails or on the ObjectResult and use it to configure the other.
- // This lets users write return Conflict(new Problem("some description"))
- // or return Problem("some-problem", 422) and have the response have consistent fields.
- if (problemDetails.Status is null)
- {
- if (StatusCode is not null)
- {
- problemDetails.Status = StatusCode;
- }
- else
- {
- problemDetails.Status = problemDetails is HttpValidationProblemDetails ?
- StatusCodes.Status400BadRequest :
- StatusCodes.Status500InternalServerError;
- }
- }
-
- if (StatusCode is null)
- {
- StatusCode = problemDetails.Status;
- }
-
- if (ProblemDetailsDefaults.Defaults.TryGetValue(problemDetails.Status.Value, out var defaults))
- {
- problemDetails.Title ??= defaults.Title;
- problemDetails.Type ??= defaults.Type;
- }
- }
-
- private static partial class Log
- {
- public static void ObjectResultExecuting(ILogger logger, object? value, int? statusCode)
- {
- if (logger.IsEnabled(LogLevel.Information))
- {
- if (value is null)
- {
- ObjectResultExecutingWithoutValue(logger, statusCode ?? StatusCodes.Status200OK);
- }
- else
- {
- var valueType = value.GetType().FullName!;
- ObjectResultExecuting(logger, valueType, statusCode ?? StatusCodes.Status200OK);
- }
- }
- }
-
- [LoggerMessage(1, LogLevel.Information, "Writing value of type '{Type}' with status code '{StatusCode}'.", EventName = "ObjectResultExecuting", SkipEnabledCheck = true)]
- private static partial void ObjectResultExecuting(ILogger logger, string type, int statusCode);
-
- [LoggerMessage(2, LogLevel.Information, "Executing result with status code '{StatusCode}'.", EventName = "ObjectResultExecutingWithoutValue", SkipEnabledCheck = true)]
- private static partial void ObjectResultExecutingWithoutValue(ILogger logger, int statusCode);
- }
-}
diff --git a/src/Http/Http.Results/src/OkObjectHttpResult.cs b/src/Http/Http.Results/src/OkObjectHttpResult.cs
new file mode 100644
index 000000000000..2dedc139d7ab
--- /dev/null
+++ b/src/Http/Http.Results/src/OkObjectHttpResult.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response
+/// with Ok (200) status code.
+///
+public sealed class OkObjectHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values.
+ ///
+ /// The value to format in the entity body.
+ internal OkObjectHttpResult(object? value)
+ {
+ Value = value;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ /// Gets the object result.
+ ///
+ public object? Value { get; internal init; }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status200OK;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.OkObjectResult");
+
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger: logger,
+ Value,
+ StatusCode);
+ }
+}
diff --git a/src/Http/Http.Results/src/OkObjectResult.cs b/src/Http/Http.Results/src/OkObjectResult.cs
deleted file mode 100644
index 70013671deea..000000000000
--- a/src/Http/Http.Results/src/OkObjectResult.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class OkObjectResult : ObjectResult
-{
- public OkObjectResult(object? value)
- : base(value, StatusCodes.Status200OK)
- {
- }
-}
diff --git a/src/Http/Http.Results/src/PhysicalFileHttpResult.cs b/src/Http/Http.Results/src/PhysicalFileHttpResult.cs
new file mode 100644
index 000000000000..97f8e01dca38
--- /dev/null
+++ b/src/Http/Http.Results/src/PhysicalFileHttpResult.cs
@@ -0,0 +1,184 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http;
+
+///
+/// A on execution will write a file from disk to the response
+/// using mechanisms provided by the host.
+///
+public sealed partial class PhysicalFileHttpResult : IResult
+{
+ ///
+ /// Creates a new instance with
+ /// the provided and the provided .
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type header of the response.
+ internal PhysicalFileHttpResult(string fileName, string? contentType)
+ : this(fileName, contentType, fileDownloadName: null)
+ {
+ }
+
+ ///
+ /// Creates a new instance with
+ /// the provided , the provided
+ /// and the provided .
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type header of the response.
+ /// The suggested file name.
+ internal PhysicalFileHttpResult(
+ string fileName,
+ string? contentType,
+ string? fileDownloadName)
+ : this(fileName, contentType, fileDownloadName, enableRangeProcessing: false)
+ {
+ }
+
+ ///
+ /// Creates a new instance with the provided values.
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type header of the response.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ internal PhysicalFileHttpResult(
+ string fileName,
+ string? contentType,
+ string? fileDownloadName,
+ bool enableRangeProcessing,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null)
+ {
+ FileName = fileName;
+ ContentType = contentType ?? "application/octet-stream";
+ FileDownloadName = fileDownloadName;
+ EnableRangeProcessing = enableRangeProcessing;
+ LastModified = lastModified;
+ EntityTag = entityTag;
+ }
+
+ ///
+ /// Gets the Content-Type header for the response.
+ ///
+ public string ContentType { get; internal set; }
+
+ ///
+ /// Gets the file name that will be used in the Content-Disposition header of the response.
+ ///
+ public string? FileDownloadName { get; internal set; }
+
+ ///
+ /// Gets the last modified information associated with the file result.
+ ///
+ public DateTimeOffset? LastModified { get; internal set; }
+
+ ///
+ /// Gets the etag associated with the file result.
+ ///
+ public EntityTagHeaderValue? EntityTag { get; internal init; }
+
+ ///
+ /// Gets the value that enables range processing for the file result.
+ ///
+ public bool EnableRangeProcessing { get; internal init; }
+
+ ///
+ /// Gets or sets the file length information .
+ ///
+ public long? FileLength { get; internal set; }
+
+ ///
+ /// Gets or sets the path to the file that will be sent back as the response.
+ ///
+ public string FileName { get; }
+
+ // For testing
+ internal Func GetFileInfoWrapper { get; init; } =
+ static path => new FileInfoWrapper(path);
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var fileInfo = GetFileInfoWrapper(FileName);
+ if (!fileInfo.Exists)
+ {
+ throw new FileNotFoundException($"Could not find file: {FileName}", FileName);
+ }
+
+ LastModified ??= fileInfo.LastWriteTimeUtc;
+ FileLength = fileInfo.Length;
+
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.PhysicalFileResult");
+
+ var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore(
+ httpContext,
+ logger,
+ FileDownloadName,
+ FileLength,
+ ContentType,
+ EnableRangeProcessing,
+ LastModified,
+ EntityTag);
+
+ return completed ?
+ Task.CompletedTask :
+ ExecuteCoreAsync(httpContext, range, rangeLength, FileName);
+ }
+
+ private static Task ExecuteCoreAsync(HttpContext httpContext, RangeItemHeaderValue? range, long rangeLength, string fileName)
+ {
+ var response = httpContext.Response;
+ if (!Path.IsPathRooted(fileName))
+ {
+ throw new NotSupportedException($"Path '{fileName}' was not rooted.");
+ }
+
+ var offset = 0L;
+ var count = (long?)null;
+ if (range != null)
+ {
+ offset = range.From ?? 0L;
+ count = rangeLength;
+ }
+
+ return response.SendFileAsync(
+ fileName,
+ offset: offset,
+ count: count);
+ }
+
+ internal readonly struct FileInfoWrapper
+ {
+ public FileInfoWrapper(string path)
+ {
+ var fileInfo = new FileInfo(path);
+
+ // It means we are dealing with a symlink and need to get the information
+ // from the target file instead.
+ if (fileInfo.Exists && !string.IsNullOrEmpty(fileInfo.LinkTarget))
+ {
+ fileInfo = (FileInfo?)fileInfo.ResolveLinkTarget(returnFinalTarget: true) ?? fileInfo;
+ }
+
+ Exists = fileInfo.Exists;
+ Length = fileInfo.Length;
+ LastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
+ }
+
+ public bool Exists { get; init; }
+
+ public long Length { get; init; }
+
+ public DateTimeOffset LastWriteTimeUtc { get; init; }
+ }
+}
diff --git a/src/Http/Http.Results/src/PhysicalFileResult.cs b/src/Http/Http.Results/src/PhysicalFileResult.cs
deleted file mode 100644
index 63d883ea6cdc..000000000000
--- a/src/Http/Http.Results/src/PhysicalFileResult.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-///
-/// A on execution will write a file from disk to the response
-/// using mechanisms provided by the host.
-///
-internal sealed partial class PhysicalFileResult : FileResult, IResult
-{
- ///
- /// Creates a new instance with
- /// the provided and the provided .
- ///
- /// The path to the file. The path must be an absolute path.
- /// The Content-Type header of the response.
- public PhysicalFileResult(string fileName, string? contentType)
- : base(contentType)
- {
- FileName = fileName;
- }
-
- ///
- /// Gets or sets the path to the file that will be sent back as the response.
- ///
- public string FileName { get; }
-
- // For testing
- public Func GetFileInfoWrapper { get; init; } =
- static path => new FileInfoWrapper(path);
-
- protected override ILogger GetLogger(HttpContext httpContext)
- {
- return httpContext.RequestServices.GetRequiredService>();
- }
-
- public override Task ExecuteAsync(HttpContext httpContext)
- {
- var fileInfo = GetFileInfoWrapper(FileName);
- if (!fileInfo.Exists)
- {
- throw new FileNotFoundException($"Could not find file: {FileName}", FileName);
- }
-
- LastModified = LastModified ?? fileInfo.LastWriteTimeUtc;
- FileLength = fileInfo.Length;
-
- return base.ExecuteAsync(httpContext);
- }
-
- protected override Task ExecuteCoreAsync(HttpContext httpContext, RangeItemHeaderValue? range, long rangeLength)
- {
- var response = httpContext.Response;
- if (!Path.IsPathRooted(FileName))
- {
- throw new NotSupportedException($"Path '{FileName}' was not rooted.");
- }
-
- var offset = 0L;
- var count = (long?)null;
- if (range != null)
- {
- offset = range.From ?? 0L;
- count = rangeLength;
- }
-
- return response.SendFileAsync(
- FileName,
- offset: offset,
- count: count);
- }
-
- internal readonly struct FileInfoWrapper
- {
- public FileInfoWrapper(string path)
- {
- var fileInfo = new FileInfo(path);
-
- // It means we are dealing with a symlink and need to get the information
- // from the target file instead.
- if (fileInfo.Exists && !string.IsNullOrEmpty(fileInfo.LinkTarget))
- {
- fileInfo = (FileInfo?)fileInfo.ResolveLinkTarget(returnFinalTarget: true) ?? fileInfo;
- }
-
- Exists = fileInfo.Exists;
- Length = fileInfo.Length;
- LastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
- }
-
- public bool Exists { get; init; }
-
- public long Length { get; init; }
-
- public DateTimeOffset LastWriteTimeUtc { get; init; }
- }
-}
diff --git a/src/Http/Http.Results/src/ProblemHttpResult.cs b/src/Http/Http.Results/src/ProblemHttpResult.cs
new file mode 100644
index 000000000000..0a5c52a0fcca
--- /dev/null
+++ b/src/Http/Http.Results/src/ProblemHttpResult.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write Problem Details
+/// HTTP API responses based on https://tools.ietf.org/html/rfc7807
+///
+public sealed class ProblemHttpResult : IResult
+{
+ ///
+ /// Creates a new instance with
+ /// the provided .
+ ///
+ /// The instance to format in the entity body.
+ internal ProblemHttpResult(ProblemDetails problemDetails)
+ {
+ ProblemDetails = problemDetails;
+ HttpResultsHelper.ApplyProblemDetailsDefaults(ProblemDetails, statusCode: null);
+ }
+
+ ///
+ /// Gets the instance.
+ ///
+ public ProblemDetails ProblemDetails { get; }
+
+ ///
+ /// Gets or sets the value for the Content-Type header.
+ ///
+ public string ContentType => "application/problem+json";
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int? StatusCode => ProblemDetails.Status;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger(typeof(ProblemHttpResult));
+
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger,
+ value: ProblemDetails,
+ StatusCode,
+ ContentType);
+ }
+}
diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt
index aafb2178b5a7..b53f85370ac8 100644
--- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt
@@ -1,4 +1,148 @@
#nullable enable
+Microsoft.AspNetCore.Http.AcceptedAtRouteHttpResult
+Microsoft.AspNetCore.Http.AcceptedAtRouteHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.AcceptedAtRouteHttpResult.RouteName.get -> string?
+Microsoft.AspNetCore.Http.AcceptedAtRouteHttpResult.RouteValues.get -> Microsoft.AspNetCore.Routing.RouteValueDictionary!
+Microsoft.AspNetCore.Http.AcceptedAtRouteHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.AcceptedAtRouteHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.AcceptedHttpResult
+Microsoft.AspNetCore.Http.AcceptedHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.AcceptedHttpResult.Location.get -> string?
+Microsoft.AspNetCore.Http.AcceptedHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.AcceptedHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.BadRequestObjectHttpResult
+Microsoft.AspNetCore.Http.BadRequestObjectHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.BadRequestObjectHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.BadRequestObjectHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.ChallengeHttpResult
+Microsoft.AspNetCore.Http.ChallengeHttpResult.AuthenticationSchemes.get -> System.Collections.Generic.IReadOnlyList!
+Microsoft.AspNetCore.Http.ChallengeHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.ChallengeHttpResult.Properties.get -> Microsoft.AspNetCore.Authentication.AuthenticationProperties?
+Microsoft.AspNetCore.Http.ConflictObjectHttpResult
+Microsoft.AspNetCore.Http.ConflictObjectHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.ConflictObjectHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.ConflictObjectHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.ContentHttpResult
+Microsoft.AspNetCore.Http.ContentHttpResult.Content.get -> string?
+Microsoft.AspNetCore.Http.ContentHttpResult.ContentType.get -> string?
+Microsoft.AspNetCore.Http.ContentHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.ContentHttpResult.StatusCode.get -> int?
+Microsoft.AspNetCore.Http.CreatedAtRouteHttpResult
+Microsoft.AspNetCore.Http.CreatedAtRouteHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.CreatedAtRouteHttpResult.RouteName.get -> string?
+Microsoft.AspNetCore.Http.CreatedAtRouteHttpResult.RouteValues.get -> Microsoft.AspNetCore.Routing.RouteValueDictionary?
+Microsoft.AspNetCore.Http.CreatedAtRouteHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.CreatedAtRouteHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.CreatedHttpResult
+Microsoft.AspNetCore.Http.CreatedHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.CreatedHttpResult.Location.get -> string?
+Microsoft.AspNetCore.Http.CreatedHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.CreatedHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.EmptyHttpResult
+Microsoft.AspNetCore.Http.EmptyHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.FileContentHttpResult
+Microsoft.AspNetCore.Http.FileContentHttpResult.ContentType.get -> string!
+Microsoft.AspNetCore.Http.FileContentHttpResult.EnableRangeProcessing.get -> bool
+Microsoft.AspNetCore.Http.FileContentHttpResult.EntityTag.get -> Microsoft.Net.Http.Headers.EntityTagHeaderValue?
+Microsoft.AspNetCore.Http.FileContentHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.FileContentHttpResult.FileContents.get -> System.ReadOnlyMemory
+Microsoft.AspNetCore.Http.FileContentHttpResult.FileDownloadName.get -> string?
+Microsoft.AspNetCore.Http.FileContentHttpResult.FileLength.get -> long?
+Microsoft.AspNetCore.Http.FileContentHttpResult.LastModified.get -> System.DateTimeOffset?
+Microsoft.AspNetCore.Http.FileStreamHttpResult
+Microsoft.AspNetCore.Http.FileStreamHttpResult.ContentType.get -> string!
+Microsoft.AspNetCore.Http.FileStreamHttpResult.EnableRangeProcessing.get -> bool
+Microsoft.AspNetCore.Http.FileStreamHttpResult.EntityTag.get -> Microsoft.Net.Http.Headers.EntityTagHeaderValue?
+Microsoft.AspNetCore.Http.FileStreamHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.FileStreamHttpResult.FileDownloadName.get -> string?
+Microsoft.AspNetCore.Http.FileStreamHttpResult.FileLength.get -> long?
+Microsoft.AspNetCore.Http.FileStreamHttpResult.FileStream.get -> System.IO.Stream!
+Microsoft.AspNetCore.Http.FileStreamHttpResult.LastModified.get -> System.DateTimeOffset?
+Microsoft.AspNetCore.Http.ForbidHttpResult
+Microsoft.AspNetCore.Http.ForbidHttpResult.AuthenticationSchemes.get -> System.Collections.Generic.IReadOnlyList!
+Microsoft.AspNetCore.Http.ForbidHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.ForbidHttpResult.Properties.get -> Microsoft.AspNetCore.Authentication.AuthenticationProperties?
+Microsoft.AspNetCore.Http.JsonHttpResult
+Microsoft.AspNetCore.Http.JsonHttpResult.ContentType.get -> string?
+Microsoft.AspNetCore.Http.JsonHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.JsonHttpResult.JsonSerializerOptions.get -> System.Text.Json.JsonSerializerOptions?
+Microsoft.AspNetCore.Http.JsonHttpResult.StatusCode.get -> int?
+Microsoft.AspNetCore.Http.JsonHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.NoContentHttpResult
+Microsoft.AspNetCore.Http.NoContentHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.NoContentHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.NotFoundObjectHttpResult
+Microsoft.AspNetCore.Http.NotFoundObjectHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.NotFoundObjectHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.NotFoundObjectHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.OkObjectHttpResult
+Microsoft.AspNetCore.Http.OkObjectHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.OkObjectHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.OkObjectHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult.ContentType.get -> string!
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult.EnableRangeProcessing.get -> bool
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult.EntityTag.get -> Microsoft.Net.Http.Headers.EntityTagHeaderValue?
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult.FileDownloadName.get -> string?
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult.FileLength.get -> long?
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult.FileName.get -> string!
+Microsoft.AspNetCore.Http.PhysicalFileHttpResult.LastModified.get -> System.DateTimeOffset?
+Microsoft.AspNetCore.Http.ProblemHttpResult
+Microsoft.AspNetCore.Http.ProblemHttpResult.ContentType.get -> string!
+Microsoft.AspNetCore.Http.ProblemHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.ProblemHttpResult.ProblemDetails.get -> Microsoft.AspNetCore.Mvc.ProblemDetails!
+Microsoft.AspNetCore.Http.ProblemHttpResult.StatusCode.get -> int?
+Microsoft.AspNetCore.Http.PushStreamHttpResult
+Microsoft.AspNetCore.Http.PushStreamHttpResult.ContentType.get -> string!
+Microsoft.AspNetCore.Http.PushStreamHttpResult.EnableRangeProcessing.get -> bool
+Microsoft.AspNetCore.Http.PushStreamHttpResult.EntityTag.get -> Microsoft.Net.Http.Headers.EntityTagHeaderValue?
+Microsoft.AspNetCore.Http.PushStreamHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.PushStreamHttpResult.FileDownloadName.get -> string?
+Microsoft.AspNetCore.Http.PushStreamHttpResult.FileLength.get -> long?
+Microsoft.AspNetCore.Http.PushStreamHttpResult.LastModified.get -> System.DateTimeOffset?
+Microsoft.AspNetCore.Http.RedirectHttpResult
+Microsoft.AspNetCore.Http.RedirectHttpResult.AcceptLocalUrlOnly.get -> bool
+Microsoft.AspNetCore.Http.RedirectHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.RedirectHttpResult.Permanent.get -> bool
+Microsoft.AspNetCore.Http.RedirectHttpResult.PreserveMethod.get -> bool
+Microsoft.AspNetCore.Http.RedirectHttpResult.Url.get -> string!
+Microsoft.AspNetCore.Http.RedirectToRouteHttpResult
+Microsoft.AspNetCore.Http.RedirectToRouteHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.RedirectToRouteHttpResult.Fragment.get -> string?
+Microsoft.AspNetCore.Http.RedirectToRouteHttpResult.Permanent.get -> bool
+Microsoft.AspNetCore.Http.RedirectToRouteHttpResult.PreserveMethod.get -> bool
+Microsoft.AspNetCore.Http.RedirectToRouteHttpResult.RouteName.get -> string?
+Microsoft.AspNetCore.Http.RedirectToRouteHttpResult.RouteValues.get -> Microsoft.AspNetCore.Routing.RouteValueDictionary?
+Microsoft.AspNetCore.Http.SignInHttpResult
+Microsoft.AspNetCore.Http.SignInHttpResult.AuthenticationScheme.get -> string?
+Microsoft.AspNetCore.Http.SignInHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.SignInHttpResult.Principal.get -> System.Security.Claims.ClaimsPrincipal!
+Microsoft.AspNetCore.Http.SignInHttpResult.Properties.get -> Microsoft.AspNetCore.Authentication.AuthenticationProperties?
+Microsoft.AspNetCore.Http.SignOutHttpResult
+Microsoft.AspNetCore.Http.SignOutHttpResult.AuthenticationSchemes.get -> System.Collections.Generic.IReadOnlyList!
+Microsoft.AspNetCore.Http.SignOutHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.SignOutHttpResult.Properties.get -> Microsoft.AspNetCore.Authentication.AuthenticationProperties?
+Microsoft.AspNetCore.Http.StatusCodeHttpResult
+Microsoft.AspNetCore.Http.StatusCodeHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.StatusCodeHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.UnauthorizedHttpResult
+Microsoft.AspNetCore.Http.UnauthorizedHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.UnauthorizedHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.UnprocessableEntityObjectHttpResult
+Microsoft.AspNetCore.Http.UnprocessableEntityObjectHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.UnprocessableEntityObjectHttpResult.StatusCode.get -> int
+Microsoft.AspNetCore.Http.UnprocessableEntityObjectHttpResult.Value.get -> object?
+Microsoft.AspNetCore.Http.VirtualFileHttpResult
+Microsoft.AspNetCore.Http.VirtualFileHttpResult.ContentType.get -> string!
+Microsoft.AspNetCore.Http.VirtualFileHttpResult.EnableRangeProcessing.get -> bool
+Microsoft.AspNetCore.Http.VirtualFileHttpResult.EntityTag.get -> Microsoft.Net.Http.Headers.EntityTagHeaderValue?
+Microsoft.AspNetCore.Http.VirtualFileHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
+Microsoft.AspNetCore.Http.VirtualFileHttpResult.FileDownloadName.get -> string?
+Microsoft.AspNetCore.Http.VirtualFileHttpResult.FileLength.get -> long?
+Microsoft.AspNetCore.Http.VirtualFileHttpResult.FileName.get -> string!
+Microsoft.AspNetCore.Http.VirtualFileHttpResult.LastModified.get -> System.DateTimeOffset?
+static Microsoft.AspNetCore.Http.EmptyHttpResult.Instance.get -> Microsoft.AspNetCore.Http.EmptyHttpResult!
static Microsoft.AspNetCore.Http.Results.Bytes(System.ReadOnlyMemory contents, string? contentType = null, string? fileDownloadName = null, bool enableRangeProcessing = false, System.DateTimeOffset? lastModified = null, Microsoft.Net.Http.Headers.EntityTagHeaderValue? entityTag = null) -> Microsoft.AspNetCore.Http.IResult!
static Microsoft.AspNetCore.Http.Results.Empty.get -> Microsoft.AspNetCore.Http.IResult!
static Microsoft.AspNetCore.Http.Results.Stream(System.Func! streamWriterCallback, string? contentType = null, string? fileDownloadName = null, System.DateTimeOffset? lastModified = null, Microsoft.Net.Http.Headers.EntityTagHeaderValue? entityTag = null) -> Microsoft.AspNetCore.Http.IResult!
diff --git a/src/Http/Http.Results/src/PushStreamHttpResult.cs b/src/Http/Http.Results/src/PushStreamHttpResult.cs
new file mode 100644
index 000000000000..71f454af2bf2
--- /dev/null
+++ b/src/Http/Http.Results/src/PushStreamHttpResult.cs
@@ -0,0 +1,121 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http;
+
+///
+/// Represents an that when executed will
+/// write a file from the writer callback to the response.
+///
+public sealed class PushStreamHttpResult : IResult
+{
+ private readonly Func _streamWriterCallback;
+
+ ///
+ /// Creates a new instance with
+ /// the provided and the provided .
+ ///
+ /// The stream writer callback.
+ /// The Content-Type header of the response.
+ internal PushStreamHttpResult(Func streamWriterCallback, string? contentType)
+ : this(streamWriterCallback, contentType, fileDownloadName: null)
+ {
+ }
+
+ ///
+ /// Creates a new instance with
+ /// the provided , the provided
+ /// and the provided .
+ ///
+ /// The stream writer callback.
+ /// The Content-Type header of the response.
+ /// The suggested file name.
+ internal PushStreamHttpResult(
+ Func streamWriterCallback,
+ string? contentType,
+ string? fileDownloadName)
+ : this(streamWriterCallback, contentType, fileDownloadName, enableRangeProcessing: false)
+ {
+ }
+
+ ///
+ /// Creates a new instance with the provided values.
+ ///
+ /// The stream writer callback.
+ /// The Content-Type header of the response.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ internal PushStreamHttpResult(
+ Func streamWriterCallback,
+ string? contentType,
+ string? fileDownloadName,
+ bool enableRangeProcessing,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null)
+ {
+ _streamWriterCallback = streamWriterCallback;
+ ContentType = contentType ?? "application/octet-stream";
+ FileDownloadName = fileDownloadName;
+ EnableRangeProcessing = enableRangeProcessing;
+ LastModified = lastModified;
+ EntityTag = entityTag;
+ }
+
+ ///
+ /// Gets the Content-Type header for the response.
+ ///
+ public string ContentType { get; internal set; }
+
+ ///
+ /// Gets the file name that will be used in the Content-Disposition header of the response.
+ ///
+ public string? FileDownloadName { get; internal set; }
+
+ ///
+ /// Gets the last modified information associated with the file result.
+ ///
+ public DateTimeOffset? LastModified { get; internal set; }
+
+ ///
+ /// Gets the etag associated with the file result.
+ ///
+ public EntityTagHeaderValue? EntityTag { get; internal init; }
+
+ ///
+ /// Gets the value that enables range processing for the file result.
+ ///
+ public bool EnableRangeProcessing { get; internal init; }
+
+ ///
+ /// Gets or sets the file length information .
+ ///
+ public long? FileLength { get; internal set; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.PushStreamResult");
+
+ var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore(
+ httpContext,
+ logger,
+ FileDownloadName,
+ FileLength,
+ ContentType,
+ EnableRangeProcessing,
+ LastModified,
+ EntityTag);
+
+ return completed ?
+ Task.CompletedTask :
+ _streamWriterCallback(httpContext.Response.Body);
+ }
+}
diff --git a/src/Http/Http.Results/src/PushStreamResult.cs b/src/Http/Http.Results/src/PushStreamResult.cs
deleted file mode 100644
index c498bf69d95b..000000000000
--- a/src/Http/Http.Results/src/PushStreamResult.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class PushStreamResult : FileResult
-{
- private readonly Func _streamWriterCallback;
-
- public PushStreamResult(Func streamWriterCallback, string? contentType)
- : base(contentType)
- {
- _streamWriterCallback = streamWriterCallback;
- }
-
- protected override ILogger GetLogger(HttpContext httpContext)
- {
- return httpContext.RequestServices.GetRequiredService>();
- }
-
- protected override Task ExecuteCoreAsync(HttpContext httpContext, RangeItemHeaderValue? range, long rangeLength)
- {
- return _streamWriterCallback(httpContext.Response.Body);
- }
-}
diff --git a/src/Http/Http.Results/src/RedirectHttpResult.cs b/src/Http/Http.Results/src/RedirectHttpResult.cs
new file mode 100644
index 000000000000..3c1bdc6ce911
--- /dev/null
+++ b/src/Http/Http.Results/src/RedirectHttpResult.cs
@@ -0,0 +1,138 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http;
+
+///
+/// An that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
+/// or Permanent Redirect (308) response with a Location header to the supplied URL.
+///
+public sealed partial class RedirectHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The URL to redirect to.
+ internal RedirectHttpResult(string url)
+ : this(url, permanent: false)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The URL to redirect to.
+ /// Specifies whether the redirect should be permanent (301) or temporary (302).
+ internal RedirectHttpResult(string url, bool permanent)
+ : this(url, permanent, preserveMethod: false)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The URL to redirect to.
+ /// Specifies whether the redirect should be permanent (301) or temporary (302).
+ /// If set to true, make the temporary redirect (307)
+ /// or permanent redirect (308) preserve the initial request method.
+ internal RedirectHttpResult(string url, bool permanent, bool preserveMethod)
+ : this(url, acceptLocalUrlOnly: false, permanent, preserveMethod)
+ { }
+
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The URL to redirect to.
+ /// Specifies whether the redirect should be permanent (301) or temporary (302).
+ /// If set to true, make the temporary redirect (307)
+ /// or permanent redirect (308) preserve the initial request method.
+ /// If set to true, only local URLs are accepted
+ /// and will throw an exception when the supplied URL is not considered local.
+ internal RedirectHttpResult(string url, bool acceptLocalUrlOnly, bool permanent, bool preserveMethod)
+ {
+ if (url == null)
+ {
+ throw new ArgumentNullException(nameof(url));
+ }
+
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException("Argument cannot be null or empty", nameof(url));
+ }
+
+ Url = url;
+ Permanent = permanent;
+ PreserveMethod = preserveMethod;
+ AcceptLocalUrlOnly = acceptLocalUrlOnly;
+ }
+
+ ///
+ /// Gets the value that specifies that the redirect should be permanent if true or temporary if false.
+ ///
+ public bool Permanent { get; }
+
+ ///
+ /// Gets an indication that the redirect preserves the initial request method.
+ ///
+ public bool PreserveMethod { get; }
+
+ ///
+ /// Gets the URL to redirect to.
+ ///
+ public string Url { get; }
+
+ ///
+ /// Gets an indication that only local URLs are accepted.
+ ///
+ public bool AcceptLocalUrlOnly { get; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.RedirectResult");
+
+ var isLocalUrl = SharedUrlHelper.IsLocalUrl(Url);
+
+ if (AcceptLocalUrlOnly && !isLocalUrl)
+ {
+ throw new InvalidOperationException("The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.");
+ }
+
+ // IsLocalUrl is called to handle URLs starting with '~/'.
+ var destinationUrl = isLocalUrl ? SharedUrlHelper.Content(httpContext, contentPath: Url) : Url;
+
+ Log.RedirectResultExecuting(logger, destinationUrl);
+
+ if (PreserveMethod)
+ {
+ httpContext.Response.StatusCode = Permanent
+ ? StatusCodes.Status308PermanentRedirect
+ : StatusCodes.Status307TemporaryRedirect;
+ httpContext.Response.Headers.Location = destinationUrl;
+ }
+ else
+ {
+ httpContext.Response.Redirect(destinationUrl, Permanent);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing RedirectResult, redirecting to {Destination}.",
+ EventName = "RedirectResultExecuting")]
+ public static partial void RedirectResultExecuting(ILogger logger, string destination);
+ }
+}
diff --git a/src/Http/Http.Results/src/RedirectResult.cs b/src/Http/Http.Results/src/RedirectResult.cs
deleted file mode 100644
index b5bdf7b1775d..000000000000
--- a/src/Http/Http.Results/src/RedirectResult.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.AspNetCore.Internal;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed partial class RedirectResult : IResult
-{
- ///
- /// Initializes a new instance of the class with the values
- /// provided.
- ///
- /// The URL to redirect to.
- /// Specifies whether the redirect should be permanent (301) or temporary (302).
- /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.
- public RedirectResult(string url, bool permanent, bool preserveMethod)
- {
- if (url == null)
- {
- throw new ArgumentNullException(nameof(url));
- }
-
- if (string.IsNullOrEmpty(url))
- {
- throw new ArgumentException("Argument cannot be null or empty", nameof(url));
- }
-
- Permanent = permanent;
- PreserveMethod = preserveMethod;
- Url = url;
- }
-
- ///
- /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false.
- ///
- public bool Permanent { get; }
-
- ///
- /// Gets or sets an indication that the redirect preserves the initial request method.
- ///
- public bool PreserveMethod { get; }
-
- ///
- /// Gets or sets the URL to redirect to.
- ///
- public string Url { get; }
-
- ///
- public Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService>();
-
- // IsLocalUrl is called to handle URLs starting with '~/'.
- var destinationUrl = SharedUrlHelper.IsLocalUrl(Url) ? SharedUrlHelper.Content(httpContext, Url) : Url;
-
- Log.RedirectResultExecuting(logger, destinationUrl);
-
- if (PreserveMethod)
- {
- httpContext.Response.StatusCode = Permanent
- ? StatusCodes.Status308PermanentRedirect
- : StatusCodes.Status307TemporaryRedirect;
- httpContext.Response.Headers.Location = destinationUrl;
- }
- else
- {
- httpContext.Response.Redirect(destinationUrl, Permanent);
- }
-
- return Task.CompletedTask;
- }
-
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Information,
- "Executing RedirectResult, redirecting to {Destination}.",
- EventName = "RedirectResultExecuting")]
- public static partial void RedirectResultExecuting(ILogger logger, string destination);
- }
-}
diff --git a/src/Http/Http.Results/src/RedirectToRouteResult.cs b/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs
similarity index 76%
rename from src/Http/Http.Results/src/RedirectToRouteResult.cs
rename to src/Http/Http.Results/src/RedirectToRouteHttpResult.cs
index 1d7458fe6738..586694792e59 100644
--- a/src/Http/Http.Results/src/RedirectToRouteResult.cs
+++ b/src/Http/Http.Results/src/RedirectToRouteHttpResult.cs
@@ -5,32 +5,32 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result;
+namespace Microsoft.AspNetCore.Http;
///
/// An that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
/// or Permanent Redirect (308) response with a Location header.
/// Targets a registered route.
///
-internal sealed partial class RedirectToRouteResult : IResult
+public sealed partial class RedirectToRouteHttpResult : IResult
{
///
- /// Initializes a new instance of the with the values
+ /// Initializes a new instance of the with the values
/// provided.
///
/// The parameters for the route.
- public RedirectToRouteResult(object? routeValues)
+ internal RedirectToRouteHttpResult(object? routeValues)
: this(routeName: null, routeValues: routeValues)
{
}
///
- /// Initializes a new instance of the with the values
+ /// Initializes a new instance of the with the values
/// provided.
///
/// The name of the route.
/// The parameters for the route.
- public RedirectToRouteResult(
+ internal RedirectToRouteHttpResult(
string? routeName,
object? routeValues)
: this(routeName, routeValues, permanent: false)
@@ -38,13 +38,14 @@ public RedirectToRouteResult(
}
///
- /// Initializes a new instance of the with the values
+ /// Initializes a new instance of the with the values
/// provided.
///
/// The name of the route.
/// The parameters for the route.
- /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).
- public RedirectToRouteResult(
+ /// If set to true, makes the redirect permanent (301).
+ /// Otherwise a temporary redirect is used (302).
+ internal RedirectToRouteHttpResult(
string? routeName,
object? routeValues,
bool permanent)
@@ -53,14 +54,16 @@ public RedirectToRouteResult(
}
///
- /// Initializes a new instance of the with the values
+ /// Initializes a new instance of the with the values
/// provided.
///
/// The name of the route.
/// The parameters for the route.
- /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).
- /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.
- public RedirectToRouteResult(
+ /// If set to true, makes the redirect permanent (301).
+ /// Otherwise a temporary redirect is used (302).
+ /// If set to true, make the temporary redirect (307)
+ /// or permanent redirect (308) preserve the initial request method.
+ internal RedirectToRouteHttpResult(
string? routeName,
object? routeValues,
bool permanent,
@@ -70,13 +73,13 @@ public RedirectToRouteResult(
}
///
- /// Initializes a new instance of the with the values
+ /// Initializes a new instance of the with the values
/// provided.
///
/// The name of the route.
/// The parameters for the route.
/// The fragment to add to the URL.
- public RedirectToRouteResult(
+ internal RedirectToRouteHttpResult(
string? routeName,
object? routeValues,
string? fragment)
@@ -85,14 +88,15 @@ public RedirectToRouteResult(
}
///
- /// Initializes a new instance of the with the values
+ /// Initializes a new instance of the with the values
/// provided.
///
/// The name of the route.
/// The parameters for the route.
- /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).
+ /// If set to true, makes the redirect permanent (301).
+ /// Otherwise a temporary redirect is used (302).
/// The fragment to add to the URL.
- public RedirectToRouteResult(
+ internal RedirectToRouteHttpResult(
string? routeName,
object? routeValues,
bool permanent,
@@ -102,15 +106,17 @@ public RedirectToRouteResult(
}
///
- /// Initializes a new instance of the with the values
+ /// Initializes a new instance of the with the values
/// provided.
///
/// The name of the route.
/// The parameters for the route.
- /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).
- /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.
+ /// If set to true, makes the redirect permanent (301).
+ /// Otherwise a temporary redirect is used (302).
+ /// If set to true, make the temporary redirect (307)
+ /// or permanent redirect (308) preserve the initial request method.
/// The fragment to add to the URL.
- public RedirectToRouteResult(
+ internal RedirectToRouteHttpResult(
string? routeName,
object? routeValues,
bool permanent,
@@ -125,27 +131,27 @@ public RedirectToRouteResult(
}
///
- /// Gets or sets the name of the route to use for generating the URL.
+ /// Gets the name of the route to use for generating the URL.
///
public string? RouteName { get; }
///
- /// Gets or sets the route data to use for generating the URL.
+ /// Gets the route data to use for generating the URL.
///
public RouteValueDictionary? RouteValues { get; }
///
- /// Gets or sets an indication that the redirect is permanent.
+ /// Gets the value that specifies that the redirect should be permanent if true or temporary if false.
///
public bool Permanent { get; }
///
- /// Gets or sets an indication that the redirect preserves the initial request method.
+ /// Gets an indication that the redirect preserves the initial request method.
///
public bool PreserveMethod { get; }
///
- /// Gets or sets the fragment to add to the URL.
+ /// Gets the fragment to add to the URL.
///
public string? Fragment { get; }
@@ -159,12 +165,15 @@ public Task ExecuteAsync(HttpContext httpContext)
RouteName,
RouteValues,
fragment: Fragment == null ? FragmentString.Empty : new FragmentString("#" + Fragment));
+
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException("No route matches the supplied values.");
}
- var logger = httpContext.RequestServices.GetRequiredService>();
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.RedirectToRouteResult");
Log.RedirectToRouteResultExecuting(logger, destinationUrl, RouteName);
if (PreserveMethod)
diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs
index 0f0328e76110..89b9c12bde7d 100644
--- a/src/Http/Http.Results/src/Results.cs
+++ b/src/Http/Http.Results/src/Results.cs
@@ -7,7 +7,6 @@
using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http.Result;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
@@ -33,7 +32,7 @@ public static class Results
public static IResult Challenge(
AuthenticationProperties? properties = null,
IList? authenticationSchemes = null)
- => new ChallengeResult { AuthenticationSchemes = authenticationSchemes ?? Array.Empty(), Properties = properties };
+ => new ChallengeHttpResult(authenticationSchemes: authenticationSchemes ?? Array.Empty(), properties);
///
/// Creates a that on execution invokes .
@@ -51,7 +50,7 @@ public static IResult Challenge(
/// a redirect to show a login page.
///
public static IResult Forbid(AuthenticationProperties? properties = null, IList? authenticationSchemes = null)
- => new ForbidResult { Properties = properties, AuthenticationSchemes = authenticationSchemes ?? Array.Empty(), };
+ => new ForbidHttpResult(authenticationSchemes: authenticationSchemes ?? Array.Empty(), properties);
///
/// Creates an that on execution invokes .
@@ -64,7 +63,7 @@ public static IResult SignIn(
ClaimsPrincipal principal,
AuthenticationProperties? properties = null,
string? authenticationScheme = null)
- => new SignInResult(authenticationScheme, principal, properties);
+ => new SignInHttpResult(principal, authenticationScheme, properties);
///
/// Creates an that on execution invokes .
@@ -73,7 +72,7 @@ public static IResult SignIn(
/// The authentication scheme to use for the sign-out operation.
/// The created for the response.
public static IResult SignOut(AuthenticationProperties? properties = null, IList? authenticationSchemes = null)
- => new SignOutResult(authenticationSchemes ?? Array.Empty(), properties);
+ => new SignOutHttpResult(authenticationSchemes ?? Array.Empty(), properties);
///
/// Writes the string to the HTTP response.
@@ -115,11 +114,7 @@ public static IResult Text(string content, string? contentType = null, Encoding?
mediaTypeHeaderValue.Encoding = contentEncoding ?? mediaTypeHeaderValue.Encoding;
}
- return new ContentResult
- {
- Content = content,
- ContentType = mediaTypeHeaderValue?.ToString()
- };
+ return new ContentHttpResult(content, mediaTypeHeaderValue?.ToString());
}
///
@@ -129,11 +124,7 @@ public static IResult Text(string content, string? contentType = null, Encoding?
/// The content type (MIME type).
/// The created object for the response.
public static IResult Content(string content, MediaTypeHeaderValue contentType)
- => new ContentResult
- {
- Content = content,
- ContentType = contentType.ToString()
- };
+ => new ContentHttpResult(content, contentType.ToString());
///
/// Creates a that serializes the specified object to JSON.
@@ -142,17 +133,14 @@ public static IResult Content(string content, MediaTypeHeaderValue contentType)
/// The serializer options to use when serializing the value.
/// The content-type to set on the response.
/// The status code to set on the response.
- /// The created that serializes the specified
+ /// The created that serializes the specified
/// as JSON format for the response.
/// Callers should cache an instance of serializer settings to avoid
/// recreating cached data with each call.
public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null)
- => new JsonResult
+ => new JsonHttpResult(data, statusCode, options)
{
- Value = data,
- JsonSerializerOptions = options,
ContentType = contentType,
- StatusCode = statusCode,
};
///
@@ -165,11 +153,11 @@ public static IResult Json(object? data, JsonSerializerOptions? options = null,
/// This API is an alias for .
///
/// The file contents.
- /// The Content-Type of the file.
- /// The suggested file name.
- /// Set to true to enable range requests processing.
- /// The of when the file was last modified.
- /// The associated with the file.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The of when the file was last modified.
+ /// The associated with the file.
/// The created for the response.
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
public static IResult File(
@@ -180,7 +168,7 @@ public static IResult File(
bool enableRangeProcessing = false,
DateTimeOffset? lastModified = null,
EntityTagHeaderValue? entityTag = null)
- => new FileContentResult(fileContents, contentType)
+ => new FileContentHttpResult(fileContents, contentType)
{
FileDownloadName = fileDownloadName,
EnableRangeProcessing = enableRangeProcessing,
@@ -211,7 +199,7 @@ public static IResult Bytes(
bool enableRangeProcessing = false,
DateTimeOffset? lastModified = null,
EntityTagHeaderValue? entityTag = null)
- => new FileContentResult(contents, contentType)
+ => new FileContentHttpResult(contents, contentType)
{
FileDownloadName = fileDownloadName,
EnableRangeProcessing = enableRangeProcessing,
@@ -242,7 +230,7 @@ public static IResult Bytes(
bool enableRangeProcessing = false,
DateTimeOffset? lastModified = null,
EntityTagHeaderValue? entityTag = null)
- => new FileContentResult(contents, contentType)
+ => new FileContentHttpResult(contents, contentType)
{
FileDownloadName = fileDownloadName,
EnableRangeProcessing = enableRangeProcessing,
@@ -282,7 +270,7 @@ public static IResult File(
EntityTagHeaderValue? entityTag = null,
bool enableRangeProcessing = false)
{
- return new FileStreamResult(fileStream, contentType)
+ return new FileStreamHttpResult(fileStream, contentType)
{
LastModified = lastModified,
EntityTag = entityTag,
@@ -321,7 +309,7 @@ public static IResult Stream(
EntityTagHeaderValue? entityTag = null,
bool enableRangeProcessing = false)
{
- return new FileStreamResult(stream, contentType)
+ return new FileStreamHttpResult(stream, contentType)
{
LastModified = lastModified,
EntityTag = entityTag,
@@ -359,7 +347,7 @@ public static IResult Stream(
EntityTagHeaderValue? entityTag = null,
bool enableRangeProcessing = false)
{
- return new FileStreamResult(pipeReader.AsStream(), contentType)
+ return new FileStreamHttpResult(pipeReader.AsStream(), contentType)
{
LastModified = lastModified,
EntityTag = entityTag,
@@ -392,7 +380,7 @@ public static IResult Stream(
DateTimeOffset? lastModified = null,
EntityTagHeaderValue? entityTag = null)
{
- return new PushStreamResult(streamWriterCallback, contentType)
+ return new PushStreamHttpResult(streamWriterCallback, contentType)
{
LastModified = lastModified,
EntityTag = entityTag,
@@ -426,7 +414,7 @@ public static IResult File(
{
if (Path.IsPathRooted(path))
{
- return new PhysicalFileResult(path, contentType)
+ return new PhysicalFileHttpResult(path, contentType)
{
FileDownloadName = fileDownloadName,
LastModified = lastModified,
@@ -436,7 +424,7 @@ public static IResult File(
}
else
{
- return new VirtualFileResult(path, contentType)
+ return new VirtualFileHttpResult(path, contentType)
{
FileDownloadName = fileDownloadName,
LastModified = lastModified,
@@ -460,7 +448,7 @@ public static IResult File(
/// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.
/// The created for the response.
public static IResult Redirect(string url, bool permanent = false, bool preserveMethod = false)
- => new RedirectResult(url, permanent, preserveMethod);
+ => new RedirectHttpResult(url, permanent, preserveMethod);
///
/// Redirects to the specified .
@@ -476,7 +464,7 @@ public static IResult Redirect(string url, bool permanent = false, bool preserve
/// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.
/// The created for the response.
public static IResult LocalRedirect(string localUrl, bool permanent = false, bool preserveMethod = false)
- => new LocalRedirectResult(localUrl, permanent, preserveMethod);
+ => new RedirectHttpResult(localUrl, acceptLocalUrlOnly: true, permanent, preserveMethod);
///
/// Redirects to the specified route.
@@ -494,7 +482,7 @@ public static IResult LocalRedirect(string localUrl, bool permanent = false, boo
/// The fragment to add to the URL.
/// The created for the response.
public static IResult RedirectToRoute(string? routeName = null, object? routeValues = null, bool permanent = false, bool preserveMethod = false, string? fragment = null)
- => new RedirectToRouteResult(
+ => new RedirectToRouteHttpResult(
routeName: routeName,
routeValues: routeValues,
permanent: permanent,
@@ -502,12 +490,12 @@ public static IResult RedirectToRoute(string? routeName = null, object? routeVal
fragment: fragment);
///
- /// Creates a object by specifying a .
+ /// Creates a object by specifying a .
///
/// The status code to set on the response.
- /// The created object for the response.
+ /// The created object for the response.
public static IResult StatusCode(int statusCode)
- => new StatusCodeResult(statusCode);
+ => new StatusCodeHttpResult(statusCode);
///
/// Produces a response.
@@ -515,14 +503,14 @@ public static IResult StatusCode(int statusCode)
/// The value to be included in the HTTP response body.
/// The created for the response.
public static IResult NotFound(object? value = null)
- => new NotFoundObjectResult(value);
+ => new NotFoundObjectHttpResult(value);
///
/// Produces a response.
///
/// The created for the response.
public static IResult Unauthorized()
- => new UnauthorizedResult();
+ => new UnauthorizedHttpResult();
///
/// Produces a response.
@@ -530,7 +518,7 @@ public static IResult Unauthorized()
/// An error object to be included in the HTTP response body.
/// The created for the response.
public static IResult BadRequest(object? error = null)
- => new BadRequestObjectResult(error);
+ => new BadRequestObjectHttpResult(error);
///
/// Produces a response.
@@ -538,14 +526,14 @@ public static IResult BadRequest(object? error = null)
/// An error object to be included in the HTTP response body.
/// The created for the response.
public static IResult Conflict(object? error = null)
- => new ConflictObjectResult(error);
+ => new ConflictObjectHttpResult(error);
///
/// Produces a response.
///
/// The created for the response.
public static IResult NoContent()
- => new NoContentResult();
+ => new NoContentHttpResult();
///
/// Produces a response.
@@ -553,7 +541,7 @@ public static IResult NoContent()
/// The value to be included in the HTTP response body.
/// The created for the response.
public static IResult Ok(object? value = null)
- => new OkObjectResult(value);
+ => new OkObjectHttpResult(value);
///
/// Produces a response.
@@ -561,7 +549,7 @@ public static IResult Ok(object? value = null)
/// An error object to be included in the HTTP response body.
/// The created for the response.
public static IResult UnprocessableEntity(object? error = null)
- => new UnprocessableEntityObjectResult(error);
+ => new UnprocessableEntityObjectHttpResult(error);
///
/// Produces a response.
@@ -598,10 +586,7 @@ public static IResult Problem(
}
}
- return new ObjectResult(problemDetails)
- {
- ContentType = "application/problem+json",
- };
+ return new ProblemHttpResult(problemDetails);
}
///
@@ -611,10 +596,7 @@ public static IResult Problem(
/// The created for the response.
public static IResult Problem(ProblemDetails problemDetails)
{
- return new ObjectResult(problemDetails)
- {
- ContentType = "application/problem+json",
- };
+ return new ProblemHttpResult(problemDetails);
}
///
@@ -656,10 +638,7 @@ public static IResult ValidationProblem(
}
}
- return new ObjectResult(problemDetails)
- {
- ContentType = "application/problem+json",
- };
+ return new ProblemHttpResult(problemDetails);
}
///
@@ -675,7 +654,7 @@ public static IResult Created(string uri, object? value)
throw new ArgumentNullException(nameof(uri));
}
- return new CreatedResult(uri, value);
+ return new CreatedHttpResult(uri, value);
}
///
@@ -691,7 +670,7 @@ public static IResult Created(Uri uri, object? value)
throw new ArgumentNullException(nameof(uri));
}
- return new CreatedResult(uri, value);
+ return new CreatedHttpResult(uri, value);
}
///
@@ -702,7 +681,7 @@ public static IResult Created(Uri uri, object? value)
/// The value to be included in the HTTP response body.
/// The created for the response.
public static IResult CreatedAtRoute(string? routeName = null, object? routeValues = null, object? value = null)
- => new CreatedAtRouteResult(routeName, routeValues, value);
+ => new CreatedAtRouteHttpResult(routeName, routeValues, value);
///
/// Produces a response.
@@ -711,7 +690,7 @@ public static IResult CreatedAtRoute(string? routeName = null, object? routeValu
/// The optional content value to format in the response body.
/// The created for the response.
public static IResult Accepted(string? uri = null, object? value = null)
- => new AcceptedResult(uri, value);
+ => new AcceptedHttpResult(uri, value);
///
/// Produces a response.
@@ -721,12 +700,12 @@ public static IResult Accepted(string? uri = null, object? value = null)
/// The optional content value to format in the response body.
/// The created for the response.
public static IResult AcceptedAtRoute(string? routeName = null, object? routeValues = null, object? value = null)
- => new AcceptedAtRouteResult(routeName, routeValues, value);
+ => new AcceptedAtRouteHttpResult(routeName, routeValues, value);
///
/// Produces an empty result response, that when executed will do nothing.
///
- public static IResult Empty { get; } = EmptyResult.Instance;
+ public static IResult Empty { get; } = EmptyHttpResult.Instance;
///
/// Provides a container for external libraries to extend
diff --git a/src/Http/Http.Results/src/SignInResult.cs b/src/Http/Http.Results/src/SignInHttpResult.cs
similarity index 55%
rename from src/Http/Http.Results/src/SignInResult.cs
rename to src/Http/Http.Results/src/SignInHttpResult.cs
index d0bd19996157..a1b0d805e867 100644
--- a/src/Http/Http.Results/src/SignInResult.cs
+++ b/src/Http/Http.Results/src/SignInHttpResult.cs
@@ -6,53 +6,31 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result;
+namespace Microsoft.AspNetCore.Http;
///
/// An that on execution invokes .
///
-internal sealed partial class SignInResult : IResult
+public sealed partial class SignInHttpResult : IResult
{
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// default authentication scheme.
///
/// The claims principal containing the user claims.
- public SignInResult(ClaimsPrincipal principal)
- : this(authenticationScheme: null, principal, properties: null)
+ internal SignInHttpResult(ClaimsPrincipal principal)
+ : this(principal, authenticationScheme: null, properties: null)
{
}
///
- /// Initializes a new instance of with the
- /// specified authentication scheme.
- ///
- /// The authentication scheme to use when signing in the user.
- /// The claims principal containing the user claims.
- public SignInResult(string? authenticationScheme, ClaimsPrincipal principal)
- : this(authenticationScheme, principal, properties: null)
- {
- }
-
- ///
- /// Initializes a new instance of with the
- /// default authentication scheme and .
- ///
- /// The claims principal containing the user claims.
- /// used to perform the sign-in operation.
- public SignInResult(ClaimsPrincipal principal, AuthenticationProperties? properties)
- : this(authenticationScheme: null, principal, properties)
- {
- }
-
- ///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication scheme and .
///
- /// The authentication schemes to use when signing in the user.
/// The claims principal containing the user claims.
+ /// The authentication schemes to use when signing in the user.
/// used to perform the sign-in operation.
- public SignInResult(string? authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties? properties)
+ internal SignInHttpResult(ClaimsPrincipal principal, string? authenticationScheme, AuthenticationProperties? properties)
{
Principal = principal ?? throw new ArgumentNullException(nameof(principal));
AuthenticationScheme = authenticationScheme;
@@ -62,22 +40,24 @@ public SignInResult(string? authenticationScheme, ClaimsPrincipal principal, Aut
///
/// Gets or sets the authentication scheme that is used to perform the sign-in operation.
///
- public string? AuthenticationScheme { get; set; }
+ public string? AuthenticationScheme { get; internal init; }
///
/// Gets or sets the containing the user claims.
///
- public ClaimsPrincipal Principal { get; set; }
+ public ClaimsPrincipal Principal { get; internal init; }
///
/// Gets or sets the used to perform the sign-in operation.
///
- public AuthenticationProperties? Properties { get; set; }
+ public AuthenticationProperties? Properties { get; internal init; }
///
public Task ExecuteAsync(HttpContext httpContext)
{
- var logger = httpContext.RequestServices.GetRequiredService>();
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.SignInResult");
Log.SignInResultExecuting(logger, AuthenticationScheme, Principal);
diff --git a/src/Http/Http.Results/src/SignOutResult.cs b/src/Http/Http.Results/src/SignOutHttpResult.cs
similarity index 63%
rename from src/Http/Http.Results/src/SignOutResult.cs
rename to src/Http/Http.Results/src/SignOutHttpResult.cs
index 422037cfe9de..7d187fe52c15 100644
--- a/src/Http/Http.Results/src/SignOutResult.cs
+++ b/src/Http/Http.Results/src/SignOutHttpResult.cs
@@ -6,88 +6,95 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result;
+namespace Microsoft.AspNetCore.Http;
///
/// An that on execution invokes .
///
-internal sealed partial class SignOutResult : IResult
+public sealed partial class SignOutHttpResult : IResult
{
///
- /// Initializes a new instance of with the default sign out scheme.
+ /// Initializes a new instance of with the default sign out scheme.
///
- public SignOutResult()
+ internal SignOutHttpResult()
: this(Array.Empty())
{
}
///
- /// Initializes a new instance of with the default sign out scheme.
+ /// Initializes a new instance of with the default sign out scheme.
/// specified authentication scheme and .
///
/// used to perform the sign-out operation.
- public SignOutResult(AuthenticationProperties properties)
+ internal SignOutHttpResult(AuthenticationProperties properties)
: this(Array.Empty(), properties)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication scheme.
///
/// The authentication scheme to use when signing out the user.
- public SignOutResult(string authenticationScheme)
+ internal SignOutHttpResult(string authenticationScheme)
: this(new[] { authenticationScheme })
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication schemes.
///
/// The authentication schemes to use when signing out the user.
- public SignOutResult(IList authenticationSchemes)
+ internal SignOutHttpResult(IList authenticationSchemes)
: this(authenticationSchemes, properties: null)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication scheme and .
///
/// The authentication schemes to use when signing out the user.
/// used to perform the sign-out operation.
- public SignOutResult(string authenticationScheme, AuthenticationProperties? properties)
+ internal SignOutHttpResult(string authenticationScheme, AuthenticationProperties? properties)
: this(new[] { authenticationScheme }, properties)
{
}
///
- /// Initializes a new instance of with the
+ /// Initializes a new instance of with the
/// specified authentication schemes and .
///
/// The authentication scheme to use when signing out the user.
/// used to perform the sign-out operation.
- public SignOutResult(IList authenticationSchemes, AuthenticationProperties? properties)
+ internal SignOutHttpResult(IList authenticationSchemes, AuthenticationProperties? properties)
{
- AuthenticationSchemes = authenticationSchemes ?? throw new ArgumentNullException(nameof(authenticationSchemes));
+ if (authenticationSchemes is null)
+ {
+ throw new ArgumentNullException(nameof(authenticationSchemes));
+ }
+
+ AuthenticationSchemes = authenticationSchemes.AsReadOnly();
Properties = properties;
}
///
- /// Gets or sets the authentication schemes that are challenged.
+ /// Gets the authentication schemes that are challenged.
///
- public IList AuthenticationSchemes { get; init; }
+ public IReadOnlyList AuthenticationSchemes { get; internal init; }
///
- /// Gets or sets the used to perform the sign-out operation.
+ /// Gets the used to perform the sign-out operation.
///
- public AuthenticationProperties? Properties { get; init; }
+ public AuthenticationProperties? Properties { get; internal init; }
///
public async Task ExecuteAsync(HttpContext httpContext)
{
- var logger = httpContext.RequestServices.GetRequiredService>();
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.SignOutResult");
Log.SignOutResultExecuting(logger, AuthenticationSchemes);
@@ -106,7 +113,7 @@ public async Task ExecuteAsync(HttpContext httpContext)
private static partial class Log
{
- public static void SignOutResultExecuting(ILogger logger, IList authenticationSchemes)
+ public static void SignOutResultExecuting(ILogger logger, IReadOnlyList authenticationSchemes)
{
if (logger.IsEnabled(LogLevel.Information))
{
diff --git a/src/Http/Http.Results/src/StatusCodeResult.cs b/src/Http/Http.Results/src/StatusCodeHttpResult.cs
similarity index 56%
rename from src/Http/Http.Results/src/StatusCodeResult.cs
rename to src/Http/Http.Results/src/StatusCodeHttpResult.cs
index 2140eb5cc30f..3e24b45a7767 100644
--- a/src/Http/Http.Results/src/StatusCodeResult.cs
+++ b/src/Http/Http.Results/src/StatusCodeHttpResult.cs
@@ -1,19 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+namespace Microsoft.AspNetCore.Http;
+
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal partial class StatusCodeResult : IResult
+///
+/// Represents an that when executed will
+/// produce an HTTP response with the given response status code.
+///
+public sealed partial class StatusCodeHttpResult : IResult
{
///
- /// Initializes a new instance of the class
+ /// Initializes a new instance of the class
/// with the given .
///
/// The HTTP status code of the response.
- public StatusCodeResult(int statusCode)
+ internal StatusCodeHttpResult(int statusCode)
{
StatusCode = statusCode;
}
@@ -30,20 +34,13 @@ public StatusCodeResult(int statusCode)
/// A task that represents the asynchronous execute operation.
public Task ExecuteAsync(HttpContext httpContext)
{
- var factory = httpContext.RequestServices.GetRequiredService();
- var logger = factory.CreateLogger(GetType());
-
- Log.StatusCodeResultExecuting(logger, StatusCode);
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.StatusCodeResult");
+ HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode);
httpContext.Response.StatusCode = StatusCode;
- return Task.CompletedTask;
- }
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Information,
- "Executing StatusCodeResult, setting HTTP status code {StatusCode}.",
- EventName = "StatusCodeResultExecuting")]
- public static partial void StatusCodeResultExecuting(ILogger logger, int statusCode);
+ return Task.CompletedTask;
}
}
diff --git a/src/Http/Http.Results/src/UnauthorizedHttpResult.cs b/src/Http/Http.Results/src/UnauthorizedHttpResult.cs
new file mode 100644
index 000000000000..79bc2c17dd91
--- /dev/null
+++ b/src/Http/Http.Results/src/UnauthorizedHttpResult.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// Represents an that when executed will
+/// produce an HTTP response with the No Unauthorized (401) status code.
+///
+public sealed class UnauthorizedHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal UnauthorizedHttpResult()
+ {
+ }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode => StatusCodes.Status401Unauthorized;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.UnauthorizedResult");
+ HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode);
+
+ httpContext.Response.StatusCode = StatusCode;
+
+ return Task.CompletedTask;
+ }
+
+}
diff --git a/src/Http/Http.Results/src/UnauthorizedResult.cs b/src/Http/Http.Results/src/UnauthorizedResult.cs
deleted file mode 100644
index 28ec97eaf462..000000000000
--- a/src/Http/Http.Results/src/UnauthorizedResult.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class UnauthorizedResult : StatusCodeResult
-{
- public UnauthorizedResult() : base(StatusCodes.Status401Unauthorized)
- {
- }
-}
diff --git a/src/Http/Http.Results/src/UnprocessableEntityObjectHttpResult.cs b/src/Http/Http.Results/src/UnprocessableEntityObjectHttpResult.cs
new file mode 100644
index 000000000000..d1c8eda62dae
--- /dev/null
+++ b/src/Http/Http.Results/src/UnprocessableEntityObjectHttpResult.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Http;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+///
+/// An that on execution will write an object to the response
+/// with Unprocessable Entity (422) status code.
+///
+public sealed class UnprocessableEntityObjectHttpResult : IResult
+{
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The value to format in the entity body.
+ internal UnprocessableEntityObjectHttpResult(object? value)
+ {
+ Value = value;
+ HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode);
+ }
+
+ ///
+ public object? Value { get; internal init; }
+
+ /// >
+ public int StatusCode => StatusCodes.Status422UnprocessableEntity;
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.UnprocessableEntityObjectResult");
+
+ return HttpResultsHelper.WriteResultAsJsonAsync(
+ httpContext,
+ logger: logger,
+ Value,
+ StatusCode);
+ }
+}
diff --git a/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs b/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
deleted file mode 100644
index fd7b456eb887..000000000000
--- a/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-internal sealed class UnprocessableEntityObjectResult : ObjectResult
-{
- public UnprocessableEntityObjectResult(object? error)
- : base(error, StatusCodes.Status422UnprocessableEntity)
- {
- }
-}
diff --git a/src/Http/Http.Results/src/VirtualFileHttpResult.cs b/src/Http/Http.Results/src/VirtualFileHttpResult.cs
new file mode 100644
index 000000000000..cac64665d23d
--- /dev/null
+++ b/src/Http/Http.Results/src/VirtualFileHttpResult.cs
@@ -0,0 +1,161 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http;
+
+///
+/// A that on execution writes the file specified
+/// using a virtual path to the response using mechanisms provided by the host.
+///
+public sealed class VirtualFileHttpResult : IResult
+{
+ private string _fileName;
+
+ ///
+ /// Creates a new instance with
+ /// the provided and the provided .
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type header of the response.
+ internal VirtualFileHttpResult(string fileName, string? contentType)
+ : this(fileName, contentType, fileDownloadName: null)
+ {
+ }
+
+ ///
+ /// Creates a new instance with
+ /// the provided , the provided
+ /// and the provided .
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type header of the response.
+ /// The suggested file name.
+ internal VirtualFileHttpResult(
+ string fileName,
+ string? contentType,
+ string? fileDownloadName)
+ : this(fileName, contentType, fileDownloadName, enableRangeProcessing: false)
+ {
+ }
+
+ ///
+ /// Creates a new instance with the provided values.
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type header of the response.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ internal VirtualFileHttpResult(
+ string fileName,
+ string? contentType,
+ string? fileDownloadName,
+ bool enableRangeProcessing,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null)
+ {
+ FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
+ ContentType = contentType ?? "application/octet-stream";
+ FileDownloadName = fileDownloadName;
+ EnableRangeProcessing = enableRangeProcessing;
+ LastModified = lastModified;
+ EntityTag = entityTag;
+ }
+
+ ///
+ public string ContentType { get; internal set; }
+
+ ///
+ public string? FileDownloadName { get; internal set; }
+
+ ///
+ public DateTimeOffset? LastModified { get; internal set; }
+
+ ///
+ public EntityTagHeaderValue? EntityTag { get; internal init; }
+
+ ///
+ public bool EnableRangeProcessing { get; internal init; }
+
+ ///
+ public long? FileLength { get; internal set; }
+
+ ///
+ /// Gets or sets the path to the file that will be sent back as the response.
+ ///
+ public string FileName
+ {
+ get => _fileName;
+ [MemberNotNull(nameof(_fileName))]
+ internal set => _fileName = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var hostingEnvironment = httpContext.RequestServices.GetRequiredService();
+
+ var fileInfo = GetFileInformation(hostingEnvironment.WebRootFileProvider);
+ if (!fileInfo.Exists)
+ {
+ throw new FileNotFoundException($"Could not find file: {FileName}.", FileName);
+ }
+ LastModified = LastModified ?? fileInfo.LastModified;
+ FileLength = fileInfo.Length;
+
+ // Creating the logger with a string to preserve the category after the refactoring.
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.VirtualFileResult");
+
+ var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore(
+ httpContext,
+ logger,
+ FileDownloadName,
+ FileLength,
+ ContentType,
+ EnableRangeProcessing,
+ LastModified,
+ EntityTag);
+
+ return completed ?
+ Task.CompletedTask :
+ ExecuteCoreAsync(httpContext, range, rangeLength, fileInfo);
+ }
+
+ private static Task ExecuteCoreAsync(HttpContext httpContext, RangeItemHeaderValue? range, long rangeLength, IFileInfo fileInfo)
+ {
+ var response = httpContext.Response;
+ var offset = 0L;
+ var count = (long?)null;
+ if (range != null)
+ {
+ offset = range.From ?? 0L;
+ count = rangeLength;
+ }
+
+ return response.SendFileAsync(
+ fileInfo!,
+ offset,
+ count);
+ }
+
+ internal IFileInfo GetFileInformation(IFileProvider fileProvider)
+ {
+ var normalizedPath = FileName;
+ if (normalizedPath.StartsWith("~", StringComparison.Ordinal))
+ {
+ normalizedPath = normalizedPath.Substring(1);
+ }
+
+ var fileInfo = fileProvider.GetFileInfo(normalizedPath);
+ return fileInfo;
+ }
+}
diff --git a/src/Http/Http.Results/src/VirtualFileResult.cs b/src/Http/Http.Results/src/VirtualFileResult.cs
deleted file mode 100644
index 38d0c32eaf2c..000000000000
--- a/src/Http/Http.Results/src/VirtualFileResult.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics.CodeAnalysis;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNetCore.Http.Result;
-
-///
-/// A that on execution writes the file specified using a virtual path to the response
-/// using mechanisms provided by the host.
-///
-internal sealed class VirtualFileResult : FileResult, IResult
-{
- private string _fileName;
- private IFileInfo? _fileInfo;
-
- ///
- /// Creates a new instance with the provided
- /// and the provided .
- ///
- /// The path to the file. The path must be relative/virtual.
- /// The Content-Type header of the response.
- public VirtualFileResult(string fileName, string? contentType)
- : base(contentType)
- {
- FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
- }
-
- ///
- /// Gets or sets the path to the file that will be sent back as the response.
- ///
- public string FileName
- {
- get => _fileName;
- [MemberNotNull(nameof(_fileName))]
- set => _fileName = value ?? throw new ArgumentNullException(nameof(value));
- }
-
- protected override Task ExecuteCoreAsync(HttpContext httpContext, RangeItemHeaderValue? range, long rangeLength)
- {
- var response = httpContext.Response;
- var offset = 0L;
- var count = (long?)null;
- if (range != null)
- {
- offset = range.From ?? 0L;
- count = rangeLength;
- }
-
- return response.SendFileAsync(
- _fileInfo!,
- offset,
- count);
- }
-
- protected override ILogger GetLogger(HttpContext httpContext)
- {
- return httpContext.RequestServices.GetRequiredService>();
- }
-
- ///
- public override Task ExecuteAsync(HttpContext httpContext)
- {
- var hostingEnvironment = httpContext.RequestServices.GetRequiredService();
-
- var fileInfo = GetFileInformation(hostingEnvironment.WebRootFileProvider);
- if (!fileInfo.Exists)
- {
- throw new FileNotFoundException($"Could not find file: {FileName}.", FileName);
- }
-
- _fileInfo = fileInfo;
- LastModified = LastModified ?? fileInfo.LastModified;
- FileLength = fileInfo.Length;
-
- return base.ExecuteAsync(httpContext);
- }
-
- internal IFileInfo GetFileInformation(IFileProvider fileProvider)
- {
- var normalizedPath = FileName;
- if (normalizedPath.StartsWith("~", StringComparison.Ordinal))
- {
- normalizedPath = normalizedPath.Substring(1);
- }
-
- var fileInfo = fileProvider.GetFileInfo(normalizedPath);
- return fileInfo;
- }
-}
diff --git a/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
index 5b186a474c6c..ae0b8f754085 100644
--- a/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
+++ b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
@@ -10,6 +10,24 @@ namespace Microsoft.AspNetCore.Http.Result;
public class AcceptedAtRouteResultTests
{
+ [Fact]
+ public void AcceptedAtRouteResult_ProblemDetails_SetsStatusCodeAndValue()
+ {
+ // Arrange & Act
+ var routeValues = new RouteValueDictionary(new Dictionary()
+ {
+ { "test", "case" },
+ { "sample", "route" }
+ });
+ var obj = new HttpValidationProblemDetails();
+ var result = new AcceptedAtRouteHttpResult(routeValues, obj);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
+ Assert.Equal(StatusCodes.Status202Accepted, obj.Status);
+ Assert.Equal(obj, result.Value);
+ }
+
[Fact]
public async Task ExecuteResultAsync_FormatsData()
{
@@ -27,7 +45,7 @@ public async Task ExecuteResultAsync_FormatsData()
});
// Act
- var result = new AcceptedAtRouteResult(
+ var result = new AcceptedAtRouteHttpResult(
routeName: "sample",
routeValues: routeValues,
value: "Hello world");
@@ -69,7 +87,7 @@ public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader(object valu
var httpContext = GetHttpContext(linkGenerator);
// Act
- var result = new AcceptedAtRouteResult(routeValues: values, value: null);
+ var result = new AcceptedAtRouteHttpResult(routeValues: values, value: null);
await result.ExecuteAsync(httpContext);
// Assert
@@ -85,7 +103,7 @@ public async Task ExecuteResultAsync_ThrowsIfRouteUrlIsNull()
var httpContext = GetHttpContext(linkGenerator);
// Act
- var result = new AcceptedAtRouteResult(
+ var result = new AcceptedAtRouteHttpResult(
routeName: null,
routeValues: new Dictionary(),
value: null);
diff --git a/src/Http/Http.Results/test/AcceptedResultTests.cs b/src/Http/Http.Results/test/AcceptedResultTests.cs
index 1d5f98931186..9cf5eb6bbfb8 100644
--- a/src/Http/Http.Results/test/AcceptedResultTests.cs
+++ b/src/Http/Http.Results/test/AcceptedResultTests.cs
@@ -16,7 +16,7 @@ public async Task ExecuteResultAsync_FormatsData()
var stream = new MemoryStream();
httpContext.Response.Body = stream;
// Act
- var result = new AcceptedResult("my-location", value: "Hello world");
+ var result = new AcceptedHttpResult("my-location", value: "Hello world");
await result.ExecuteAsync(httpContext);
// Assert
@@ -32,7 +32,7 @@ public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader()
var httpContext = GetHttpContext();
// Act
- var result = new AcceptedResult(expectedUrl, value: "some-value");
+ var result = new AcceptedHttpResult(expectedUrl, value: "some-value");
await result.ExecuteAsync(httpContext);
// Assert
@@ -40,6 +40,20 @@ public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader()
Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
}
+ [Fact]
+ public void AcceptedResult_ProblemDetails_SetsStatusCodeAndValue()
+ {
+ // Arrange & Act
+ var expectedUrl = "testAction";
+ var obj = new HttpValidationProblemDetails();
+ var result = new AcceptedHttpResult(expectedUrl, obj);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
+ Assert.Equal(StatusCodes.Status202Accepted, obj.Status);
+ Assert.Equal(obj, result.Value);
+ }
+
private static HttpContext GetHttpContext()
{
var httpContext = new DefaultHttpContext();
diff --git a/src/Http/Http.Results/test/BadRequestObjectResultTests.cs b/src/Http/Http.Results/test/BadRequestObjectResultTests.cs
index 2cddb2616671..c72a82752af1 100644
--- a/src/Http/Http.Results/test/BadRequestObjectResultTests.cs
+++ b/src/Http/Http.Results/test/BadRequestObjectResultTests.cs
@@ -3,6 +3,11 @@
namespace Microsoft.AspNetCore.Http.Result;
+using System.Text;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
public class BadRequestObjectResultTests
{
[Fact]
@@ -10,10 +15,69 @@ public void BadRequestObjectResult_SetsStatusCodeAndValue()
{
// Arrange & Act
var obj = new object();
- var badRequestObjectResult = new BadRequestObjectResult(obj);
+ var badRequestObjectResult = new BadRequestObjectHttpResult(obj);
// Assert
Assert.Equal(StatusCodes.Status400BadRequest, badRequestObjectResult.StatusCode);
Assert.Equal(obj, badRequestObjectResult.Value);
}
+
+ [Fact]
+ public void BadRequestObjectResult_ProblemDetails_SetsStatusCodeAndValue()
+ {
+ // Arrange & Act
+ var obj = new HttpValidationProblemDetails();
+ var result = new BadRequestObjectHttpResult(obj);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode);
+ Assert.Equal(StatusCodes.Status400BadRequest, obj.Status);
+ Assert.Equal(obj, result.Value);
+ }
+
+ [Fact]
+ public async Task BadRequestObjectResult_ExecuteAsync_SetsStatusCode()
+ {
+ // Arrange
+ var result = new BadRequestObjectHttpResult("Hello");
+ var httpContext = new DefaultHttpContext()
+ {
+ RequestServices = CreateServices(),
+ };
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode);
+ }
+
+ [Fact]
+ public async Task BadRequestObjectResult_ExecuteResultAsync_FormatsData()
+ {
+ // Arrange
+ var result = new BadRequestObjectHttpResult("Hello");
+ var stream = new MemoryStream();
+ var httpContext = new DefaultHttpContext()
+ {
+ RequestServices = CreateServices(),
+ Response =
+ {
+ Body = stream,
+ },
+ };
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal("\"Hello\"", Encoding.UTF8.GetString(stream.ToArray()));
+ }
+
+ private static IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton();
+ return services.BuildServiceProvider();
+ }
}
diff --git a/src/Http/Http.Results/test/ChallengeResultTest.cs b/src/Http/Http.Results/test/ChallengeResultTest.cs
index a5a869adb898..95817e6ba449 100644
--- a/src/Http/Http.Results/test/ChallengeResultTest.cs
+++ b/src/Http/Http.Results/test/ChallengeResultTest.cs
@@ -15,7 +15,7 @@ public class ChallengeResultTest
public async Task ChallengeResult_ExecuteAsync()
{
// Arrange
- var result = new ChallengeResult("", null);
+ var result = new ChallengeHttpResult("", null);
var auth = new Mock();
var httpContext = GetHttpContext(auth);
@@ -30,7 +30,7 @@ public async Task ChallengeResult_ExecuteAsync()
public async Task ChallengeResult_ExecuteAsync_NoSchemes()
{
// Arrange
- var result = new ChallengeResult(new string[] { }, null);
+ var result = new ChallengeHttpResult(new string[] { }, null);
var auth = new Mock();
var httpContext = GetHttpContext(auth);
@@ -54,6 +54,7 @@ private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+ services.AddSingleton(NullLoggerFactory.Instance);
return services;
}
}
diff --git a/src/Http/Http.Results/test/ConflictObjectResultTest.cs b/src/Http/Http.Results/test/ConflictObjectResultTest.cs
index 4310cb3a3575..091d03bc605f 100644
--- a/src/Http/Http.Results/test/ConflictObjectResultTest.cs
+++ b/src/Http/Http.Results/test/ConflictObjectResultTest.cs
@@ -3,6 +3,12 @@
namespace Microsoft.AspNetCore.Http.Result;
+using System.Text;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
public class ConflictObjectResultTest
{
[Fact]
@@ -10,10 +16,69 @@ public void ConflictObjectResult_SetsStatusCodeAndValue()
{
// Arrange & Act
var obj = new object();
- var conflictObjectResult = new ConflictObjectResult(obj);
+ var conflictObjectResult = new ConflictObjectHttpResult(obj);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status409Conflict, conflictObjectResult.StatusCode);
+ Assert.Equal(obj, conflictObjectResult.Value);
+ }
+
+ [Fact]
+ public void ConflictObjectResult_ProblemDetails_SetsStatusCodeAndValue()
+ {
+ // Arrange & Act
+ var obj = new ProblemDetails();
+ var conflictObjectResult = new ConflictObjectHttpResult(obj);
// Assert
Assert.Equal(StatusCodes.Status409Conflict, conflictObjectResult.StatusCode);
+ Assert.Equal(StatusCodes.Status409Conflict, obj.Status);
Assert.Equal(obj, conflictObjectResult.Value);
}
+
+ [Fact]
+ public async Task ConflictObjectResult_ExecuteAsync_SetsStatusCode()
+ {
+ // Arrange
+ var result = new ConflictObjectHttpResult("Hello");
+ var httpContext = new DefaultHttpContext()
+ {
+ RequestServices = CreateServices(),
+ };
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status409Conflict, httpContext.Response.StatusCode);
+ }
+
+ [Fact]
+ public async Task ConflictObjectResult_ExecuteResultAsync_FormatsData()
+ {
+ // Arrange
+ var result = new ConflictObjectHttpResult("Hello");
+ var stream = new MemoryStream();
+ var httpContext = new DefaultHttpContext()
+ {
+ RequestServices = CreateServices(),
+ Response =
+ {
+ Body = stream,
+ },
+ };
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal("\"Hello\"", Encoding.UTF8.GetString(stream.ToArray()));
+ }
+
+ private static IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton();
+ return services.BuildServiceProvider();
+ }
}
diff --git a/src/Http/Http.Results/test/ContentResultTest.cs b/src/Http/Http.Results/test/ContentResultTest.cs
index f373151b2b83..f45ea77e6226 100644
--- a/src/Http/Http.Results/test/ContentResultTest.cs
+++ b/src/Http/Http.Results/test/ContentResultTest.cs
@@ -15,14 +15,12 @@ public class ContentResultTest
public async Task ContentResult_ExecuteAsync_Response_NullContent_SetsContentTypeAndEncoding()
{
// Arrange
- var contentResult = new ContentResult
+ var contentType = new MediaTypeHeaderValue("text/plain")
{
- Content = null,
- ContentType = new MediaTypeHeaderValue("text/plain")
- {
- Encoding = Encoding.Unicode
- }.ToString()
- };
+ Encoding = Encoding.Unicode
+ }.ToString();
+
+ var contentResult = new ContentHttpResult(null, contentType);
var httpContext = GetHttpContext();
// Act
@@ -109,11 +107,7 @@ public async Task ContentResult_ExecuteAsync_SetContentTypeAndEncoding_OnRespons
byte[] expectedContentData)
{
// Arrange
- var contentResult = new ContentResult
- {
- Content = content,
- ContentType = contentType?.ToString()
- };
+ var contentResult = new ContentHttpResult(content, contentType?.ToString());
var httpContext = GetHttpContext();
var memoryStream = new MemoryStream();
httpContext.Response.Body = memoryStream;
@@ -133,6 +127,7 @@ private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+ services.AddSingleton(NullLoggerFactory.Instance);
return services;
}
diff --git a/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs b/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
index e1235d763447..5fd0575a3c35 100644
--- a/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
+++ b/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
@@ -11,6 +11,23 @@ namespace Microsoft.AspNetCore.Http.Result;
public partial class CreatedAtRouteResultTests
{
+ [Fact]
+ public void CreatedAtRouteResult_ProblemDetails_SetsStatusCodeAndValue()
+ {
+ // Arrange & Act
+ var routeValues = new RouteValueDictionary(new Dictionary()
+ {
+ { "test", "case" },
+ { "sample", "route" }
+ });
+ var obj = new HttpValidationProblemDetails();
+ var result = new CreatedAtRouteHttpResult(routeValues, obj);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status201Created, result.StatusCode);
+ Assert.Equal(StatusCodes.Status201Created, obj.Status);
+ Assert.Equal(obj, result.Value);
+ }
public static IEnumerable