[Perf] Using single UrlHelper per HttpContext and one StringBuilder per UrlHelper to reduce allocations

This commit is contained in:
mnltejaswini 2016-02-24 16:43:13 -08:00
parent 8746fae12e
commit d537ec06cc
4 changed files with 75 additions and 18 deletions

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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));

View File

@ -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));