diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs index 1cf815cb41..ff839a1556 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs @@ -233,6 +233,15 @@ namespace Microsoft.AspNetCore.Mvc.Routing // VirtualPathData.VirtualPath returns string.Empty instead of null. Debug.Assert(pathData.VirtualPath != null); + // Perf: In most of the common cases, GenerateUrl is called with a null protocol, host and fragment. + // In such cases, we might not need to build any URL as the url generated is mostly same as the virtual path available in pathData. + // For such common cases, this FastGenerateUrl method saves a string allocation per GenerateUrl call. + string url; + if (TryFastGenerateUrl(protocol, host, pathData, fragment, out url)) + { + return url; + } + var builder = GetStringBuilder(); try { @@ -266,5 +275,35 @@ namespace Microsoft.AspNetCore.Mvc.Routing builder.Clear(); } } + + private bool TryFastGenerateUrl( + string protocol, + string host, + VirtualPathData pathData, + string fragment, + out string url) + { + var pathBase = HttpContext.Request.PathBase; + url = null; + + if (string.IsNullOrEmpty(protocol) + && string.IsNullOrEmpty(host) + && string.IsNullOrEmpty(fragment) + && !pathBase.HasValue) + { + if (pathData.VirtualPath.Length == 0) + { + url = "/"; + return true; + } + else if (pathData.VirtualPath.StartsWith("/", StringComparison.Ordinal)) + { + url = pathData.VirtualPath; + return true; + } + } + + return false; + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs index aba3837fd5..22ce84c8c4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs @@ -939,6 +939,38 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.Equal(expected, builder.ToString()); } + [Theory] + [InlineData(null, null, null, "/", null, "/")] + [InlineData(null, null, null, "/", null, "/")] + [InlineData(null, null, null, "/Hello", null, "/Hello" )] + [InlineData(null, null, null, "Hello", null, "/Hello")] + [InlineData(null, null, null, "/Hello", null, "/Hello")] + [InlineData("/", null, null, "", null, "/")] + [InlineData("/hello/", null, null, "/world", null, "/hello/world")] + [InlineData("/hello/", "https", "myhost", "/world", "fragment-value", "https://myhost/hello/world#fragment-value")] + public void GenerateUrl_FastAndSlowPathsReturnsExpected( + string appBase, + string protocol, + string host, + string virtualPath, + string fragment, + string expected) + { + // Arrage + var router = Mock.Of(); + var pathData = new VirtualPathData(router, virtualPath) + { + VirtualPath = virtualPath + }; + var urlHelper = CreateUrlHelper(appBase, router); + + // Act + var url = urlHelper.GenerateUrl(protocol, host, pathData, fragment); + + // Assert + Assert.Equal(expected, url); + } + private static HttpContext CreateHttpContext( IServiceProvider services, string appRoot) @@ -1002,13 +1034,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing return new UrlHelper(actionContext); } - private static UrlHelper CreateUrlHelper(string appBase, IRouter router) + private static TestUrlHelper CreateUrlHelper(string appBase, IRouter router) { var services = CreateServices(); var context = CreateHttpContext(services, appBase); var actionContext = CreateActionContext(context, router); - return new UrlHelper(actionContext); + return new TestUrlHelper(actionContext); } private static UrlHelper CreateUrlHelperWithRouteCollection( @@ -1095,5 +1127,22 @@ namespace Microsoft.AspNetCore.Mvc.Routing return Task.FromResult(false); } } + + private class TestUrlHelper : UrlHelper + { + public TestUrlHelper(ActionContext actionContext) : + base(actionContext) + { + + } + public new string GenerateUrl(string protocol, string host, VirtualPathData pathData, string fragment) + { + return base.GenerateUrl( + protocol, + host, + pathData, + fragment); + } + } } } \ No newline at end of file