[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>
|
||||
public class UrlHelper : IUrlHelper
|
||||
{
|
||||
|
||||
// Perf: Share the StringBuilder object across multiple calls of GenerateURL for this UrlHelper
|
||||
private StringBuilder _stringBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UrlHelper"/> class using the specified action context and
|
||||
/// action selector.
|
||||
|
|
@ -201,6 +205,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
});
|
||||
}
|
||||
|
||||
private StringBuilder GetStringBuilder()
|
||||
{
|
||||
if(_stringBuilder == null)
|
||||
{
|
||||
_stringBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
return _stringBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the URL using the specified components.
|
||||
/// </summary>
|
||||
|
|
@ -219,29 +233,38 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
// VirtualPathData.VirtualPath returns string.Empty instead of null.
|
||||
Debug.Assert(pathData.VirtualPath != null);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host))
|
||||
var builder = GetStringBuilder();
|
||||
try
|
||||
{
|
||||
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] != '/')
|
||||
if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host))
|
||||
{
|
||||
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;
|
||||
builder.Append(protocol);
|
||||
|
||||
builder.Append("://");
|
||||
|
||||
host = string.IsNullOrEmpty(host) ? HttpContext.Request.Host.Value : host;
|
||||
builder.Append(host);
|
||||
AppendPathAndFragment(builder, pathData, fragment);
|
||||
// Clear the StringBuilder so that it can reused for the next call.
|
||||
builder.Clear();
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
// 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.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -11,7 +15,31 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
/// <inheritdoc />
|
||||
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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
|
@ -120,6 +121,8 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
.Returns(response);
|
||||
httpContext.SetupGet(o => o.RequestServices)
|
||||
.Returns(serviceProvider);
|
||||
httpContext.SetupGet(o => o.Items)
|
||||
.Returns(new ItemsDictionary());
|
||||
httpContext.Setup(o => o.Request.PathBase)
|
||||
.Returns(new PathString(appRoot));
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
|
@ -128,6 +129,8 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
.Returns(response);
|
||||
httpContext.SetupGet(o => o.RequestServices)
|
||||
.Returns(serviceProvider);
|
||||
httpContext.SetupGet(o => o.Items)
|
||||
.Returns(new ItemsDictionary());
|
||||
httpContext.Setup(o => o.Request.PathBase)
|
||||
.Returns(new PathString(appRoot));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue