diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs index ff839a1556..d60a3d6974 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Text; using Microsoft.AspNetCore.Http; @@ -18,6 +19,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Perf: Share the StringBuilder object across multiple calls of GenerateURL for this UrlHelper private StringBuilder _stringBuilder; + // Perf: Reuse the RouteValueDictionary across multiple calls of Action for this UrlHelper + private readonly RouteValueDictionary _routeValueDictionary; /// /// Initializes a new instance of the class using the specified action context and @@ -32,6 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } ActionContext = actionContext; + _routeValueDictionary = new RouteValueDictionary(); } /// @@ -51,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing throw new ArgumentNullException(nameof(actionContext)); } - var valuesDictionary = new RouteValueDictionary(actionContext.Values); + var valuesDictionary = GetValuesDictionary(actionContext.Values); if (actionContext.Action == null) { @@ -106,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing throw new ArgumentNullException(nameof(routeContext)); } - var valuesDictionary = new RouteValueDictionary(routeContext.Values); + var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values); var virtualPathData = GetVirtualPathData(routeContext.RouteName, valuesDictionary); return GenerateUrl(routeContext.Protocol, routeContext.Host, virtualPathData, routeContext.Fragment); } @@ -205,6 +209,37 @@ namespace Microsoft.AspNetCore.Mvc.Routing }); } + private RouteValueDictionary GetValuesDictionary(object values) + { + // Perf: RouteValueDictionary can be cast to IDictionary, but it is + // special cased to avoid allocating boxed Enumerator. + var routeValuesDictionary = values as RouteValueDictionary; + if (routeValuesDictionary != null) + { + _routeValueDictionary.Clear(); + foreach (var kvp in routeValuesDictionary) + { + _routeValueDictionary.Add(kvp.Key, kvp.Value); + } + + return _routeValueDictionary; + } + + var dictionaryValues = values as IDictionary; + if (dictionaryValues != null) + { + _routeValueDictionary.Clear(); + foreach (var kvp in dictionaryValues) + { + _routeValueDictionary.Add(kvp.Key, kvp.Value); + } + + return _routeValueDictionary; + } + + return new RouteValueDictionary(values); + } + private StringBuilder GetStringBuilder() { if(_stringBuilder == null)