Modify UrlHelper to use a single StringBuilder
This commit is contained in:
parent
aa5a4d4af2
commit
e078259547
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue