Modify UrlHelper to use a single StringBuilder

This commit is contained in:
Pranav K 2016-01-06 12:47:36 -08:00
parent aa5a4d4af2
commit e078259547
2 changed files with 100 additions and 84 deletions

View File

@ -19,9 +19,7 @@ namespace Microsoft.AspNet.Mvc.Routing
/// Initializes a new instance of the <see cref="UrlHelper"/> class using the specified action context and
/// action selector.
/// </summary>
/// <param name="actionContext">
/// The <see cref="Mvc.ActionContext"/> for the current request.
/// </param>
/// <param name="actionContext">The <see cref="Mvc.ActionContext"/> for the current request.</param>
public UrlHelper(ActionContext actionContext)
{
if (actionContext == null)
@ -79,13 +77,8 @@ namespace Microsoft.AspNet.Mvc.Routing
valuesDictionary["controller"] = actionContext.Controller;
}
var path = GeneratePathFromRoute(routeName: null, values: valuesDictionary);
if (path == null)
{
return null;
}
return GenerateUrl(actionContext.Protocol, actionContext.Host, path, actionContext.Fragment);
var virtualPathData = GetVirtualPathData(routeName: null, values: valuesDictionary);
return GenerateUrl(actionContext.Protocol, actionContext.Host, virtualPathData, actionContext.Fragment);
}
/// <inheritdoc />
@ -110,61 +103,53 @@ namespace Microsoft.AspNet.Mvc.Routing
}
var valuesDictionary = new RouteValueDictionary(routeContext.Values);
var path = GeneratePathFromRoute(routeContext.RouteName, valuesDictionary);
if (path == null)
{
return null;
}
return GenerateUrl(routeContext.Protocol, routeContext.Host, path, routeContext.Fragment);
var virtualPathData = GetVirtualPathData(routeContext.RouteName, valuesDictionary);
return GenerateUrl(routeContext.Protocol, routeContext.Host, virtualPathData, routeContext.Fragment);
}
/// <summary>
/// Generates the absolute path of the url for the specified route values by
/// using the specified route name.
/// Gets the <see cref="VirtualPathData"/> for the specified route values by using the specified route name.
/// </summary>
/// <param name="routeName">The name of the route that is used to generate the URL.</param>
/// <param name="routeName">The name of the route that is used to generate the <see cref="VirtualPathData"/>.
/// </param>
/// <param name="values">A dictionary that contains the parameters for a route.</param>
/// <returns>The absolute path of the URL.</returns>
protected virtual string GeneratePathFromRoute(string routeName, RouteValueDictionary values)
/// <returns>The <see cref="VirtualPathData"/>.</returns>
protected virtual VirtualPathData GetVirtualPathData(string routeName, RouteValueDictionary values)
{
var context = new VirtualPathContext(HttpContext, AmbientValues, values, routeName);
var pathData = Router.GetVirtualPath(context);
if (pathData == null)
{
return null;
}
return Router.GetVirtualPath(context);
}
// VirtualPathData.VirtualPath returns string.Empty for null.
Debug.Assert(pathData.VirtualPath != null);
// Internal for unit testing.
internal void AppendPathAndFragment(StringBuilder builder, VirtualPathData pathData, string fragment)
{
var pathBase = HttpContext.Request.PathBase;
if (!pathBase.HasValue)
{
if (pathData.VirtualPath.Length == 0)
{
return "/";
}
else if (!pathData.VirtualPath.StartsWith("/", StringComparison.Ordinal))
{
return "/" + pathData.VirtualPath;
builder.Append("/");
}
else
{
return pathData.VirtualPath;
if (!pathData.VirtualPath.StartsWith("/", StringComparison.Ordinal))
{
builder.Append("/");
}
builder.Append(pathData.VirtualPath);
}
}
else
{
if (pathData.VirtualPath.Length == 0)
{
return pathBase;
builder.Append(pathBase.Value);
}
else
{
var builder = new StringBuilder(
pathBase.Value,
pathBase.Value.Length + pathData.VirtualPath.Length);
builder.Append(pathBase.Value);
if (pathBase.Value.EndsWith("/", StringComparison.Ordinal))
{
@ -177,10 +162,13 @@ namespace Microsoft.AspNet.Mvc.Routing
}
builder.Append(pathData.VirtualPath);
return builder.ToString();
}
}
if (!string.IsNullOrEmpty(fragment))
{
builder.Append("#").Append(fragment);
}
}
/// <inheritdoc />
@ -213,34 +201,47 @@ namespace Microsoft.AspNet.Mvc.Routing
});
}
private string GenerateUrl(string protocol, string host, string path, string fragment)
/// <summary>
/// Generates the URL using the specified components.
/// </summary>
/// <param name="protocol">The protocol.</param>
/// <param name="host">The host.</param>
/// <param name="pathData">The <see cref="VirtualPathData"/>.</param>
/// <param name="fragment">The URL fragment.</param>
/// <returns>The generated URL.</returns>
protected virtual string GenerateUrl(string protocol, string host, VirtualPathData pathData, string fragment)
{
Debug.Assert(path != null);
var url = path;
if (!string.IsNullOrEmpty(fragment))
if (pathData == null)
{
url += "#" + fragment;
return null;
}
// VirtualPathData.VirtualPath returns string.Empty instead of null.
Debug.Assert(pathData.VirtualPath != null);
var builder = new StringBuilder();
if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host))
{
// We're returning a partial url (just path + query + fragment), but we still want it
// to be rooted.
if (!url.StartsWith("/", StringComparison.Ordinal))
AppendPathAndFragment(builder, pathData, fragment);
// We're returning a partial URL (just path + query + fragment), but we still want it to be rooted.
if (builder.Length == 0 || builder[0] != '/')
{
url = "/" + url;
builder.Insert(0, '/');
}
return url;
}
else
{
protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol;
host = string.IsNullOrEmpty(host) ? HttpContext.Request.Host.Value : host;
builder.Append(protocol);
url = protocol + "://" + host + url;
return url;
builder.Append("://");
host = string.IsNullOrEmpty(host) ? HttpContext.Request.Host.Value : host;
builder.Append(host);
AppendPathAndFragment(builder, pathData, fragment);
}
return builder.ToString();
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
@ -892,25 +893,50 @@ namespace Microsoft.AspNet.Mvc.Routing
[Theory]
[MemberData(nameof(GeneratePathFromRoute_HandlesLeadingAndTrailingSlashesData))]
public void GeneratePathFromRoute_HandlesLeadingAndTrailingSlashes(
public void AppendPathAndFragment_HandlesLeadingAndTrailingSlashes(
string appBase,
string virtualPath,
string expected)
{
// Arrage
var router = new Mock<IRouter>();
router.Setup(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()))
.Returns(new VirtualPathData(router.Object, virtualPath)
{
VirtualPath = virtualPath
});
var urlHelper = CreateUrlHelper(appBase, router.Object);
var router = Mock.Of<IRouter>();
var pathData = new VirtualPathData(router, virtualPath)
{
VirtualPath = virtualPath
};
var urlHelper = CreateUrlHelper(appBase, router);
var builder = new StringBuilder();
// Act
var result = urlHelper.GeneratePathFromRoutePublic("some-name", new RouteValueDictionary());
urlHelper.AppendPathAndFragment(builder, pathData, string.Empty);
// Assert
Assert.Equal(expected, result);
Assert.Equal(expected, builder.ToString());
}
[Theory]
[MemberData(nameof(GeneratePathFromRoute_HandlesLeadingAndTrailingSlashesData))]
public void AppendPathAndFragment_AppendsFragments(
string appBase,
string virtualPath,
string expected)
{
// Arrage
var fragmentValue = "fragment-value";
expected += $"#{fragmentValue}";
var router = Mock.Of<IRouter>();
var pathData = new VirtualPathData(router, virtualPath)
{
VirtualPath = virtualPath
};
var urlHelper = CreateUrlHelper(appBase, router);
var builder = new StringBuilder();
// Act
urlHelper.AppendPathAndFragment(builder, pathData, fragmentValue);
// Assert
Assert.Equal(expected, builder.ToString());
}
private static HttpContext CreateHttpContext(
@ -939,13 +965,13 @@ namespace Microsoft.AspNet.Mvc.Routing
return new ActionContext(context, routeData, new ActionDescriptor());
}
private static TestableUrlHelper CreateUrlHelper()
private static UrlHelper CreateUrlHelper()
{
var services = CreateServices();
var context = CreateHttpContext(services, string.Empty);
var actionContext = CreateActionContext(context);
return new TestableUrlHelper(actionContext);
return new UrlHelper(actionContext);
}
private static UrlHelper CreateUrlHelper(ActionContext context)
@ -964,7 +990,7 @@ namespace Microsoft.AspNet.Mvc.Routing
return new UrlHelper(actionContext);
}
private static TestableUrlHelper CreateUrlHelper(string host, string protocol, IRouter router)
private static UrlHelper CreateUrlHelper(string host, string protocol, IRouter router)
{
var services = CreateServices();
var context = CreateHttpContext(services, string.Empty);
@ -973,19 +999,19 @@ namespace Microsoft.AspNet.Mvc.Routing
var actionContext = CreateActionContext(context, router);
return new TestableUrlHelper(actionContext);
return new UrlHelper(actionContext);
}
private static TestableUrlHelper CreateUrlHelper(string appBase, IRouter router)
private static UrlHelper CreateUrlHelper(string appBase, IRouter router)
{
var services = CreateServices();
var context = CreateHttpContext(services, appBase);
var actionContext = CreateActionContext(context, router);
return new TestableUrlHelper(actionContext);
return new UrlHelper(actionContext);
}
private static TestableUrlHelper CreateUrlHelperWithRouteCollection(
private static UrlHelper CreateUrlHelperWithRouteCollection(
IServiceProvider services,
string appPrefix)
{
@ -1066,16 +1092,5 @@ namespace Microsoft.AspNet.Mvc.Routing
return Task.FromResult(false);
}
}
private class TestableUrlHelper : UrlHelper
{
public TestableUrlHelper(ActionContext context)
: base(context)
{
}
public string GeneratePathFromRoutePublic(string routeName, RouteValueDictionary values) =>
GeneratePathFromRoute(routeName, values);
}
}
}