[Perf] Using single UrlHelper per HttpContext and one StringBuilder per UrlHelper to reduce allocations
This commit is contained in:
parent
8746fae12e
commit
d537ec06cc
|
|
@ -15,6 +15,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UrlHelper : IUrlHelper
|
public class UrlHelper : IUrlHelper
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Perf: Share the StringBuilder object across multiple calls of GenerateURL for this UrlHelper
|
||||||
|
private StringBuilder _stringBuilder;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UrlHelper"/> class using the specified action context and
|
/// Initializes a new instance of the <see cref="UrlHelper"/> class using the specified action context and
|
||||||
/// action selector.
|
/// action selector.
|
||||||
|
|
@ -201,6 +205,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private StringBuilder GetStringBuilder()
|
||||||
|
{
|
||||||
|
if(_stringBuilder == null)
|
||||||
|
{
|
||||||
|
_stringBuilder = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _stringBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates the URL using the specified components.
|
/// Generates the URL using the specified components.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -219,29 +233,38 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
// VirtualPathData.VirtualPath returns string.Empty instead of null.
|
// VirtualPathData.VirtualPath returns string.Empty instead of null.
|
||||||
Debug.Assert(pathData.VirtualPath != null);
|
Debug.Assert(pathData.VirtualPath != null);
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = GetStringBuilder();
|
||||||
if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host))
|
try
|
||||||
{
|
{
|
||||||
AppendPathAndFragment(builder, pathData, fragment);
|
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 (builder.Length == 0 || builder[0] != '/')
|
|
||||||
{
|
{
|
||||||
builder.Insert(0, '/');
|
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] != '/')
|
||||||
|
{
|
||||||
|
builder.Insert(0, '/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol;
|
||||||
|
builder.Append(protocol);
|
||||||
|
|
||||||
|
builder.Append("://");
|
||||||
|
|
||||||
|
host = string.IsNullOrEmpty(host) ? HttpContext.Request.Host.Value : host;
|
||||||
|
builder.Append(host);
|
||||||
|
AppendPathAndFragment(builder, pathData, fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = builder.ToString();
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol;
|
// Clear the StringBuilder so that it can reused for the next call.
|
||||||
builder.Append(protocol);
|
builder.Clear();
|
||||||
|
|
||||||
builder.Append("://");
|
|
||||||
|
|
||||||
host = string.IsNullOrEmpty(host) ? HttpContext.Request.Host.Value : host;
|
|
||||||
builder.Append(host);
|
|
||||||
AppendPathAndFragment(builder, pathData, fragment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -11,7 +15,31 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IUrlHelper GetUrlHelper(ActionContext context)
|
public IUrlHelper GetUrlHelper(ActionContext context)
|
||||||
{
|
{
|
||||||
return new UrlHelper(context);
|
var httpContext = context.HttpContext;
|
||||||
|
|
||||||
|
if (httpContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
|
||||||
|
nameof(ActionContext.HttpContext),
|
||||||
|
nameof(ActionContext)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpContext.Items == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
|
||||||
|
nameof(HttpContext.Items),
|
||||||
|
nameof(HttpContext)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perf: Create only one UrlHelper per context
|
||||||
|
var urlHelper = httpContext.Items[typeof(IUrlHelper)] as IUrlHelper;
|
||||||
|
if (urlHelper == null)
|
||||||
|
{
|
||||||
|
urlHelper = new UrlHelper(context);
|
||||||
|
httpContext.Items[typeof(IUrlHelper)] = urlHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlHelper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
|
@ -120,6 +121,8 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
.Returns(response);
|
.Returns(response);
|
||||||
httpContext.SetupGet(o => o.RequestServices)
|
httpContext.SetupGet(o => o.RequestServices)
|
||||||
.Returns(serviceProvider);
|
.Returns(serviceProvider);
|
||||||
|
httpContext.SetupGet(o => o.Items)
|
||||||
|
.Returns(new ItemsDictionary());
|
||||||
httpContext.Setup(o => o.Request.PathBase)
|
httpContext.Setup(o => o.Request.PathBase)
|
||||||
.Returns(new PathString(appRoot));
|
.Returns(new PathString(appRoot));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
|
@ -128,6 +129,8 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
.Returns(response);
|
.Returns(response);
|
||||||
httpContext.SetupGet(o => o.RequestServices)
|
httpContext.SetupGet(o => o.RequestServices)
|
||||||
.Returns(serviceProvider);
|
.Returns(serviceProvider);
|
||||||
|
httpContext.SetupGet(o => o.Items)
|
||||||
|
.Returns(new ItemsDictionary());
|
||||||
httpContext.Setup(o => o.Request.PathBase)
|
httpContext.Setup(o => o.Request.PathBase)
|
||||||
.Returns(new PathString(appRoot));
|
.Returns(new PathString(appRoot));
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue