diff --git a/AspNetCore.sln b/AspNetCore.sln
index f1e9736e420b..9b085a67eedc 100644
--- a/AspNetCore.sln
+++ b/AspNetCore.sln
@@ -1626,6 +1626,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Compon
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleWebSiteWithWebApplicationBuilderException", "src\Mvc\test\WebSites\SimpleWebSiteWithWebApplicationBuilderException\SimpleWebSiteWithWebApplicationBuilderException.csproj", "{5C641396-7E92-4F5C-A5A1-B4CDF480539B}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Results", "Http.Results", "{323C3EB6-1D15-4B3D-918D-699D7F64DED9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Results", "src\Http\Http.Results\src\Microsoft.AspNetCore.Http.Results.csproj", "{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Results.Tests", "src\Http\Http.Results\test\Microsoft.AspNetCore.Http.Results.Tests.csproj", "{F599EAA6-399F-4A91-9B1F-D311305B43D9}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Logging.W3C.Sample", "src\Middleware\HttpLogging\samples\Logging.W3C.Sample\Logging.W3C.Sample.csproj", "{17459B97-1AA3-4154-83D3-C6BDC9FA3F85}"
EndProject
Global
@@ -7747,6 +7753,30 @@ Global
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x64.Build.0 = Release|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x86.ActiveCfg = Release|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x86.Build.0 = Release|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|x64.Build.0 = Debug|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|x86.Build.0 = Debug|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|x64.ActiveCfg = Release|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|x64.Build.0 = Release|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|x86.ActiveCfg = Release|Any CPU
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|x86.Build.0 = Release|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|x64.Build.0 = Debug|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|x86.Build.0 = Debug|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|x64.ActiveCfg = Release|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|x64.Build.0 = Release|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|x86.ActiveCfg = Release|Any CPU
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|x86.Build.0 = Release|Any CPU
{17459B97-1AA3-4154-83D3-C6BDC9FA3F85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17459B97-1AA3-4154-83D3-C6BDC9FA3F85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17459B97-1AA3-4154-83D3-C6BDC9FA3F85}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -8564,6 +8594,9 @@ Global
{558C46DE-DE16-41D5-8DB7-D6D748E32977} = {3EC71A0E-6515-4A5A-B759-F0BCF1BCFC56}
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD} = {44963D50-8B58-44E6-918D-788BCB406695}
{5C641396-7E92-4F5C-A5A1-B4CDF480539B} = {088C37A5-30D2-40FB-B031-D163CFBED006}
+ {323C3EB6-1D15-4B3D-918D-699D7F64DED9} = {627BE8B3-59E6-4F1D-8C9C-76B804D41724}
+ {092EA9F6-84D4-41EF-A618-BDA50A0E10A8} = {323C3EB6-1D15-4B3D-918D-699D7F64DED9}
+ {F599EAA6-399F-4A91-9B1F-D311305B43D9} = {323C3EB6-1D15-4B3D-918D-699D7F64DED9}
{17459B97-1AA3-4154-83D3-C6BDC9FA3F85} = {022B4B80-E813-4256-8034-11A68146F4EF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index fd90ae09e737..1035737d2d83 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -29,6 +29,7 @@
+
diff --git a/eng/SharedFramework.Local.props b/eng/SharedFramework.Local.props
index f230f2e82817..34a5b503e568 100644
--- a/eng/SharedFramework.Local.props
+++ b/eng/SharedFramework.Local.props
@@ -48,6 +48,7 @@
+
diff --git a/src/Framework/test/TestData.cs b/src/Framework/test/TestData.cs
index dd11b6d25171..ea48ec61ad4b 100644
--- a/src/Framework/test/TestData.cs
+++ b/src/Framework/test/TestData.cs
@@ -55,6 +55,7 @@ static TestData()
"Microsoft.AspNetCore.Http.Connections.Common",
"Microsoft.AspNetCore.Http.Extensions",
"Microsoft.AspNetCore.Http.Features",
+ "Microsoft.AspNetCore.Http.Results",
"Microsoft.AspNetCore.HttpLogging",
"Microsoft.AspNetCore.HttpOverrides",
"Microsoft.AspNetCore.HttpsPolicy",
@@ -188,6 +189,7 @@ static TestData()
{ "Microsoft.AspNetCore.Http.Connections.Common", "6.0.0.0" },
{ "Microsoft.AspNetCore.Http.Extensions", "6.0.0.0" },
{ "Microsoft.AspNetCore.Http.Features", "6.0.0.0" },
+ { "Microsoft.AspNetCore.Http.Results", "6.0.0.0" },
{ "Microsoft.AspNetCore.HttpLogging", "6.0.0.0" },
{ "Microsoft.AspNetCore.HttpOverrides", "6.0.0.0" },
{ "Microsoft.AspNetCore.HttpsPolicy", "6.0.0.0" },
diff --git a/src/Http/Http.Results/src/AcceptedAtRouteResult.cs b/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
new file mode 100644
index 000000000000..8159803bccce
--- /dev/null
+++ b/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
@@ -0,0 +1,68 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal sealed class AcceptedAtRouteResult : 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 AcceptedAtRouteResult(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 AcceptedAtRouteResult(
+ string? routeName,
+ object? routeValues,
+ object? value)
+ : base(value, StatusCodes.Status202Accepted)
+ {
+ RouteName = routeName;
+ RouteValues = new RouteValueDictionary(routeValues);
+ }
+
+ ///
+ /// 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; }
+
+ ///
+ protected override void OnFormatting(HttpContext context)
+ {
+ var linkGenerator = context.RequestServices.GetRequiredService();
+ var url = linkGenerator.GetUriByAddress(
+ 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/AcceptedResult.cs b/src/Http/Http.Results/src/AcceptedResult.cs
new file mode 100644
index 000000000000..d7e1da54e514
--- /dev/null
+++ b/src/Http/Http.Results/src/AcceptedResult.cs
@@ -0,0 +1,74 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+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 OnFormatting(HttpContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (!string.IsNullOrEmpty(Location))
+ {
+ context.Response.Headers.Location = Location;
+ }
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/BadRequestObjectResult.cs b/src/Http/Http.Results/src/BadRequestObjectResult.cs
new file mode 100644
index 000000000000..2599ec549c6c
--- /dev/null
+++ b/src/Http/Http.Results/src/BadRequestObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+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/BadRequestResult.cs b/src/Http/Http.Results/src/BadRequestResult.cs
new file mode 100644
index 000000000000..eff601caa1f0
--- /dev/null
+++ b/src/Http/Http.Results/src/BadRequestResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal sealed class BadRequestResult : StatusCodeResult
+ {
+ public BadRequestResult() : base(StatusCodes.Status400BadRequest)
+ {
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/ChallengeResult.cs b/src/Http/Http.Results/src/ChallengeResult.cs
new file mode 100644
index 000000000000..e3614755e937
--- /dev/null
+++ b/src/Http/Http.Results/src/ChallengeResult.cs
@@ -0,0 +1,120 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ ///
+ /// An that on execution invokes .
+ ///
+ internal sealed partial class ChallengeResult : IResult
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ public ChallengeResult()
+ : this(Array.Empty())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified authentication scheme.
+ ///
+ /// The authentication scheme to challenge.
+ public ChallengeResult(string authenticationScheme)
+ : this(new[] { authenticationScheme })
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified authentication schemes.
+ ///
+ /// The authentication schemes to challenge.
+ public ChallengeResult(IList authenticationSchemes)
+ : this(authenticationSchemes, properties: null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified .
+ ///
+ /// used to perform the authentication
+ /// challenge.
+ public ChallengeResult(AuthenticationProperties? properties)
+ : this(Array.Empty(), properties)
+ {
+ }
+
+ ///
+ /// 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)
+ : this(new[] { authenticationScheme }, properties)
+ {
+ }
+
+ ///
+ /// 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)
+ {
+ AuthenticationSchemes = authenticationSchemes;
+ Properties = properties;
+ }
+
+ public IList AuthenticationSchemes { get; init; } = Array.Empty();
+
+ public AuthenticationProperties? Properties { get; init; }
+
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService>();
+
+ Log.ChallengeResultExecuting(logger, AuthenticationSchemes);
+
+ if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
+ {
+ foreach (var scheme in AuthenticationSchemes)
+ {
+ await httpContext.ChallengeAsync(scheme, Properties);
+ }
+ }
+ else
+ {
+ await httpContext.ChallengeAsync(Properties);
+ }
+ }
+
+ private static partial class Log
+ {
+ public static void ChallengeResultExecuting(ILogger logger, IList authenticationSchemes)
+ {
+ if (logger.IsEnabled(LogLevel.Information))
+ {
+ ChallengeResultExecuting(logger, authenticationSchemes.ToArray());
+ }
+ }
+
+ [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
+ private static partial void ChallengeResultExecuting(ILogger logger, string[] schemes);
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/ConflictObjectResult.cs b/src/Http/Http.Results/src/ConflictObjectResult.cs
new file mode 100644
index 000000000000..7e5d8faed423
--- /dev/null
+++ b/src/Http/Http.Results/src/ConflictObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+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/ConflictResult.cs b/src/Http/Http.Results/src/ConflictResult.cs
new file mode 100644
index 000000000000..2baf7d079fb0
--- /dev/null
+++ b/src/Http/Http.Results/src/ConflictResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal class ConflictResult : StatusCodeResult
+ {
+ public ConflictResult() : base(StatusCodes.Status409Conflict)
+ {
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/ContentResult.cs b/src/Http/Http.Results/src/ContentResult.cs
new file mode 100644
index 000000000000..12f1ceb24f36
--- /dev/null
+++ b/src/Http/Http.Results/src/ContentResult.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Text;
+using System.Threading.Tasks;
+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/CreatedAtRouteResult.cs b/src/Http/Http.Results/src/CreatedAtRouteResult.cs
new file mode 100644
index 000000000000..8fe982930663
--- /dev/null
+++ b/src/Http/Http.Results/src/CreatedAtRouteResult.cs
@@ -0,0 +1,68 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using 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 OnFormatting(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/CreatedResult.cs b/src/Http/Http.Results/src/CreatedResult.cs
new file mode 100644
index 000000000000..23be3d084722
--- /dev/null
+++ b/src/Http/Http.Results/src/CreatedResult.cs
@@ -0,0 +1,57 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+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 OnFormatting(HttpContext context)
+ {
+ context.Response.Headers.Location = Location;
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/FileContentResult.cs b/src/Http/Http.Results/src/FileContentResult.cs
new file mode 100644
index 000000000000..cc51ed505112
--- /dev/null
+++ b/src/Http/Http.Results/src/FileContentResult.cs
@@ -0,0 +1,74 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal sealed partial class FileContentResult : FileResult, IResult
+ {
+ ///
+ /// 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(byte[] fileContents, string contentType)
+ : base(contentType)
+ {
+ FileContents = fileContents;
+ }
+
+ ///
+ /// Gets or sets the file contents.
+ ///
+ public byte[] FileContents { get; init; }
+
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService>();
+ 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,
+ FileContents.Length,
+ EnableRangeProcessing,
+ LastModified,
+ EntityTag,
+ logger);
+
+ if (!serveBody)
+ {
+ return Task.CompletedTask;
+ }
+
+ if (range != null && rangeLength == 0)
+ {
+ return Task.CompletedTask;
+ }
+
+ if (range != null)
+ {
+ FileResultHelper.Log.WritingRangeToBody(logger);
+ }
+
+ var fileContentStream = new MemoryStream(FileContents);
+ return FileResultHelper.WriteFileAsync(httpContext, fileContentStream, range, rangeLength);
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/FileResult.cs b/src/Http/Http.Results/src/FileResult.cs
new file mode 100644
index 000000000000..1e0afe40a1f7
--- /dev/null
+++ b/src/Http/Http.Results/src/FileResult.cs
@@ -0,0 +1,93 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal abstract partial class FileResult
+ {
+ private string? _fileDownloadName;
+
+ ///
+ /// Creates a new instance with
+ /// the provided .
+ ///
+ /// The Content-Type header of the response.
+ protected FileResult(string contentType)
+ {
+ if (contentType == null)
+ {
+ throw new ArgumentNullException(nameof(contentType));
+ }
+
+ ContentType = contentType;
+ }
+
+ ///
+ /// 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; init; }
+
+ ///
+ /// 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; }
+
+ protected static partial class Log
+ {
+ public static void ExecutingFileResult(ILogger logger, FileResult fileResult)
+ {
+ if (logger.IsEnabled(LogLevel.Information))
+ {
+ var fileResultType = fileResult.GetType().Name;
+ ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
+ }
+ }
+
+ public static void ExecutingFileResult(ILogger logger, FileResult 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/FileStreamResult.cs b/src/Http/Http.Results/src/FileStreamResult.cs
new file mode 100644
index 000000000000..7b7862047ed1
--- /dev/null
+++ b/src/Http/Http.Results/src/FileStreamResult.cs
@@ -0,0 +1,110 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+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)
+ : this(fileStream, MediaTypeHeaderValue.Parse(contentType))
+ {
+ if (fileStream == null)
+ {
+ throw new ArgumentNullException(nameof(fileStream));
+ }
+
+ FileStream = fileStream;
+ }
+
+ ///
+ /// 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, MediaTypeHeaderValue contentType)
+ : base(contentType.ToString())
+ {
+ if (fileStream == null)
+ {
+ throw new ArgumentNullException(nameof(fileStream));
+ }
+
+ FileStream = fileStream;
+ }
+
+ ///
+ /// Gets or sets the stream with the file that will be sent back as the response.
+ ///
+ public Stream FileStream { get; }
+
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService>();
+ using (FileStream)
+ {
+ Log.ExecutingFileResult(logger, this);
+
+ long? fileLength = null;
+ if (FileStream.CanSeek)
+ {
+ fileLength = FileStream.Length;
+ }
+
+ var fileResultInfo = new FileResultInfo
+ {
+ ContentType = ContentType,
+ EnableRangeProcessing = EnableRangeProcessing,
+ EntityTag = EntityTag,
+ FileDownloadName = FileDownloadName,
+ };
+
+ 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 FileResultHelper.WriteFileAsync(httpContext, FileStream, range, rangeLength);
+ }
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/ForbidResult.cs b/src/Http/Http.Results/src/ForbidResult.cs
new file mode 100644
index 000000000000..9a22b6240d3b
--- /dev/null
+++ b/src/Http/Http.Results/src/ForbidResult.cs
@@ -0,0 +1,125 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal sealed partial class ForbidResult : IResult
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ public ForbidResult()
+ : this(Array.Empty())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified authentication scheme.
+ ///
+ /// The authentication scheme to challenge.
+ public ForbidResult(string authenticationScheme)
+ : this(new[] { authenticationScheme })
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified authentication schemes.
+ ///
+ /// The authentication schemes to challenge.
+ public ForbidResult(IList authenticationSchemes)
+ : this(authenticationSchemes, properties: null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified .
+ ///
+ /// used to perform the authentication
+ /// challenge.
+ public ForbidResult(AuthenticationProperties? properties)
+ : this(Array.Empty(), properties)
+ {
+ }
+
+ ///
+ /// 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)
+ : this(new[] { authenticationScheme }, properties)
+ {
+ }
+
+ ///
+ /// 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)
+ {
+ AuthenticationSchemes = authenticationSchemes;
+ Properties = properties;
+ }
+
+ ///
+ /// Gets or sets the authentication schemes that are challenged.
+ ///
+ public IList AuthenticationSchemes { get; init; }
+
+ ///
+ /// Gets or sets the used to perform the authentication challenge.
+ ///
+ public AuthenticationProperties? Properties { get; init; }
+
+ ///
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService>();
+
+ Log.ForbidResultExecuting(logger, AuthenticationSchemes);
+
+ if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
+ {
+ for (var i = 0; i < AuthenticationSchemes.Count; i++)
+ {
+ await httpContext.ForbidAsync(AuthenticationSchemes[i], Properties);
+ }
+ }
+ else
+ {
+ await httpContext.ForbidAsync(Properties);
+ }
+ }
+
+ private static partial class Log
+ {
+ public static void ForbidResultExecuting(ILogger logger, IList authenticationSchemes)
+ {
+ if (logger.IsEnabled(LogLevel.Information))
+ {
+ ForbidResultExecuting(logger, authenticationSchemes.ToArray());
+ }
+ }
+
+ [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
+ private static partial void ForbidResultExecuting(ILogger logger, string[] schemes);
+ }
+
+ }
+}
diff --git a/src/Http/Http.Results/src/JsonResult.cs b/src/Http/Http.Results/src/JsonResult.cs
new file mode 100644
index 000000000000..597048c2b627
--- /dev/null
+++ b/src/Http/Http.Results/src/JsonResult.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Text.Json;
+using System.Threading.Tasks;
+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
new file mode 100644
index 000000000000..51aa27568b2e
--- /dev/null
+++ b/src/Http/Http.Results/src/LocalRedirectResult.cs
@@ -0,0 +1,111 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+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/Microsoft.AspNetCore.Http.Results.csproj b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj
new file mode 100644
index 000000000000..54b85a37bbfb
--- /dev/null
+++ b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj
@@ -0,0 +1,31 @@
+
+
+
+ ASP.NET Core types that implement Microsoft.AspNetCore.Http.IResult.
+ $(DefaultNetCoreTargetFramework)
+ true
+ true
+ aspnetcore
+ false
+ enable
+ Microsoft.AspNetCore.Http.Result
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Http/Http.Results/src/NoContentResult.cs b/src/Http/Http.Results/src/NoContentResult.cs
new file mode 100644
index 000000000000..120ce546ef09
--- /dev/null
+++ b/src/Http/Http.Results/src/NoContentResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal class NoContentResult : StatusCodeResult
+ {
+ public NoContentResult() : base(StatusCodes.Status204NoContent)
+ {
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/NotFoundObjectResult.cs b/src/Http/Http.Results/src/NotFoundObjectResult.cs
new file mode 100644
index 000000000000..fbda773257c0
--- /dev/null
+++ b/src/Http/Http.Results/src/NotFoundObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+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/NotFoundResult.cs b/src/Http/Http.Results/src/NotFoundResult.cs
new file mode 100644
index 000000000000..b0d7902eb8e4
--- /dev/null
+++ b/src/Http/Http.Results/src/NotFoundResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal sealed class NotFoundResult : StatusCodeResult
+ {
+ public NotFoundResult() : base(StatusCodes.Status404NotFound)
+ {
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/ObjectResult.cs b/src/Http/Http.Results/src/ObjectResult.cs
new file mode 100644
index 000000000000..e01a0f6ffd48
--- /dev/null
+++ b/src/Http/Http.Results/src/ObjectResult.cs
@@ -0,0 +1,62 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+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, int statusCode)
+ {
+ Value = value;
+ StatusCode = statusCode;
+ }
+
+ ///
+ /// The object result.
+ ///
+ public object? Value { get; }
+
+ ///
+ /// Gets or sets the HTTP status code.
+ ///
+ public int StatusCode { get; }
+
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var loggerFactory = httpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger(GetType());
+ Log.ObjectResultExecuting(logger, Value);
+
+ httpContext.Response.StatusCode = StatusCode;
+
+ OnFormatting(httpContext);
+ return httpContext.Response.WriteAsJsonAsync(Value);
+ }
+
+ protected virtual void OnFormatting(HttpContext httpContext)
+ {
+ }
+
+ private static partial class Log
+ {
+ public static void ObjectResultExecuting(ILogger logger, object? value)
+ {
+ if (logger.IsEnabled(LogLevel.Information))
+ {
+ var valueType = value is null ? "null" : value.GetType().FullName!;
+ ObjectResultExecuting(logger, valueType);
+ }
+ }
+
+ [LoggerMessage(1, LogLevel.Information, "Writing value of type '{Type}'.", EventName = "ObjectResultExecuting", SkipEnabledCheck = true)]
+ public static partial void ObjectResultExecuting(ILogger logger, string type);
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/OkObjectResult.cs b/src/Http/Http.Results/src/OkObjectResult.cs
new file mode 100644
index 000000000000..f7ec6e9603bc
--- /dev/null
+++ b/src/Http/Http.Results/src/OkObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+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/OkResult.cs b/src/Http/Http.Results/src/OkResult.cs
new file mode 100644
index 000000000000..9cf0430fa990
--- /dev/null
+++ b/src/Http/Http.Results/src/OkResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal class OkResult : StatusCodeResult
+ {
+ public OkResult() : base(StatusCodes.Status200OK)
+ {
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/PhysicalFileResult.cs b/src/Http/Http.Results/src/PhysicalFileResult.cs
new file mode 100644
index 000000000000..9a53b91e9466
--- /dev/null
+++ b/src/Http/Http.Results/src/PhysicalFileResult.cs
@@ -0,0 +1,123 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+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);
+
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var fileInfo = GetFileInfoWrapper(FileName);
+ if (!fileInfo.Exists)
+ {
+ throw new FileNotFoundException($"Could not find file: {FileName}", FileName);
+ }
+
+ var logger = httpContext.RequestServices.GetRequiredService>();
+
+ Log.ExecutingFileResult(logger, this, FileName);
+
+ var lastModified = LastModified ?? fileInfo.LastWriteTimeUtc;
+ var fileResultInfo = new FileResultInfo
+ {
+ ContentType = ContentType,
+ EnableRangeProcessing = EnableRangeProcessing,
+ EntityTag = EntityTag,
+ FileDownloadName = FileDownloadName,
+ LastModified = lastModified,
+ };
+
+ var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+ httpContext,
+ fileResultInfo,
+ fileInfo.Length,
+ EnableRangeProcessing,
+ lastModified,
+ EntityTag,
+ logger);
+
+ if (!serveBody)
+ {
+ return Task.CompletedTask;
+ }
+
+ if (range != null && rangeLength == 0)
+ {
+ return Task.CompletedTask;
+ }
+
+ var response = httpContext.Response;
+ if (!Path.IsPathRooted(FileName))
+ {
+ throw new NotSupportedException($"Path '{FileName}' was not rooted.");
+ }
+
+ if (range != null)
+ {
+ FileResultHelper.Log.WritingRangeToBody(logger);
+ }
+
+ 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);
+ 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/PublicAPI.Shipped.txt b/src/Http/Http.Results/src/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/Http/Http.Results/src/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..dffa3a246402
--- /dev/null
+++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt
@@ -0,0 +1,104 @@
+#nullable enable
+Microsoft.AspNetCore.Http.Results
+static Microsoft.AspNetCore.Http.Results.Accepted() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(System.Uri! uri) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(System.Uri! uri, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(string? uri) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(string? uri, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(object? routeValues, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, object? routeValues, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.BadRequest() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.BadRequest(object? error) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Challenge() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Challenge(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Challenge(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties, params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Challenge(params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Conflict() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Conflict(object? error) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Content(string! content) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Content(string! content, Microsoft.Net.Http.Headers.MediaTypeHeaderValue? contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Content(string! content, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Content(string! content, string! contentType, System.Text.Encoding! contentEncoding) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Created(System.Uri! uri, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Created(string! uri, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(object? routeValues, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName, object? routeValues, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, string? fileDownloadName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, string? fileDownloadName, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, string? fileDownloadName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, string? fileDownloadName, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, string? fileDownloadName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, string? fileDownloadName, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Forbid() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Forbid(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Forbid(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties, params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Forbid(params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Json(object? data, System.Text.Json.JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.LocalRedirect(string! localUrl) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.LocalRedirectPermanent(string! localUrl) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.LocalRedirectPermanentPreserveMethod(string! localUrl) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.LocalRedirectPreserveMethod(string! localUrl) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.NoContent() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.NotFound() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.NotFound(object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Ok() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Ok(object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, string? fileDownloadName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, string? fileDownloadName, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Redirect(string! url) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectPermanent(string! url) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectPermanentPreserveMethod(string! url) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectPreserveMethod(string! url) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, object? routeValues, string? fragment) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, string? fragment) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(string? routeName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(string? routeName, object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(string? routeName, object? routeValues, string? fragment) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(string? routeName, string? fragment) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanentPreserveMethod(string? routeName = null, object? routeValues = null, string? fragment = null) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePreserveMethod(string? routeName = null, object? routeValues = null, string? fragment = null) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal, Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal, Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties, string! authenticationScheme) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal, string! authenticationScheme) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignOut() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignOut(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignOut(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties, params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignOut(params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.StatusCode(int statusCode) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Unauthorized() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.UnprocessableEntity() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.UnprocessableEntity(object? error) -> Microsoft.AspNetCore.Http.IResult!
diff --git a/src/Http/Http.Results/src/RedirectResult.cs b/src/Http/Http.Results/src/RedirectResult.cs
new file mode 100644
index 000000000000..8624683290b6
--- /dev/null
+++ b/src/Http/Http.Results/src/RedirectResult.cs
@@ -0,0 +1,107 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+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 local URL to redirect to.
+ public RedirectResult(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).
+ public RedirectResult(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.
+ 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/RedirectToRouteResult.cs
new file mode 100644
index 000000000000..11711b1d8cb0
--- /dev/null
+++ b/src/Http/Http.Results/src/RedirectToRouteResult.cs
@@ -0,0 +1,194 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Routing;
+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.
+ /// Targets a registered route.
+ ///
+ internal sealed partial class RedirectToRouteResult : IResult
+ {
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The parameters for the route.
+ public RedirectToRouteResult(object? routeValues)
+ : this(routeName: null, routeValues: routeValues)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the route.
+ /// The parameters for the route.
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues)
+ : this(routeName, routeValues, permanent: false)
+ {
+ }
+
+ ///
+ /// 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(
+ string? routeName,
+ object? routeValues,
+ bool permanent)
+ : this(routeName, routeValues, permanent, fragment: null)
+ {
+ }
+
+ ///
+ /// 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(
+ string? routeName,
+ object? routeValues,
+ bool permanent,
+ bool preserveMethod)
+ : this(routeName, routeValues, permanent, preserveMethod, fragment: null)
+ {
+ }
+
+ ///
+ /// 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(
+ string? routeName,
+ object? routeValues,
+ string? fragment)
+ : this(routeName, routeValues, permanent: false, fragment: fragment)
+ {
+ }
+
+ ///
+ /// 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).
+ /// The fragment to add to the URL.
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues,
+ bool permanent,
+ string? fragment)
+ : this(routeName, routeValues, permanent, preserveMethod: false, fragment: fragment)
+ {
+ }
+
+ ///
+ /// 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.
+ /// The fragment to add to the URL.
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues,
+ bool permanent,
+ bool preserveMethod,
+ string? fragment)
+ {
+ RouteName = routeName;
+ RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
+ PreserveMethod = preserveMethod;
+ Permanent = permanent;
+ Fragment = fragment;
+ }
+
+ ///
+ /// Gets or sets 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.
+ ///
+ public RouteValueDictionary? RouteValues { get; }
+
+ ///
+ /// Gets or sets an indication that the redirect is permanent.
+ ///
+ 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 fragment to add to the URL.
+ ///
+ public string? Fragment { get; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var linkGenerator = httpContext.RequestServices.GetRequiredService();
+
+ var destinationUrl = linkGenerator.GetUriByRouteValues(
+ 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>();
+ Log.RedirectToRouteResultExecuting(logger, destinationUrl, RouteName);
+
+ 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 RedirectToRouteResult, redirecting to {Destination} from route {RouteName}.",
+ EventName = "RedirectToRouteResultExecuting")]
+ public static partial void RedirectToRouteResultExecuting(ILogger logger, string destination, string? routeName);
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs
new file mode 100644
index 000000000000..5b4162c924ea
--- /dev/null
+++ b/src/Http/Http.Results/src/Results.cs
@@ -0,0 +1,1527 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Json;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http.Result;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http
+{
+ ///
+ /// A factory for .
+ ///
+ public static class Results
+ {
+ #region SignIn / SignOut / Challenge
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The behavior of this method depends on the in use.
+ /// and
+ /// are among likely status results.
+ ///
+ ///
+ /// The created for the response.
+ public static IResult Challenge()
+ => new ChallengeResult();
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The behavior of this method depends on the in use.
+ /// and
+ /// are among likely status results.
+ ///
+ ///
+ /// The authentication schemes to challenge.
+ /// The created for the response.
+ public static IResult Challenge(params string[] authenticationSchemes)
+ => new ChallengeResult { AuthenticationSchemes = authenticationSchemes };
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The behavior of this method depends on the in use.
+ /// and
+ /// are among likely status results.
+ ///
+ ///
+ /// used to perform the authentication
+ /// challenge.
+ /// The created for the response.
+ public static IResult Challenge(AuthenticationProperties properties)
+ => new ChallengeResult { Properties = properties };
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The behavior of this method depends on the in use.
+ /// and
+ /// are among likely status results.
+ ///
+ ///
+ /// used to perform the authentication
+ /// challenge.
+ /// The authentication schemes to challenge.
+ /// The created for the response.
+ public static IResult Challenge(
+ AuthenticationProperties properties,
+ params string[] authenticationSchemes)
+ => new ChallengeResult { AuthenticationSchemes = authenticationSchemes, Properties = properties };
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The containing the user claims.
+ /// The created for the response.
+ public static IResult SignIn(ClaimsPrincipal principal)
+ => new SignInResult(principal);
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The containing the user claims.
+ /// The authentication scheme to use for the sign-in operation.
+ /// The created for the response.
+ public static IResult SignIn(ClaimsPrincipal principal, string authenticationScheme)
+ => new SignInResult(authenticationScheme, principal);
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The containing the user claims.
+ /// used to perform the sign-in operation.
+ /// The created for the response.
+ public static IResult SignIn(
+ ClaimsPrincipal principal,
+ AuthenticationProperties properties)
+ => new SignInResult(principal, properties);
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The containing the user claims.
+ /// used to perform the sign-in operation.
+ /// The authentication scheme to use for the sign-in operation.
+ /// The created for the response.
+ public static IResult SignIn(
+ ClaimsPrincipal principal,
+ AuthenticationProperties properties,
+ string authenticationScheme)
+ => new SignInResult(authenticationScheme, principal, properties);
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The created for the response.
+ public static IResult SignOut()
+ => new SignOutResult();
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// used to perform the sign-out operation.
+ /// The created for the response.
+ public static IResult SignOut(AuthenticationProperties properties)
+ => new SignOutResult(properties);
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// The authentication schemes to use for the sign-out operation.
+ /// The created for the response.
+ public static IResult SignOut(params string[] authenticationSchemes)
+ => new SignOutResult(authenticationSchemes);
+
+ ///
+ /// Creates an that on execution invokes .
+ ///
+ /// used to perform the sign-out operation.
+ /// The authentication scheme to use for the sign-out operation.
+ /// The created for the response.
+ public static IResult SignOut(AuthenticationProperties properties, params string[] authenticationSchemes)
+ => new SignOutResult(authenticationSchemes, properties);
+ #endregion
+
+ #region ContentResult
+ ///
+ /// Writes the string to the HTTP response.
+ ///
+ /// The content to write to the response.
+ /// The created object for the response.
+ public static IResult Content(string content)
+ => Content(content, (MediaTypeHeaderValue?)null);
+
+ ///
+ /// Writes the string to the HTTP response.
+ ///
+ /// The content to write to the response.
+ /// The content type (MIME type).
+ /// The created object for the response.
+ public static IResult Content(string content, string contentType)
+ => Content(content, MediaTypeHeaderValue.Parse(contentType));
+
+ ///
+ /// Writes the string to the HTTP response.
+ ///
+ /// The content to write to the response.
+ /// The content type (MIME type).
+ /// The content encoding.
+ /// The created object for the response.
+ ///
+ /// If encoding is provided by both the 'charset' and the parameters, then
+ /// the parameter is chosen as the final encoding.
+ ///
+ public static IResult Content(string content, string contentType, Encoding contentEncoding)
+ {
+ var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType);
+ mediaTypeHeaderValue.Encoding = contentEncoding ?? mediaTypeHeaderValue.Encoding;
+ return Content(content, mediaTypeHeaderValue);
+ }
+
+ ///
+ /// Writes the string to the HTTP response.
+ ///
+ /// The content to write to the response.
+ /// The content type (MIME type).
+ /// The created object for the response.
+ public static IResult Content(string content, MediaTypeHeaderValue? contentType)
+ {
+ return new ContentResult
+ {
+ Content = content,
+ ContentType = contentType?.ToString()
+ };
+ }
+ #endregion
+
+ #region ForbidResult
+ ///
+ /// Creates a that on execution invokes .
+ ///
+ /// By default, executing this result returns a . Some authentication schemes, such as cookies,
+ /// will convert to a redirect to show a login page.
+ ///
+ ///
+ /// The created for the response.
+ public static IResult Forbid()
+ => new ForbidResult();
+
+ ///
+ /// Creates a that on execution invokes .
+ ///
+ /// By default, executing this result returns a . Some authentication schemes, such as cookies,
+ /// will convert to a redirect to show a login page.
+ ///
+ ///
+ /// The authentication schemes to challenge.
+ /// The created for the response.
+ ///
+ /// Some authentication schemes, such as cookies, will convert to
+ /// a redirect to show a login page.
+ ///
+ public static IResult Forbid(params string[] authenticationSchemes)
+ => new ForbidResult { AuthenticationSchemes = authenticationSchemes };
+
+ ///
+ /// Creates a that on execution invokes .
+ ///
+ /// By default, executing this result returns a . Some authentication schemes, such as cookies,
+ /// will convert to a redirect to show a login page.
+ ///
+ ///
+ /// used to perform the authentication
+ /// challenge.
+ /// The created for the response.
+ ///
+ /// Some authentication schemes, such as cookies, will convert to
+ /// a redirect to show a login page.
+ ///
+ public static IResult Forbid(AuthenticationProperties properties)
+ => new ForbidResult { Properties = properties };
+
+ ///
+ /// Creates a that on execution invokes .
+ ///
+ /// By default, executing this result returns a . Some authentication schemes, such as cookies,
+ /// will convert to a redirect to show a login page.
+ ///
+ ///
+ /// used to perform the authentication
+ /// challenge.
+ /// The authentication schemes to challenge.
+ /// The created for the response.
+ ///
+ /// Some authentication schemes, such as cookies, will convert to
+ /// a redirect to show a login page.
+ ///
+ public static IResult Forbid(AuthenticationProperties properties, params string[] authenticationSchemes)
+ => new ForbidResult { Properties = properties, AuthenticationSchemes = authenticationSchemes, };
+ #endregion
+
+ ///
+ /// Creates a that serializes the specified object to JSON.
+ ///
+ /// The object to write as JSON.
+ /// The serializer options 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
+ /// 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)
+ {
+ return new JsonResult
+ {
+ Value = data,
+ JsonSerializerOptions = options,
+ ContentType = contentType,
+ StatusCode = statusCode,
+ };
+ }
+
+ #region FileContentResult
+ ///
+ /// Returns a file with the specified as content (),
+ /// and the specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The file contents.
+ /// The Content-Type of the file.
+ /// The created for the response.
+ public static IResult File(byte[] fileContents, string contentType)
+ => File(fileContents, contentType, fileDownloadName: null);
+
+ ///
+ /// Returns a file with the specified as content (),
+ /// and the specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The file contents.
+ /// The Content-Type of the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult File(byte[] fileContents, string contentType, bool enableRangeProcessing)
+ => File(fileContents, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
+
+ ///
+ /// Returns a file with the specified as content (), the
+ /// specified as the Content-Type and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The file contents.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The created for the response.
+ public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName)
+ => new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName };
+
+ ///
+ /// Returns a file with the specified as content (), the
+ /// specified as the Content-Type and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The file contents.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, bool enableRangeProcessing)
+ => new FileContentResult(fileContents, contentType)
+ {
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+
+ ///
+ /// Returns a file with the specified as content (),
+ /// and the specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The file contents.
+ /// The Content-Type of the file.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// The created for the response.
+ public static IResult File(byte[] fileContents, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+ {
+ return new FileContentResult(fileContents, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ };
+ }
+
+ ///
+ /// Returns a file with the specified as content (),
+ /// and the specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The file contents.
+ /// The Content-Type of the file.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult File(byte[] fileContents, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+ {
+ return new FileContentResult(fileContents, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+
+ ///
+ /// Returns a file with the specified as content (), the
+ /// specified as the Content-Type, and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The file contents.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// The created for the response.
+ public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+ {
+ return new FileContentResult(fileContents, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ };
+ }
+
+ ///
+ /// Returns a file with the specified as content (), the
+ /// specified as the Content-Type, and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The file contents.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+ {
+ return new FileContentResult(fileContents, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+ #endregion
+
+ #region FileStreamResult
+ ///
+ /// Returns a file in the specified (), with the
+ /// specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The with the contents of the file.
+ /// The Content-Type of the file.
+ /// The created for the response.
+ ///
+ /// The parameter is disposed after the response is sent.
+ ///
+ public static IResult File(Stream fileStream, string contentType)
+ => File(fileStream, contentType, fileDownloadName: null);
+
+ ///
+ /// Returns a file in the specified (), with the
+ /// specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The with the contents of the file.
+ /// The Content-Type of the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ ///
+ /// The parameter is disposed after the response is sent.
+ ///
+ public static IResult File(Stream fileStream, string contentType, bool enableRangeProcessing)
+ => File(fileStream, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
+
+ ///
+ /// Returns a file in the specified () with the
+ /// specified as the Content-Type and the
+ /// specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The with the contents of the file.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The created for the response.
+ ///
+ /// The parameter is disposed after the response is sent.
+ ///
+ public static IResult File(Stream fileStream, string contentType, string? fileDownloadName)
+ => new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName };
+
+ ///
+ /// Returns a file in the specified () with the
+ /// specified as the Content-Type and the
+ /// specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The with the contents of the file.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ ///
+ /// The parameter is disposed after the response is sent.
+ ///
+ public static IResult File(Stream fileStream, string contentType, string? fileDownloadName, bool enableRangeProcessing)
+ => new FileStreamResult(fileStream, contentType)
+ {
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+
+ ///
+ /// Returns a file in the specified (),
+ /// and the specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The with the contents of the file.
+ /// The Content-Type of the file.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// The created for the response.
+ ///
+ /// The parameter is disposed after the response is sent.
+ ///
+ public static IResult File(Stream fileStream, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+ {
+ return new FileStreamResult(fileStream, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ };
+ }
+
+ ///
+ /// Returns a file in the specified (),
+ /// and the specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The with the contents of the file.
+ /// The Content-Type of the file.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ ///
+ /// The parameter is disposed after the response is sent.
+ ///
+ public static IResult File(Stream fileStream, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+ {
+ return new FileStreamResult(fileStream, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+
+ ///
+ /// Returns a file in the specified (), the
+ /// specified as the Content-Type, and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The with the contents of the file.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// The created for the response.
+ ///
+ /// The parameter is disposed after the response is sent.
+ ///
+ public static IResult File(Stream fileStream, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+ {
+ return new FileStreamResult(fileStream, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ };
+ }
+
+ ///
+ /// Returns a file in the specified (), the
+ /// specified as the Content-Type, and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The with the contents of the file.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ ///
+ /// The parameter is disposed after the response is sent.
+ ///
+ public static IResult File(Stream fileStream, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+ {
+ return new FileStreamResult(fileStream, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+ #endregion
+
+ #region PhysicalFileResult
+ ///
+ /// Returns the file specified by () with the
+ /// specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type of the file.
+ /// The created for the response.
+ public static IResult PhysicalFile(string physicalPath, string contentType)
+ => PhysicalFile(physicalPath, contentType, fileDownloadName: null);
+
+ ///
+ /// Returns the file specified by () with the
+ /// specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type of the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult PhysicalFile(string physicalPath, string contentType, bool enableRangeProcessing)
+ => PhysicalFile(physicalPath, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
+
+ ///
+ /// Returns the file specified by () with the
+ /// specified as the Content-Type and the
+ /// specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The created for the response.
+ public static IResult PhysicalFile(
+ string physicalPath,
+ string contentType,
+ string? fileDownloadName)
+ => new PhysicalFileResult(physicalPath, contentType) { FileDownloadName = fileDownloadName };
+
+ ///
+ /// Returns the file specified by () with the
+ /// specified as the Content-Type and the
+ /// specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult PhysicalFile(
+ string physicalPath,
+ string contentType,
+ string? fileDownloadName,
+ bool enableRangeProcessing)
+ => new PhysicalFileResult(physicalPath, contentType)
+ {
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+
+ ///
+ /// Returns the file specified by (), and
+ /// the specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type of the file.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// The created for the response.
+ public static IResult PhysicalFile(string physicalPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+ {
+ return new PhysicalFileResult(physicalPath, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ };
+ }
+
+ ///
+ /// Returns the file specified by (), and
+ /// the specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type of the file.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult PhysicalFile(string physicalPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+ {
+ return new PhysicalFileResult(physicalPath, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+
+ ///
+ /// Returns the file specified by (), the
+ /// specified as the Content-Type, and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// The created for the response.
+ public static IResult PhysicalFile(string physicalPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+ {
+ return new PhysicalFileResult(physicalPath, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ };
+ }
+
+ ///
+ /// Returns the file specified by (), the
+ /// specified as the Content-Type, and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The path to the file. The path must be an absolute path.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult PhysicalFile(string physicalPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+ {
+ return new PhysicalFileResult(physicalPath, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+ #endregion
+
+ #region VirtualFileResult
+ ///
+ /// Returns the file specified by () with the
+ /// specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The virtual path of the file to be returned.
+ /// The Content-Type of the file.
+ /// The created for the response.
+ public static IResult File(string virtualPath, string contentType)
+ => File(virtualPath, contentType, fileDownloadName: null);
+
+ ///
+ /// Returns the file specified by () with the
+ /// specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The virtual path of the file to be returned.
+ /// The Content-Type of the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult File(string virtualPath, string contentType, bool enableRangeProcessing)
+ => File(virtualPath, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
+
+ ///
+ /// Returns the file specified by () with the
+ /// specified as the Content-Type and the
+ /// specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The virtual path of the file to be returned.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The created for the response.
+ public static IResult File(string virtualPath, string contentType, string? fileDownloadName)
+ => new VirtualFileResult(virtualPath, contentType) { FileDownloadName = fileDownloadName };
+
+ ///
+ /// Returns the file specified by () with the
+ /// specified as the Content-Type and the
+ /// specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The virtual path of the file to be returned.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult File(string virtualPath, string contentType, string? fileDownloadName, bool enableRangeProcessing)
+ => new VirtualFileResult(virtualPath, contentType)
+ {
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+
+ ///
+ /// Returns the file specified by (), and the
+ /// specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The virtual path of the file to be returned.
+ /// The Content-Type of the file.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// The created for the response.
+ public static IResult File(string virtualPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+ {
+ return new VirtualFileResult(virtualPath, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ };
+ }
+
+ ///
+ /// Returns the file specified by (), and the
+ /// specified as the Content-Type.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The virtual path of the file to be returned.
+ /// The Content-Type of the file.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult File(string virtualPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+ {
+ return new VirtualFileResult(virtualPath, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+
+ ///
+ /// Returns the file specified by (), the
+ /// specified as the Content-Type, and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The virtual path of the file to be returned.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// The created for the response.
+ public static IResult File(string virtualPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+ {
+ return new VirtualFileResult(virtualPath, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ };
+ }
+
+ ///
+ /// Returns the file specified by (), the
+ /// specified as the Content-Type, and the specified as the suggested file name.
+ ///
+ /// This supports range requests ( or
+ /// if the range is not satisfiable).
+ ///
+ ///
+ /// The virtual path of the file to be returned.
+ /// The Content-Type of the file.
+ /// The suggested file name.
+ /// The of when the file was last modified.
+ /// The associated with the file.
+ /// Set to true to enable range requests processing.
+ /// The created for the response.
+ public static IResult File(string virtualPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+ {
+ return new VirtualFileResult(virtualPath, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+ #endregion
+
+ #region RedirectResult variants
+ ///
+ /// Redirects ()
+ /// to the specified .
+ ///
+ /// The URL to redirect to.
+ /// The created for the response.
+ public static IResult Redirect(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException("Argument cannot be null or empty.", nameof(url));
+ }
+
+ return new RedirectResult(url);
+ }
+
+ ///
+ /// Redirects ()
+ /// to the specified .
+ ///
+ /// The URL to redirect to.
+ /// The created for the response.
+ public static IResult RedirectPermanent(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException("Argument cannot be null or empty.", nameof(url));
+ }
+
+ return new RedirectResult(url, permanent: true);
+ }
+
+ ///
+ /// Redirects ()
+ /// to the specified preserving the HTTP method.
+ ///
+ /// The URL to redirect to.
+ /// The created for the response.
+ public static IResult RedirectPreserveMethod(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException("Argument cannot be null or empty.", nameof(url));
+ }
+
+ return new RedirectResult(url: url, permanent: false, preserveMethod: true);
+ }
+
+ ///
+ /// Redirects ()
+ /// to the specified preserving the HTTP method.
+ ///
+ /// The URL to redirect to.
+ /// The created for the response.
+ public static IResult RedirectPermanentPreserveMethod(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException("Argument cannot be null or empty.", nameof(url));
+ }
+
+ return new RedirectResult(url: url, permanent: true, preserveMethod: true);
+ }
+
+ ///
+ /// Redirects ()
+ /// to the specified .
+ ///
+ /// The local URL to redirect to.
+ /// The created for the response.
+ public static IResult LocalRedirect(string localUrl)
+ {
+ if (string.IsNullOrEmpty(localUrl))
+ {
+ throw new ArgumentException("Argument cannot be null or empty.", nameof(localUrl));
+ }
+
+ return new LocalRedirectResult(localUrl);
+ }
+
+ ///
+ /// Redirects ()
+ /// to the specified .
+ ///
+ /// The local URL to redirect to.
+ /// The created for the response.
+ public static IResult LocalRedirectPermanent(string localUrl)
+ {
+ if (string.IsNullOrEmpty(localUrl))
+ {
+ throw new ArgumentException("Argument cannot be null or empty.", nameof(localUrl));
+ }
+
+ return new LocalRedirectResult(localUrl, permanent: true);
+ }
+
+ ///
+ /// Redirects ()
+ /// to the specified preserving the HTTP method.
+ ///
+ /// The local URL to redirect to.
+ /// The created for the response.
+ public static IResult LocalRedirectPreserveMethod(string localUrl)
+ {
+ if (string.IsNullOrEmpty(localUrl))
+ {
+ throw new ArgumentException("Argument cannot be null or empty.", nameof(localUrl));
+ }
+
+ return new LocalRedirectResult(localUrl: localUrl, permanent: false, preserveMethod: true);
+ }
+
+ ///
+ /// Redirects ()
+ /// to the specified preserving the HTTP method.
+ ///
+ /// The local URL to redirect to.
+ /// The created for the response.
+ public static IResult LocalRedirectPermanentPreserveMethod(string localUrl)
+ {
+ if (string.IsNullOrEmpty(localUrl))
+ {
+ throw new ArgumentException("Argument cannot be null or empty.", nameof(localUrl));
+ }
+
+ return new LocalRedirectResult(localUrl: localUrl, permanent: true, preserveMethod: true);
+ }
+
+ ///
+ /// Redirects () to the specified route using the specified .
+ ///
+ /// The name of the route.
+ /// The created for the response.
+ public static IResult RedirectToRoute(string? routeName)
+ => RedirectToRoute(routeName, routeValues: null);
+
+ ///
+ /// Redirects () to the specified route using the specified .
+ ///
+ /// The parameters for a route.
+ /// The created for the response.
+ public static IResult RedirectToRoute(object? routeValues)
+ => RedirectToRoute(routeName: null, routeValues: routeValues);
+
+ ///
+ /// Redirects () to the specified route using the specified
+ /// and .
+ ///
+ /// The name of the route.
+ /// The parameters for a route.
+ /// The created for the response.
+ public static IResult RedirectToRoute(string? routeName, object? routeValues)
+ => RedirectToRoute(routeName, routeValues, fragment: null);
+
+ ///
+ /// Redirects () to the specified route using the specified
+ /// and .
+ ///
+ /// The name of the route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ public static IResult RedirectToRoute(string? routeName, string? fragment)
+ => RedirectToRoute(routeName, routeValues: null, fragment: fragment);
+
+ ///
+ /// Redirects () to the specified route using the specified
+ /// , , and .
+ ///
+ /// The name of the route.
+ /// The parameters for a route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ public static IResult RedirectToRoute(
+ string? routeName,
+ object? routeValues,
+ string? fragment)
+ {
+ return new RedirectToRouteResult(routeName, routeValues, fragment);
+ }
+
+ ///
+ /// Redirects () to the specified route with
+ /// set to false and
+ /// set to true, using the specified , , and .
+ ///
+ /// The name of the route.
+ /// The route data to use for generating the URL.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ public static IResult RedirectToRoutePreserveMethod(
+ string? routeName = null,
+ object? routeValues = null,
+ string? fragment = null)
+ {
+ return new RedirectToRouteResult(
+ routeName: routeName,
+ routeValues: routeValues,
+ permanent: false,
+ preserveMethod: true,
+ fragment: fragment);
+ }
+
+ ///
+ /// Redirects () to the specified route with
+ /// set to true using the specified .
+ ///
+ /// The name of the route.
+ /// The created for the response.
+ public static IResult RedirectToRoutePermanent(string? routeName)
+ => RedirectToRoutePermanent(routeName, routeValues: null);
+
+ ///
+ /// Redirects () to the specified route with
+ /// set to true using the specified .
+ ///
+ /// The parameters for a route.
+ /// The created for the response.
+ public static IResult RedirectToRoutePermanent(object? routeValues)
+ => RedirectToRoutePermanent(routeName: null, routeValues: routeValues);
+
+ ///
+ /// Redirects () to the specified route with
+ /// set to true using the specified
+ /// and .
+ ///
+ /// The name of the route.
+ /// The parameters for a route.
+ /// The created for the response.
+ public static IResult RedirectToRoutePermanent(string? routeName, object? routeValues)
+ => RedirectToRoutePermanent(routeName, routeValues, fragment: null);
+
+ ///
+ /// Redirects () to the specified route with
+ /// set to true using the specified
+ /// and .
+ ///
+ /// The name of the route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ public static IResult RedirectToRoutePermanent(string? routeName, string? fragment)
+ => RedirectToRoutePermanent(routeName, routeValues: null, fragment: fragment);
+
+ ///
+ /// Redirects () to the specified route with
+ /// set to true using the specified ,
+ /// , and .
+ ///
+ /// The name of the route.
+ /// The parameters for a route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ public static IResult RedirectToRoutePermanent(
+ string? routeName,
+ object? routeValues,
+ string? fragment)
+ {
+ return new RedirectToRouteResult(routeName, routeValues, permanent: true, fragment: fragment);
+ }
+
+ ///
+ /// Redirects () to the specified route with
+ /// set to true and
+ /// set to true, using the specified , , and .
+ ///
+ /// The name of the route.
+ /// The route data to use for generating the URL.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ public static IResult RedirectToRoutePermanentPreserveMethod(
+ string? routeName = null,
+ object? routeValues = null,
+ string? fragment = null)
+ {
+ return new RedirectToRouteResult(
+ routeName: routeName,
+ routeValues: routeValues,
+ permanent: true,
+ preserveMethod: true,
+ fragment: fragment);
+ }
+ #endregion
+
+ ///
+ /// Creates a object by specifying a .
+ ///
+ /// The status code to set on the response.
+ /// The created object for the response.
+ public static IResult StatusCode(int statusCode)
+ => new StatusCodeResult(statusCode);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The created for the response.
+ public static IResult NotFound()
+ => new NotFoundResult();
+
+ ///
+ /// Produces a response.
+ ///
+ /// The value to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult NotFound(object? value)
+ => new NotFoundObjectResult(value);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The created for the response.
+ public static IResult Unauthorized()
+ => new UnauthorizedResult();
+
+ ///
+ /// Produces a response.
+ ///
+ /// The created for the response.
+ public static IResult BadRequest()
+ => new BadRequestResult();
+
+ ///
+ /// Produces a response.
+ ///
+ /// An error object to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult BadRequest(object? error)
+ => new BadRequestObjectResult(error);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The created for the response.
+ public static IResult Conflict()
+ => new ConflictResult();
+
+ ///
+ /// Produces a response.
+ ///
+ /// An error object to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult Conflict(object? error)
+ => new ConflictObjectResult(error);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The created for the response.
+ public static IResult NoContent()
+ => new NoContentResult();
+
+ ///
+ /// Produces a response.
+ ///
+ /// The created for the response.
+ public static IResult Ok()
+ => new OkResult();
+
+ ///
+ /// Produces a response.
+ ///
+ /// The value to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult Ok(object? value)
+ => new OkObjectResult(value);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The created for the response.
+ public static IResult UnprocessableEntity()
+ => new UnprocessableEntityResult();
+
+ ///
+ /// Produces a response.
+ ///
+ /// An error object to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult UnprocessableEntity(object? error)
+ => new UnprocessableEntityObjectResult(error);
+
+ #region CreatedResult
+ ///
+ /// Produces a response.
+ ///
+ /// The URI at which the content has been created.
+ /// The value to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult Created(string uri, object? value)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ return new CreatedResult(uri, value);
+ }
+
+ ///
+ /// Produces a response.
+ ///
+ /// The URI at which the content has been created.
+ /// The value to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult Created(Uri uri, object? value)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ return new CreatedResult(uri, value);
+ }
+
+ ///
+ /// Produces a response.
+ ///
+ /// The name of the route to use for generating the URL.
+ /// The value to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult CreatedAtRoute(string? routeName, object? value)
+ => CreatedAtRoute(routeName, routeValues: null, value: value);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The route data to use for generating the URL.
+ /// The value to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult CreatedAtRoute(object? routeValues, object? value)
+ => CreatedAtRoute(routeName: null, routeValues: routeValues, value: value);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The name of the route to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The value to be included in the HTTP response body.
+ /// The created for the response.
+ public static IResult CreatedAtRoute(string? routeName, object? routeValues, object? value)
+ => new CreatedAtRouteResult(routeName, routeValues, value);
+
+ #endregion
+
+ #region AcceptedResult
+ ///
+ /// Produces a response.
+ ///
+ /// The created for the response.
+ public static IResult Accepted()
+ => new AcceptedResult();
+
+ ///
+ /// Produces a response.
+ ///
+ /// The optional content value to format in the response body.
+ /// The created for the response.
+ public static IResult Accepted(object? value)
+ => new AcceptedResult(location: null, value: value);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The optional URI with the location at which the status of requested content can be monitored.
+ /// The created for the response.
+ public static IResult Accepted(Uri uri)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ return new AcceptedResult(locationUri: uri, value: null);
+ }
+
+ ///
+ /// Produces a response.
+ ///
+ /// The optional URI with the location at which the status of requested content can be monitored.
+ /// The created for the response.
+ public static IResult Accepted(string? uri)
+ => new AcceptedResult(location: uri, value: null);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The URI with the location at which the status of requested content can be monitored.
+ /// The optional content value to format in the response body.
+ /// The created for the response.
+ public static IResult Accepted(string? uri, object? value)
+ => new AcceptedResult(uri, value);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The URI with the location at which the status of requested content can be monitored.
+ /// The optional content value to format in the response body.
+ /// The created for the response.
+ public static IResult Accepted(Uri uri, object? value)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ return new AcceptedResult(locationUri: uri, value: value);
+ }
+
+ ///
+ /// Produces a response.
+ ///
+ /// The route data to use for generating the URL.
+ /// The created for the response.
+ public static IResult AcceptedAtRoute(object? routeValues)
+ => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: null);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The name of the route to use for generating the URL.
+ /// The created for the response.
+ public static IResult AcceptedAtRoute(string? routeName)
+ => AcceptedAtRoute(routeName, routeValues: null, value: null);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The name of the route to use for generating the URL.
+ ///The route data to use for generating the URL.
+ /// The created for the response.
+ public static IResult AcceptedAtRoute(string? routeName, object? routeValues)
+ => AcceptedAtRoute(routeName, routeValues, value: null);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The route data to use for generating the URL.
+ /// The optional content value to format in the response body.
+ /// The created for the response.
+ public static IResult AcceptedAtRoute(object? routeValues, object? value)
+ => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: value);
+
+ ///
+ /// Produces a response.
+ ///
+ /// The name of the route to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The optional content value to format in the response body.
+ /// The created for the response.
+ public static IResult AcceptedAtRoute(string? routeName, object? routeValues, object? value)
+ => new AcceptedAtRouteResult(routeName, routeValues, value);
+ #endregion
+ }
+}
diff --git a/src/Http/Http.Results/src/SignInResult.cs b/src/Http/Http.Results/src/SignInResult.cs
new file mode 100644
index 000000000000..3e42c7d35137
--- /dev/null
+++ b/src/Http/Http.Results/src/SignInResult.cs
@@ -0,0 +1,97 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ ///
+ /// An that on execution invokes .
+ ///
+ internal sealed partial class SignInResult : IResult
+ {
+ ///
+ /// 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)
+ {
+ }
+
+ ///
+ /// 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
+ /// specified authentication scheme and .
+ ///
+ /// The authentication schemes to use when signing in the user.
+ /// The claims principal containing the user claims.
+ /// used to perform the sign-in operation.
+ public SignInResult(string? authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties? properties)
+ {
+ Principal = principal ?? throw new ArgumentNullException(nameof(principal));
+ AuthenticationScheme = authenticationScheme;
+ Properties = properties;
+ }
+
+ ///
+ /// Gets or sets the authentication scheme that is used to perform the sign-in operation.
+ ///
+ public string? AuthenticationScheme { get; set; }
+
+ ///
+ /// Gets or sets the containing the user claims.
+ ///
+ public ClaimsPrincipal Principal { get; set; }
+
+ ///
+ /// Gets or sets the used to perform the sign-in operation.
+ ///
+ public AuthenticationProperties? Properties { get; set; }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService>();
+
+ Log.SignInResultExecuting(logger, AuthenticationScheme, Principal);
+
+ return httpContext.SignInAsync(AuthenticationScheme, Principal, Properties);
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing SignInResult with authentication scheme ({Scheme}) and the following principal: {Principal}.",
+ EventName = "SignInResultExecuting")]
+ public static partial void SignInResultExecuting(ILogger logger, string? scheme, ClaimsPrincipal principal);
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/SignOutResult.cs b/src/Http/Http.Results/src/SignOutResult.cs
new file mode 100644
index 000000000000..6601a9eb1ed5
--- /dev/null
+++ b/src/Http/Http.Results/src/SignOutResult.cs
@@ -0,0 +1,127 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ ///
+ /// An that on execution invokes .
+ ///
+ internal sealed partial class SignOutResult : IResult
+ {
+ ///
+ /// Initializes a new instance of with the default sign out scheme.
+ ///
+ public SignOutResult()
+ : this(Array.Empty())
+ {
+ }
+
+ ///
+ /// 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)
+ : this(Array.Empty(), properties)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified authentication scheme.
+ ///
+ /// The authentication scheme to use when signing out the user.
+ public SignOutResult(string authenticationScheme)
+ : this(new[] { authenticationScheme })
+ {
+ }
+
+ ///
+ /// Initializes a new instance of with the
+ /// specified authentication schemes.
+ ///
+ /// The authentication schemes to use when signing out the user.
+ public SignOutResult(IList authenticationSchemes)
+ : this(authenticationSchemes, properties: null)
+ {
+ }
+
+ ///
+ /// 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)
+ : this(new[] { authenticationScheme }, properties)
+ {
+ }
+
+ ///
+ /// 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)
+ {
+ AuthenticationSchemes = authenticationSchemes ?? throw new ArgumentNullException(nameof(authenticationSchemes));
+ Properties = properties;
+ }
+
+ ///
+ /// Gets or sets the authentication schemes that are challenged.
+ ///
+ public IList AuthenticationSchemes { get; init; }
+
+ ///
+ /// Gets or sets the used to perform the sign-out operation.
+ ///
+ public AuthenticationProperties? Properties { get; init; }
+
+ ///
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService>();
+
+ Log.SignOutResultExecuting(logger, AuthenticationSchemes);
+
+ if (AuthenticationSchemes.Count == 0)
+ {
+ await httpContext.SignOutAsync(Properties);
+ }
+ else
+ {
+ for (var i = 0; i < AuthenticationSchemes.Count; i++)
+ {
+ await httpContext.SignOutAsync(AuthenticationSchemes[i], Properties);
+ }
+ }
+ }
+
+ private static partial class Log
+ {
+ public static void SignOutResultExecuting(ILogger logger, IList authenticationSchemes)
+ {
+ if (logger.IsEnabled(LogLevel.Information))
+ {
+ SignOutResultExecuting(logger, authenticationSchemes.ToArray());
+ }
+ }
+
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing SignOutResult with authentication schemes ({Schemes}).",
+ EventName = "SignOutResultExecuting",
+ SkipEnabledCheck = true)]
+ private static partial void SignOutResultExecuting(ILogger logger, string[] schemes);
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/StatusCodeResult.cs b/src/Http/Http.Results/src/StatusCodeResult.cs
new file mode 100644
index 000000000000..0c8684928446
--- /dev/null
+++ b/src/Http/Http.Results/src/StatusCodeResult.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal partial class StatusCodeResult : IResult
+ {
+ ///
+ /// Initializes a new instance of the class
+ /// with the given .
+ ///
+ /// The HTTP status code of the response.
+ public StatusCodeResult(int statusCode)
+ {
+ StatusCode = statusCode;
+ }
+
+ ///
+ /// Gets the HTTP status code.
+ ///
+ public int StatusCode { get; }
+
+ ///
+ /// Sets the status code on the HTTP response.
+ ///
+ /// The for the current request.
+ /// 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);
+
+ 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);
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/UnauthorizedResult.cs b/src/Http/Http.Results/src/UnauthorizedResult.cs
new file mode 100644
index 000000000000..ddf3b032e00e
--- /dev/null
+++ b/src/Http/Http.Results/src/UnauthorizedResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal sealed class UnauthorizedResult : StatusCodeResult
+ {
+ public UnauthorizedResult() : base(StatusCodes.Status401Unauthorized)
+ {
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs b/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
new file mode 100644
index 000000000000..d188da672a78
--- /dev/null
+++ b/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+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/UnprocessableEntityResult.cs b/src/Http/Http.Results/src/UnprocessableEntityResult.cs
new file mode 100644
index 000000000000..f58c43059e94
--- /dev/null
+++ b/src/Http/Http.Results/src/UnprocessableEntityResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ internal sealed class UnprocessableEntityResult : StatusCodeResult
+ {
+ public UnprocessableEntityResult() : base(StatusCodes.Status422UnprocessableEntity)
+ {
+ }
+ }
+}
diff --git a/src/Http/Http.Results/src/VirtualFileResult.cs b/src/Http/Http.Results/src/VirtualFileResult.cs
new file mode 100644
index 000000000000..975026f35298
--- /dev/null
+++ b/src/Http/Http.Results/src/VirtualFileResult.cs
@@ -0,0 +1,129 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Internal;
+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;
+
+ ///
+ /// 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)
+ : this(fileName, MediaTypeHeaderValue.Parse(contentType))
+ {
+ }
+
+ ///
+ /// 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, MediaTypeHeaderValue contentType)
+ : base(contentType.ToString())
+ {
+ 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));
+ }
+
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var hostingEnvironment = httpContext.RequestServices.GetRequiredService();
+ var logger = httpContext.RequestServices.GetRequiredService>();
+
+ var fileInfo = GetFileInformation(hostingEnvironment.WebRootFileProvider);
+ if (!fileInfo.Exists)
+ {
+ throw new FileNotFoundException($"Could not find file: {FileName}.", FileName);
+ }
+
+ Log.ExecutingFileResult(logger, this);
+
+ var lastModified = LastModified ?? fileInfo.LastModified;
+ var fileResultInfo = new FileResultInfo
+ {
+ ContentType = ContentType,
+ FileDownloadName = FileDownloadName,
+ EnableRangeProcessing = EnableRangeProcessing,
+ EntityTag = EntityTag,
+ LastModified = lastModified,
+ };
+
+ var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+ httpContext,
+ fileResultInfo,
+ fileInfo.Length,
+ EnableRangeProcessing,
+ lastModified,
+ EntityTag,
+ logger);
+
+ if (!serveBody)
+ {
+ return Task.CompletedTask;
+ }
+
+ if (range != null)
+ {
+ FileResultHelper.Log.WritingRangeToBody(logger);
+ }
+
+ 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/test/AcceptedAtRouteResultTests.cs b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
new file mode 100644
index 000000000000..cca95a941621
--- /dev/null
+++ b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
@@ -0,0 +1,120 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+ public class AcceptedAtRouteResultTests
+ {
+ [Fact]
+ public async Task ExecuteResultAsync_FormatsData()
+ {
+ // Arrange
+ var url = "testAction";
+ var linkGenerator = new TestLinkGenerator { Url = url };
+ var httpContext = GetHttpContext(linkGenerator);
+ var stream = new MemoryStream();
+ httpContext.Response.Body = stream;
+
+ var routeValues = new RouteValueDictionary(new Dictionary()
+ {
+ { "test", "case" },
+ { "sample", "route" }
+ });
+
+ // Act
+ var result = new AcceptedAtRouteResult(
+ routeName: "sample",
+ routeValues: routeValues,
+ value: "Hello world");
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ var response = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal("\"Hello world\"", response);
+ }
+
+ public static TheoryData