This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Fixes #3346 - Added helper method in controller - Added relevant tests
- Loading branch information
1 parent
b6d7012
commit eb398c8
Showing
8 changed files
with
379 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// 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.AspNet.Mvc.Core; | ||
using Microsoft.AspNet.Mvc.Logging; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.AspNet.Mvc | ||
{ | ||
/// <summary> | ||
/// An <see cref="ActionResult"/> that returns a redirect to the supplied local URL. | ||
/// </summary> | ||
public class LocalRedirectResult : ActionResult | ||
{ | ||
private string _localUrl; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values | ||
/// provided. | ||
/// </summary> | ||
/// <param name="localUrl">The local URL to redirect to.</param> | ||
public LocalRedirectResult(string localUrl) | ||
: this(localUrl, permanent: false) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values | ||
/// provided. | ||
/// </summary> | ||
/// <param name="url">The local URL to redirect to.</param> | ||
/// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param> | ||
public LocalRedirectResult(string localUrl, bool permanent) | ||
{ | ||
if (string.IsNullOrEmpty(localUrl)) | ||
{ | ||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); | ||
} | ||
|
||
Permanent = permanent; | ||
Url = localUrl; | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false. | ||
/// </summary> | ||
public bool Permanent { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the local URL to redirect to. | ||
/// </summary> | ||
public string Url | ||
{ | ||
get | ||
{ | ||
return _localUrl; | ||
} | ||
set | ||
{ | ||
if (string.IsNullOrEmpty(value)) | ||
{ | ||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(value)); | ||
} | ||
|
||
_localUrl = value; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets the <see cref="IUrlHelper"/> for this result. | ||
/// </summary> | ||
public IUrlHelper UrlHelper { get; set; } | ||
|
||
/// <inheritdoc /> | ||
public override void ExecuteResult(ActionContext context) | ||
{ | ||
if (context == null) | ||
{ | ||
throw new ArgumentNullException(nameof(context)); | ||
} | ||
|
||
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>(); | ||
var logger = loggerFactory.CreateLogger<LocalRedirectResult>(); | ||
|
||
var urlHelper = GetUrlHelper(context); | ||
|
||
if (!urlHelper.IsLocalUrl(Url)) | ||
{ | ||
throw new InvalidOperationException(Resources.UrlNotLocal); | ||
} | ||
|
||
var destinationUrl = urlHelper.Content(Url); | ||
logger.LocalRedirectResultExecuting(destinationUrl); | ||
context.HttpContext.Response.Redirect(destinationUrl, Permanent); | ||
} | ||
|
||
private IUrlHelper GetUrlHelper(ActionContext context) | ||
{ | ||
return UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService<IUrlHelper>(); | ||
} | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
src/Microsoft.AspNet.Mvc.Core/Logging/LocalRedirectResultLoggerExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// 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.Extensions.Logging; | ||
|
||
namespace Microsoft.AspNet.Mvc.Logging | ||
{ | ||
public static class LocalRedirectResultLoggerExtensions | ||
{ | ||
private static Action<ILogger, string, Exception> _localRedirectResultExecuting; | ||
|
||
static LocalRedirectResultLoggerExtensions() | ||
{ | ||
_localRedirectResultExecuting = LoggerMessage.Define<string>( | ||
LogLevel.Information, | ||
1, | ||
"Executing LocalRedirectResult, redirecting to {Destination}."); | ||
} | ||
|
||
public static void LocalRedirectResultExecuting(this ILogger logger, string destination) | ||
{ | ||
_localRedirectResultExecuting(logger, destination, null); | ||
} | ||
} | ||
} |
37 changes: 32 additions & 5 deletions
37
src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
test/Microsoft.AspNet.Mvc.Core.Test/LocalRedirectResultTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// 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.AspNet.Http; | ||
using Microsoft.AspNet.Mvc.Abstractions; | ||
using Microsoft.AspNet.Mvc.Infrastructure; | ||
using Microsoft.AspNet.Mvc.Routing; | ||
using Microsoft.AspNet.Routing; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using Moq; | ||
using Xunit; | ||
|
||
namespace Microsoft.AspNet.Mvc | ||
{ | ||
public class LocalRedirectResultTest | ||
{ | ||
[Fact] | ||
public void Constructor_WithParameterUrl_SetsResultUrlAndNotPermanent() | ||
{ | ||
// Arrange | ||
var url = "/test/url"; | ||
|
||
// Act | ||
var result = new LocalRedirectResult(url); | ||
|
||
// Assert | ||
Assert.False(result.Permanent); | ||
Assert.Same(url, result.Url); | ||
} | ||
|
||
[Fact] | ||
public void Constructor_WithParameterUrlAndPermanent_SetsResultUrlAndPermanent() | ||
{ | ||
// Arrange | ||
var url = "/test/url"; | ||
|
||
// Act | ||
var result = new LocalRedirectResult(url, permanent: true); | ||
|
||
// Assert | ||
Assert.True(result.Permanent); | ||
Assert.Same(url, result.Url); | ||
} | ||
|
||
[Fact] | ||
public void Execute_ReturnsExpectedValues() | ||
{ | ||
// Arrange | ||
var appRoot = "/"; | ||
var contentPath = "~/Home/About"; | ||
var expectedPath = "/Home/About"; | ||
var httpResponse = new Mock<HttpResponse>(); | ||
httpResponse.Setup(o => o.Redirect(expectedPath, false)) | ||
.Verifiable(); | ||
|
||
var httpContext = GetHttpContext(appRoot, contentPath, expectedPath, httpResponse.Object); | ||
var actionContext = GetActionContext(httpContext); | ||
var result = new LocalRedirectResult(contentPath); | ||
|
||
// Act | ||
result.ExecuteResult(actionContext); | ||
|
||
// Assert | ||
httpResponse.Verify(); | ||
} | ||
|
||
[Theory] | ||
[InlineData("", "Home/About", "/Home/About")] | ||
[InlineData("/myapproot", "http://www.example.com", "/test")] | ||
public void Execute_Throws_ForNonLocalUrl( | ||
string appRoot, | ||
string contentPath, | ||
string expectedPath) | ||
{ | ||
// Arrange | ||
var httpResponse = new Mock<HttpResponse>(); | ||
httpResponse.Setup(o => o.Redirect(expectedPath, false)) | ||
.Verifiable(); | ||
|
||
var httpContext = GetHttpContext(appRoot, contentPath, expectedPath, httpResponse.Object); | ||
var actionContext = GetActionContext(httpContext); | ||
var result = new LocalRedirectResult(contentPath); | ||
|
||
// Act & Assert | ||
var exception = Assert.Throws<InvalidOperationException>(() => result.ExecuteResult(actionContext)); | ||
Assert.Equal( | ||
"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.", | ||
exception.Message); | ||
} | ||
|
||
private static ActionContext GetActionContext(HttpContext httpContext) | ||
{ | ||
var routeData = new RouteData(); | ||
routeData.Routers.Add(new Mock<IRouter>().Object); | ||
|
||
return new ActionContext(httpContext, routeData, new ActionDescriptor()); | ||
} | ||
|
||
private static IServiceProvider GetServiceProvider(IUrlHelper urlHelper) | ||
{ | ||
var serviceCollection = new ServiceCollection(); | ||
serviceCollection.AddInstance<IUrlHelper>(urlHelper); | ||
serviceCollection.AddTransient<ILoggerFactory, LoggerFactory>(); | ||
return serviceCollection.BuildServiceProvider(); | ||
} | ||
|
||
private static HttpContext GetHttpContext( | ||
string appRoot, | ||
string contentPath, | ||
string expectedPath, | ||
HttpResponse response) | ||
{ | ||
var httpContext = new Mock<HttpContext>(); | ||
var actionContext = GetActionContext(httpContext.Object); | ||
var actionContextAccessor = new ActionContextAccessor() { ActionContext = actionContext }; | ||
var mockActionSelector = new Mock<IActionSelector>(); | ||
var urlHelper = new UrlHelper(actionContextAccessor, mockActionSelector.Object); | ||
var serviceProvider = GetServiceProvider(urlHelper); | ||
|
||
httpContext.Setup(o => o.Response) | ||
.Returns(response); | ||
httpContext.SetupGet(o => o.RequestServices) | ||
.Returns(serviceProvider); | ||
httpContext.Setup(o => o.Request.PathBase) | ||
.Returns(new PathString(appRoot)); | ||
|
||
return httpContext.Object; | ||
} | ||
} | ||
} |
Oops, something went wrong.