Reduce allocation in URL generation

This change optimizes our a per-operation dictionary that really can just
be cached for the whole app's lifetime.
This commit is contained in:
Ryan Nowak 2015-12-18 09:20:52 -08:00
parent 37c167aa74
commit 813171a016
3 changed files with 37 additions and 42 deletions

View File

@ -14,14 +14,15 @@ namespace Microsoft.AspNet.Routing.Template
{ {
public class TemplateBinder public class TemplateBinder
{ {
private readonly IReadOnlyDictionary<string, object> _defaults; private readonly RouteValueDictionary _defaults;
private readonly RouteValueDictionary _filters;
private readonly RouteTemplate _template; private readonly RouteTemplate _template;
private readonly UrlEncoder _urlEncoder; private readonly UrlEncoder _urlEncoder;
public TemplateBinder( public TemplateBinder(
RouteTemplate template, RouteTemplate template,
UrlEncoder urlEncoder, UrlEncoder urlEncoder,
IReadOnlyDictionary<string, object> defaults) RouteValueDictionary defaults)
{ {
if (template == null) if (template == null)
{ {
@ -36,6 +37,14 @@ namespace Microsoft.AspNet.Routing.Template
_template = template; _template = template;
_urlEncoder = urlEncoder; _urlEncoder = urlEncoder;
_defaults = defaults; _defaults = defaults;
// Any default that doesn't have a corresponding parameter is a 'filter' and if a value
// is provided for that 'filter' it must match the value in defaults.
_filters = new RouteValueDictionary(_defaults);
foreach (var parameter in _template.Parameters)
{
_filters.Remove(parameter.Name);
}
} }
// Step 1: Get the list of values we're going to try to use to match and generate this URI // Step 1: Get the list of values we're going to try to use to match and generate this URI
@ -132,25 +141,22 @@ namespace Microsoft.AspNet.Routing.Template
// Any default values that don't appear as parameters are treated like filters. Any new values // Any default values that don't appear as parameters are treated like filters. Any new values
// provided must match these defaults. // provided must match these defaults.
if (context.Filters != null) foreach (var filter in _filters)
{ {
foreach (var filter in context.Filters) var parameter = GetParameter(filter.Key);
if (parameter != null)
{ {
var parameter = GetParameter(filter.Key); continue;
if (parameter != null) }
{
continue;
}
object value; object value;
if (values.TryGetValue(filter.Key, out value)) if (values.TryGetValue(filter.Key, out value))
{
if (!RoutePartsEqual(value, filter.Value))
{ {
if (!RoutePartsEqual(value, filter.Value)) // If there is a non-parameterized value in the route and there is a
{ // new value for it and it doesn't match, this route won't match.
// If there is a non-parameterized value in the route and there is a return null;
// new value for it and it doesn't match, this route won't match.
return null;
}
} }
} }
} }
@ -346,21 +352,14 @@ namespace Microsoft.AspNet.Routing.Template
[DebuggerDisplay("{DebuggerToString(),nq}")] [DebuggerDisplay("{DebuggerToString(),nq}")]
private class TemplateBindingContext private class TemplateBindingContext
{ {
private readonly IReadOnlyDictionary<string, object> _defaults; private readonly RouteValueDictionary _defaults;
private readonly RouteValueDictionary _acceptedValues; private readonly RouteValueDictionary _acceptedValues;
private readonly RouteValueDictionary _filters;
public TemplateBindingContext(IReadOnlyDictionary<string, object> defaults) public TemplateBindingContext(RouteValueDictionary defaults)
{ {
_defaults = defaults; _defaults = defaults;
_acceptedValues = new RouteValueDictionary(); _acceptedValues = new RouteValueDictionary();
if (_defaults != null)
{
_filters = new RouteValueDictionary(_defaults);
}
} }
public RouteValueDictionary AcceptedValues public RouteValueDictionary AcceptedValues
@ -368,11 +367,6 @@ namespace Microsoft.AspNet.Routing.Template
get { return _acceptedValues; } get { return _acceptedValues; }
} }
public RouteValueDictionary Filters
{
get { return _filters; }
}
public void Accept(string key, object value) public void Accept(string key, object value)
{ {
if (!_acceptedValues.ContainsKey(key)) if (!_acceptedValues.ContainsKey(key))
@ -388,7 +382,6 @@ namespace Microsoft.AspNet.Routing.Template
object value; object value;
if (_defaults != null && _defaults.TryGetValue(key, out value)) if (_defaults != null && _defaults.TryGetValue(key, out value))
{ {
_filters.Remove(key);
_acceptedValues.Add(key, value); _acceptedValues.Add(key, value);
} }
} }
@ -400,10 +393,7 @@ namespace Microsoft.AspNet.Routing.Template
private string DebuggerToString() private string DebuggerToString()
{ {
return string.Format( return string.Format("{{Accepted: '{0}'}}", string.Join(", ", _acceptedValues.Keys));
"{{Accepted: '{0}' Filters: '{1}'}}",
string.Join(", ", _acceptedValues.Keys),
string.Join(", ", _filters?.Keys));
} }
} }

View File

@ -109,7 +109,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
[MemberData(nameof(EmptyAndNullDefaultValues))] [MemberData(nameof(EmptyAndNullDefaultValues))]
public void Binding_WithEmptyAndNull_DefaultValues( public void Binding_WithEmptyAndNull_DefaultValues(
string template, string template,
IReadOnlyDictionary<string, object> defaults, RouteValueDictionary defaults,
RouteValueDictionary values, RouteValueDictionary values,
string expected) string expected)
{ {
@ -255,7 +255,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
[MemberData(nameof(OptionalParamValues))] [MemberData(nameof(OptionalParamValues))]
public void GetVirtualPathWithMultiSegmentWithOptionalParam( public void GetVirtualPathWithMultiSegmentWithOptionalParam(
string template, string template,
IReadOnlyDictionary<string, object> defaults, RouteValueDictionary defaults,
RouteValueDictionary ambientValues, RouteValueDictionary ambientValues,
RouteValueDictionary values, RouteValueDictionary values,
string expected) string expected)
@ -1083,7 +1083,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
private static void RunTest( private static void RunTest(
string template, string template,
IReadOnlyDictionary<string, object> defaults, RouteValueDictionary defaults,
RouteValueDictionary ambientValues, RouteValueDictionary ambientValues,
RouteValueDictionary values, RouteValueDictionary values,
string expected, string expected,

View File

@ -1571,9 +1571,14 @@ namespace Microsoft.AspNet.Routing.Tree
var entry = new TreeRouteLinkGenerationEntry(); var entry = new TreeRouteLinkGenerationEntry();
entry.Template = TemplateParser.Parse(template); entry.Template = TemplateParser.Parse(template);
var defaults = entry.Template.Parameters var defaults = new RouteValueDictionary();
.Where(p => p.DefaultValue != null) foreach (var parameter in entry.Template.Parameters)
.ToDictionary(p => p.Name, p => p.DefaultValue); {
if (parameter.DefaultValue != null)
{
defaults.Add(parameter.Name, parameter.DefaultValue);
}
}
var constraintBuilder = new RouteConstraintBuilder(CreateConstraintResolver(), template); var constraintBuilder = new RouteConstraintBuilder(CreateConstraintResolver(), template);
foreach (var parameter in entry.Template.Parameters) foreach (var parameter in entry.Template.Parameters)