| File: RedirectHttpResult.cs | Web Access |
| Project: src\aspnetcore\src\Http\Http.Results\src\Microsoft.AspNetCore.Http.Results.csproj (Microsoft.AspNetCore.Http.Results) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Http.HttpResults; /// <summary> /// An <see cref="IResult"/> that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), /// or Permanent Redirect (308) response with a Location header to the supplied URL. /// </summary> public sealed partial class RedirectHttpResult : IResult { /// <summary> /// Initializes a new instance of the <see cref="RedirectHttpResult"/> class with the values /// provided. /// </summary> /// <param name="url">The URL to redirect to.</param> internal RedirectHttpResult([StringSyntax(StringSyntaxAttribute.Uri)] string url) : this(url, permanent: false) { } /// <summary> /// Initializes a new instance of the <see cref="RedirectHttpResult"/> class with the values /// provided. /// </summary> /// <param name="url">The URL to redirect to.</param> /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param> internal RedirectHttpResult([StringSyntax(StringSyntaxAttribute.Uri)] string url, bool permanent) : this(url, permanent, preserveMethod: false) { } /// <summary> /// Initializes a new instance of the <see cref="RedirectHttpResult"/> class with the values /// provided. /// </summary> /// <param name="url">The URL to redirect to.</param> /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param> /// <param name="preserveMethod">If set to true, make the temporary redirect (307) /// or permanent redirect (308) preserve the initial request method.</param> internal RedirectHttpResult([StringSyntax(StringSyntaxAttribute.Uri)] string url, bool permanent, bool preserveMethod) : this(url, acceptLocalUrlOnly: false, permanent, preserveMethod) { } /// <summary> /// Initializes a new instance of the <see cref="RedirectHttpResult"/> class with the values /// provided. /// </summary> /// <param name="url">The URL to redirect to.</param> /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param> /// <param name="preserveMethod">If set to true, make the temporary redirect (307) /// or permanent redirect (308) preserve the initial request method.</param> /// <param name="acceptLocalUrlOnly">If set to true, only local URLs are accepted /// and will throw an exception when the supplied URL is not considered local.</param> internal RedirectHttpResult([StringSyntax(StringSyntaxAttribute.Uri)] string url, bool acceptLocalUrlOnly, bool permanent, bool preserveMethod) { Url = url; Permanent = permanent; PreserveMethod = preserveMethod; AcceptLocalUrlOnly = acceptLocalUrlOnly; } /// <summary> /// Gets the value that specifies that the redirect should be permanent if true or temporary if false. /// </summary> public bool Permanent { get; } /// <summary> /// Gets an indication that the redirect preserves the initial request method. /// </summary> public bool PreserveMethod { get; } /// <summary> /// Gets the URL to redirect to. /// </summary> public string Url { get; } /// <summary> /// Gets an indication that only local URLs are accepted. /// </summary> public bool AcceptLocalUrlOnly { get; } /// <inheritdoc /> public Task ExecuteAsync(HttpContext httpContext) { ArgumentNullException.ThrowIfNull(httpContext); // Creating the logger with a string to preserve the category after the refactoring. var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.RedirectResult"); var isLocalUrl = SharedUrlHelper.IsLocalUrl(Url); if (AcceptLocalUrlOnly && !isLocalUrl) { throw new InvalidOperationException("The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local."); } // IsLocalUrl is called to handle URLs starting with '~/'. var destinationUrl = isLocalUrl ? SharedUrlHelper.Content(httpContext, contentPath: Url) : Url; Log.RedirectResultExecuting(logger, destinationUrl); if (PreserveMethod) { httpContext.Response.StatusCode = Permanent ? StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; httpContext.Response.Headers.Location = destinationUrl; } else { httpContext.Response.Redirect(destinationUrl, Permanent); } return Task.CompletedTask; } /// <summary> /// Returns a value that indicates whether the URL is local. A URL is considered /// local if it does not have a host / authority part and it has an absolute path. /// URLs using virtual paths (<c>'~/'</c>) are also local. /// </summary> /// <param name="url">The URL.</param> /// <returns> /// <see langword="true"/> if the URL is local; otherwise, <see langword="false"/>. /// </returns> public static bool IsLocalUrl([NotNullWhen(true)][StringSyntax(StringSyntaxAttribute.Uri)] string? url) => SharedUrlHelper.IsLocalUrl(url); 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); } }