Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Added LocalRedirectresult
Browse files Browse the repository at this point in the history
- Fixes #3346
- Added helper method in controller
- Added relevant tests
  • Loading branch information
ajaybhargavb committed Oct 23, 2015
1 parent b6d7012 commit eb398c8
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/Microsoft.AspNet.Mvc.Core/IUrlHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public interface IUrlHelper
string Content(string contentPath);

/// <summary>
/// Returns a value that indicates whether the URL is local. An URL with an absolute path is considered local
/// if it does not have a host/authority part. URLs using the virtual paths ('~/') are also local.
/// Returns a value that indicates whether the URL is 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.
/// </summary>
/// <param name="url">The URL.</param>
/// <returns><c>true</c> if the URL is local; otherwise, <c>false</c>.</returns>
Expand Down
104 changes: 104 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/LocalRedirectResult.cs
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>();
}
}
}
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 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.

3 changes: 3 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,7 @@
<value>Path '{0}' was not rooted.</value>
<comment>{0} is the path which wasn't rooted</comment>
</data>
<data name="UrlNotLocal" xml:space="preserve">
<value>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.</value>
</data>
</root>
34 changes: 34 additions & 0 deletions src/Microsoft.AspNet.Mvc.ViewFeatures/Controller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,40 @@ public virtual RedirectResult RedirectPermanent(string url)
return new RedirectResult(url, permanent: true);
}

/// <summary>
/// Creates a <see cref="LocalRedirectResult"/> object that redirects to
/// the specified local <paramref name="localUrl"/>.
/// </summary>
/// <param name="localUrl">The local URL to redirect to.</param>
/// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
[NonAction]
public virtual LocalRedirectResult LocalRedirect(string localUrl)
{
if (string.IsNullOrEmpty(localUrl))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl));
}

return new LocalRedirectResult(localUrl);
}

/// <summary>
/// Creates a <see cref="LocalRedirectResult"/> object with <see cref="LocalRedirectResult.Permanent"/>
/// set to true using the specified <paramref name="localUrl"/>.
/// </summary>
/// <param name="localUrl">The local URL to redirect to.</param>
/// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
[NonAction]
public virtual LocalRedirectResult LocalRedirectPermanent(string localUrl)
{
if (string.IsNullOrEmpty(localUrl))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl));
}

return new LocalRedirectResult(localUrl, permanent: true);
}

/// <summary>
/// Redirects to the specified action using the <paramref name="actionName"/>.
/// </summary>
Expand Down
133 changes: 133 additions & 0 deletions test/Microsoft.AspNet.Mvc.Core.Test/LocalRedirectResultTest.cs
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;
}
}
}
Loading

0 comments on commit eb398c8

Please sign in to comment.