Remove the use of `Linq` and unneeded enumerators from Mvc `TagHelper`s.
- Prior to this change we were using `Linq` throughout our `TagHelper`s which resulted excess allocations. - Also went through our data structures and changed some from `Enumerable<T>` to `IList<T>` where we were just not exposing the more accessible types (list). - Changed several locations of `foreach` to `for` to remove the enumerator allocation. #3599
This commit is contained in:
parent
a14fdd8637
commit
490fcf1ab4
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
|
@ -32,6 +31,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
private const string RouteValuesDictionaryName = "asp-all-route-data";
|
||||
private const string RouteValuesPrefix = "asp-route-";
|
||||
private const string Href = "href";
|
||||
private IDictionary<string, string> _routeValues;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="AnchorTagHelper"/>.
|
||||
|
|
@ -98,8 +98,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// Additional parameters for the route.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)]
|
||||
public IDictionary<string, string> RouteValues { get; set; } =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
public IDictionary<string, string> RouteValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_routeValues == null)
|
||||
{
|
||||
_routeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return _routeValues;
|
||||
}
|
||||
set
|
||||
{
|
||||
_routeValues = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Does nothing if user provides an <c>href</c> attribute.</remarks>
|
||||
|
|
@ -148,11 +162,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
else
|
||||
{
|
||||
// Convert from Dictionary<string, string> to Dictionary<string, object>.
|
||||
var routeValues = RouteValues.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => (object)kvp.Value,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
IDictionary<string, object> routeValues = null;
|
||||
if (_routeValues != null && _routeValues.Count > 0)
|
||||
{
|
||||
// Convert from Dictionary<string, string> to Dictionary<string, object>.
|
||||
routeValues = new Dictionary<string, object>(_routeValues.Count, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var routeValue in _routeValues)
|
||||
{
|
||||
routeValues.Add(routeValue.Key, routeValue.Value);
|
||||
}
|
||||
}
|
||||
|
||||
TagBuilder tagBuilder;
|
||||
if (Route == null)
|
||||
|
|
@ -180,13 +199,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
else
|
||||
{
|
||||
tagBuilder = Generator.GenerateRouteLink(linkText: string.Empty,
|
||||
routeName: Route,
|
||||
protocol: Protocol,
|
||||
hostName: Host,
|
||||
fragment: Fragment,
|
||||
routeValues: routeValues,
|
||||
htmlAttributes: null);
|
||||
tagBuilder = Generator.GenerateRouteLink(
|
||||
linkText: string.Empty,
|
||||
routeName: Route,
|
||||
protocol: Protocol,
|
||||
hostName: Host,
|
||||
fragment: Fragment,
|
||||
routeValues: routeValues,
|
||||
htmlAttributes: null);
|
||||
}
|
||||
|
||||
if (tagBuilder != null)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
|
@ -101,7 +99,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines if the cached result is to be varied by the Identity for the logged in
|
||||
/// <see cref="HttpContext.User"/>.
|
||||
/// <see cref="Http.HttpContext.User"/>.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(VaryByUserAttributeName)]
|
||||
public bool VaryByUser { get; set; }
|
||||
|
|
@ -261,18 +259,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Append(keyName)
|
||||
.Append("(");
|
||||
|
||||
var tokenFound = false;
|
||||
foreach (var item in Tokenize(value))
|
||||
{
|
||||
tokenFound = true;
|
||||
var values = Tokenize(value);
|
||||
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
var item = values[i];
|
||||
builder.Append(item)
|
||||
.Append(CacheKeyTokenSeparator)
|
||||
.Append(sourceCollection[item])
|
||||
.Append(CacheKeyTokenSeparator);
|
||||
}
|
||||
|
||||
if (tokenFound)
|
||||
if (values.Count > 0)
|
||||
{
|
||||
// Remove the trailing separator
|
||||
builder.Length -= CacheKeyTokenSeparator.Length;
|
||||
|
|
@ -282,12 +281,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
}
|
||||
|
||||
private static void AddStringCollectionKey<T>(
|
||||
private static void AddStringCollectionKey<TSourceCollection>(
|
||||
StringBuilder builder,
|
||||
string keyName,
|
||||
string value,
|
||||
T sourceCollection,
|
||||
Func<T, string, StringValues> accessor)
|
||||
TSourceCollection sourceCollection,
|
||||
Func<TSourceCollection, string, StringValues> accessor)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
|
|
@ -296,10 +295,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Append(keyName)
|
||||
.Append("(");
|
||||
|
||||
var tokenFound = false;
|
||||
foreach (var item in Tokenize(value))
|
||||
var values = Tokenize(value);
|
||||
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
tokenFound = true;
|
||||
var item = values[i];
|
||||
|
||||
builder.Append(item)
|
||||
.Append(CacheKeyTokenSeparator)
|
||||
|
|
@ -307,7 +308,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Append(CacheKeyTokenSeparator);
|
||||
}
|
||||
|
||||
if (tokenFound)
|
||||
if (values.Count > 0)
|
||||
{
|
||||
// Remove the trailing separator
|
||||
builder.Length -= CacheKeyTokenSeparator.Length;
|
||||
|
|
@ -327,8 +328,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Append(nameof(VaryByRoute))
|
||||
.Append("(");
|
||||
|
||||
foreach (var route in Tokenize(VaryByRoute))
|
||||
var varyByRoutes = Tokenize(VaryByRoute);
|
||||
for (var i = 0; i < varyByRoutes.Count; i++)
|
||||
{
|
||||
var route = varyByRoutes[i];
|
||||
tokenFound = true;
|
||||
|
||||
builder.Append(route)
|
||||
|
|
@ -346,11 +349,27 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> Tokenize(string value)
|
||||
private static IList<string> Tokenize(string value)
|
||||
{
|
||||
return value.Split(AttributeSeparator, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(token => token.Trim())
|
||||
.Where(token => token.Length > 0);
|
||||
var values = value.Split(AttributeSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (values.Length == 0)
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
var trimmedValues = new List<string>();
|
||||
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var trimmedValue = values[i].Trim();
|
||||
|
||||
if (trimmedValue.Length > 0)
|
||||
{
|
||||
trimmedValues.Add(trimmedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return trimmedValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
|
|
@ -67,30 +67,41 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
return;
|
||||
}
|
||||
|
||||
var environments = Names.Split(NameSeparator, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(name => !string.IsNullOrWhiteSpace(name));
|
||||
|
||||
if (!environments.Any())
|
||||
{
|
||||
// Names contains only commas or empty entries, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
var currentEnvironmentName = HostingEnvironment.EnvironmentName?.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(currentEnvironmentName))
|
||||
if (string.IsNullOrEmpty(currentEnvironmentName))
|
||||
{
|
||||
// No current environment name, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
if (environments.Any(name =>
|
||||
string.Equals(name.Trim(), currentEnvironmentName, StringComparison.OrdinalIgnoreCase)))
|
||||
var values = Names.Split(NameSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||
var environments = new List<string>();
|
||||
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
// Matching environment name found, do nothing
|
||||
var trimmedValue = values[i].Trim();
|
||||
|
||||
if (trimmedValue.Length > 0)
|
||||
{
|
||||
environments.Add(trimmedValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (environments.Count == 0)
|
||||
{
|
||||
// Names contains only commas or empty entries, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < environments.Count; i++)
|
||||
{
|
||||
if (string.Equals(environments[i], currentEnvironmentName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Matching environment name found, do nothing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No matching environment name found, suppress all output
|
||||
output.SuppressOutput();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
|
@ -28,6 +27,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
private const string RouteValuesDictionaryName = "asp-all-route-data";
|
||||
private const string RouteValuesPrefix = "asp-route-";
|
||||
private const string HtmlActionAttributeName = "action";
|
||||
private IDictionary<string, string> _routeValues;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="FormTagHelper"/>.
|
||||
|
|
@ -85,8 +85,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// Additional parameters for the route.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)]
|
||||
public IDictionary<string, string> RouteValues { get; set; } =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
public IDictionary<string, string> RouteValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_routeValues == null)
|
||||
{
|
||||
_routeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return _routeValues;
|
||||
}
|
||||
set
|
||||
{
|
||||
_routeValues = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
|
|
@ -133,11 +147,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
else
|
||||
{
|
||||
// Convert from Dictionary<string, string> to Dictionary<string, object>.
|
||||
var routeValues = RouteValues.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => (object)kvp.Value,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
IDictionary<string, object> routeValues = null;
|
||||
if (_routeValues != null && _routeValues.Count > 0)
|
||||
{
|
||||
// Convert from Dictionary<string, string> to Dictionary<string, object>.
|
||||
routeValues = new Dictionary<string, object>(_routeValues.Count, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var routeValue in _routeValues)
|
||||
{
|
||||
routeValues.Add(routeValue.Key, routeValue.Value);
|
||||
}
|
||||
}
|
||||
|
||||
TagBuilder tagBuilder;
|
||||
if (Route == null)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
|
|
@ -247,9 +246,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
// Prepare to move attributes from current element to <input type="checkbox"/> generated just below.
|
||||
var htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Perf: Avoid allocating enumerator
|
||||
// Construct attributes correctly (first attribute wins).
|
||||
foreach (var attribute in output.Attributes)
|
||||
for (var i = 0; i < output.Attributes.Count; i++)
|
||||
{
|
||||
var attribute = output.Attributes[i];
|
||||
if (!htmlAttributes.ContainsKey(attribute.Name))
|
||||
{
|
||||
htmlAttributes.Add(attribute.Name, attribute.Value);
|
||||
|
|
@ -389,15 +390,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
// A variant of TemplateRenderer.GetViewNames(). Main change relates to bool? handling.
|
||||
private static IEnumerable<string> GetInputTypeHints(ModelExplorer modelExplorer)
|
||||
{
|
||||
var inputTypeHints = new string[]
|
||||
if (!string.IsNullOrEmpty(modelExplorer.Metadata.TemplateHint))
|
||||
{
|
||||
modelExplorer.Metadata.TemplateHint,
|
||||
modelExplorer.Metadata.DataTypeName,
|
||||
};
|
||||
yield return modelExplorer.Metadata.TemplateHint;
|
||||
}
|
||||
|
||||
foreach (string inputTypeHint in inputTypeHints.Where(s => !string.IsNullOrEmpty(s)))
|
||||
if (!string.IsNullOrEmpty(modelExplorer.Metadata.DataTypeName))
|
||||
{
|
||||
yield return inputTypeHint;
|
||||
yield return modelExplorer.Metadata.DataTypeName;
|
||||
}
|
||||
|
||||
// In most cases, we don't want to search for Nullable<T>. We want to search for T, which should handle
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||
|
|
@ -23,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
/// <returns>The <see cref="ModeMatchResult{TMode}"/>.</returns>
|
||||
public static ModeMatchResult<TMode> DetermineMode<TMode>(
|
||||
TagHelperContext context,
|
||||
IEnumerable<ModeAttributes<TMode>> modeInfos)
|
||||
IReadOnlyList<ModeAttributes<TMode>> modeInfos)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
|
|
@ -39,27 +38,32 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
var matchedAttributes = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
var result = new ModeMatchResult<TMode>();
|
||||
|
||||
foreach (var modeInfo in modeInfos)
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < modeInfos.Count; i++)
|
||||
{
|
||||
var modeInfo = modeInfos[i];
|
||||
var modeAttributes = GetPresentMissingAttributes(context, modeInfo.Attributes);
|
||||
|
||||
if (modeAttributes.Present.Any())
|
||||
if (modeAttributes.Present.Count > 0)
|
||||
{
|
||||
if (!modeAttributes.Missing.Any())
|
||||
if (modeAttributes.Missing.Count == 0)
|
||||
{
|
||||
// Perf: Avoid allocating enumerator
|
||||
// A complete match, mark the attribute as fully matched
|
||||
foreach (var attribute in modeAttributes.Present)
|
||||
for (var j = 0; j < modeAttributes.Present.Count; j++)
|
||||
{
|
||||
matchedAttributes[attribute] = true;
|
||||
matchedAttributes[modeAttributes.Present[j]] = true;
|
||||
}
|
||||
|
||||
result.FullMatches.Add(ModeMatchAttributes.Create(modeInfo.Mode, modeInfo.Attributes));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Perf: Avoid allocating enumerator
|
||||
// A partial match, mark the attribute as partially matched if not already fully matched
|
||||
foreach (var attribute in modeAttributes.Present)
|
||||
for (var j = 0; j < modeAttributes.Present.Count; j++)
|
||||
{
|
||||
var attribute = modeAttributes.Present[j];
|
||||
bool attributeMatch;
|
||||
if (!matchedAttributes.TryGetValue(attribute, out attributeMatch))
|
||||
{
|
||||
|
|
@ -87,14 +91,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
|
||||
private static PresentMissingAttributes GetPresentMissingAttributes(
|
||||
TagHelperContext context,
|
||||
IEnumerable<string> requiredAttributes)
|
||||
string[] requiredAttributes)
|
||||
{
|
||||
// Check for all attribute values
|
||||
var presentAttributes = new List<string>();
|
||||
var missingAttributes = new List<string>();
|
||||
|
||||
foreach (var attribute in requiredAttributes)
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < requiredAttributes.Length; i++)
|
||||
{
|
||||
var attribute = requiredAttributes[i];
|
||||
if (!context.AllAttributes.ContainsName(attribute) ||
|
||||
context.AllAttributes[attribute] == null ||
|
||||
(context.AllAttributes[attribute].Value is string &&
|
||||
|
|
@ -114,9 +120,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
|
||||
private class PresentMissingAttributes
|
||||
{
|
||||
public IEnumerable<string> Present { get; set; }
|
||||
public List<string> Present { get; set; }
|
||||
|
||||
public IEnumerable<string> Missing { get; set; }
|
||||
public List<string> Missing { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
/// <param name="includePattern">The file globbing include pattern.</param>
|
||||
/// <param name="excludePattern">The file globbing exclude pattern.</param>
|
||||
/// <returns>The list of URLs</returns>
|
||||
public virtual IEnumerable<string> BuildUrlList(string staticUrl, string includePattern, string excludePattern)
|
||||
public virtual ICollection<string> BuildUrlList(string staticUrl, string includePattern, string excludePattern)
|
||||
{
|
||||
var urls = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
|
|
@ -112,9 +112,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
if (!Cache.TryGetValue(cacheKey, out files))
|
||||
{
|
||||
var options = new MemoryCacheEntryOptions();
|
||||
foreach (var pattern in includePatterns)
|
||||
|
||||
for (var i = 0; i < includePatterns.Length; i++)
|
||||
{
|
||||
var changeToken = FileProvider.Watch(pattern);
|
||||
var changeToken = FileProvider.Watch(includePatterns[i]);
|
||||
options.AddExpirationToken(changeToken);
|
||||
}
|
||||
|
||||
|
|
@ -128,15 +129,24 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
return FindFiles(includePatterns, excludePatterns);
|
||||
}
|
||||
|
||||
private IEnumerable<string> FindFiles(IEnumerable<string> includePatterns, IEnumerable<string> excludePatterns)
|
||||
private IEnumerable<string> FindFiles(string[] includePatterns, string[] excludePatterns)
|
||||
{
|
||||
var matcher = MatcherBuilder != null ? MatcherBuilder() : new Matcher();
|
||||
|
||||
matcher.AddIncludePatterns(includePatterns.Select(pattern => TrimLeadingTildeSlash(pattern)));
|
||||
var trimmedIncludePatterns = new List<string>();
|
||||
for (var i = 0; i < includePatterns.Length; i++)
|
||||
{
|
||||
trimmedIncludePatterns.Add(TrimLeadingTildeSlash(includePatterns[i]));
|
||||
}
|
||||
matcher.AddIncludePatterns(trimmedIncludePatterns);
|
||||
|
||||
if (excludePatterns != null)
|
||||
{
|
||||
matcher.AddExcludePatterns(excludePatterns.Select(pattern => TrimLeadingTildeSlash(pattern)));
|
||||
var trimmedExcludePatterns = new List<string>();
|
||||
for (var i = 0; i < excludePatterns.Length; i++)
|
||||
{
|
||||
trimmedExcludePatterns.Add(TrimLeadingTildeSlash(excludePatterns[i]));
|
||||
}
|
||||
matcher.AddExcludePatterns(trimmedExcludePatterns);
|
||||
}
|
||||
|
||||
var matches = matcher.Execute(_baseGlobbingDirectory);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
/// <summary>
|
||||
/// Encodes a .NET string array for safe use as a JavaScript array literal, including inline in an HTML file.
|
||||
/// </summary>
|
||||
public static string Encode(JavaScriptEncoder encoder, IEnumerable<string> values)
|
||||
public static string Encode(JavaScriptEncoder encoder, string[] values)
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
|
||||
|
|
@ -23,14 +23,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
|
||||
writer.Write('[');
|
||||
|
||||
foreach (var value in values)
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (firstAdded)
|
||||
{
|
||||
writer.Write(',');
|
||||
}
|
||||
writer.Write('"');
|
||||
encoder.Encode(writer, value);
|
||||
encoder.Encode(writer, values[i]);
|
||||
writer.Write('"');
|
||||
firstAdded = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
/// <summary>
|
||||
/// Creates an <see cref="ModeAttributes{TMode}"/>/
|
||||
/// </summary>
|
||||
public static ModeAttributes<TMode> Create<TMode>(TMode mode, IEnumerable<string> attributes)
|
||||
public static ModeAttributes<TMode> Create<TMode>(TMode mode, string[] attributes)
|
||||
{
|
||||
return new ModeAttributes<TMode>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,25 +1,22 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapping of a <see cref="ITagHelper"/> mode to its required attributes.
|
||||
/// A mapping of a <see cref="AspNet.Razor.TagHelpers.ITagHelper"/> mode to its required attributes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMode">The type representing the <see cref="ITagHelper"/>'s mode.</typeparam>
|
||||
/// <typeparam name="TMode">The type representing the <see cref="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.</typeparam>
|
||||
public class ModeAttributes<TMode>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ITagHelper"/>'s mode.
|
||||
/// The <see cref="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.
|
||||
/// </summary>
|
||||
public TMode Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The names of attributes required for this mode.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Attributes { get; set; }
|
||||
public string[] Attributes { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
/// </summary>
|
||||
public static ModeMatchAttributes<TMode> Create<TMode>(
|
||||
TMode mode,
|
||||
IEnumerable<string> presentAttributes)
|
||||
IList<string> presentAttributes)
|
||||
{
|
||||
return Create(mode, presentAttributes, missingAttributes: null);
|
||||
}
|
||||
|
|
@ -25,8 +25,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
/// </summary>
|
||||
public static ModeMatchAttributes<TMode> Create<TMode>(
|
||||
TMode mode,
|
||||
IEnumerable<string> presentAttributes,
|
||||
IEnumerable<string> missingAttributes)
|
||||
IList<string> presentAttributes,
|
||||
IList<string> missingAttributes)
|
||||
{
|
||||
return new ModeMatchAttributes<TMode>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,29 +2,28 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapping of a <see cref="ITagHelper"/> mode to its missing and present attributes.
|
||||
/// A mapping of a <see cref="AspNet.Razor.TagHelpers.ITagHelper"/> mode to its missing and present attributes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMode">The type representing the <see cref="ITagHelper"/>'s mode.</typeparam>
|
||||
/// <typeparam name="TMode">The type representing the <see cref="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.</typeparam>
|
||||
public class ModeMatchAttributes<TMode>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ITagHelper"/>'s mode.
|
||||
/// The <see cref="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.
|
||||
/// </summary>
|
||||
public TMode Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The names of attributes that were present in this match.
|
||||
/// </summary>
|
||||
public IEnumerable<string> PresentAttributes { get; set; }
|
||||
public IList<string> PresentAttributes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The names of attributes that were missing in this match.
|
||||
/// </summary>
|
||||
public IEnumerable<string> MissingAttributes { get; set; }
|
||||
public IList<string> MissingAttributes { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,14 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Logging;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Logging;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of determining the mode an <see cref="ITagHelper"/> will run in.
|
||||
/// Result of determining the mode an <see cref="AspNet.Razor.TagHelpers.ITagHelper"/> will run in.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMode">The type representing the <see cref="ITagHelper"/>'s mode.</typeparam>
|
||||
/// <typeparam name="TMode">The type representing the <see cref="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.</typeparam>
|
||||
public class ModeMatchResult<TMode>
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -32,47 +26,5 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
/// <see cref="FullMatches"/>.
|
||||
/// </summary>
|
||||
public IList<string> PartiallyMatchedAttributes { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Logs the details of the <see cref="ModeMatchResult{TMode}"/>.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
||||
/// <param name="tagHelper">The <see cref="ITagHelper"/>.</param>
|
||||
/// <param name="uniqueId">The value of <see cref="TagHelperContext.UniqueId"/>.</param>
|
||||
/// <param name="viewPath">The path to the view the <see cref="ITagHelper"/> is on.</param>
|
||||
public void LogDetails<TTagHelper>(
|
||||
ILogger logger,
|
||||
TTagHelper tagHelper,
|
||||
string uniqueId,
|
||||
string viewPath)
|
||||
where TTagHelper : ITagHelper
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
if (logger.IsEnabled(LogLevel.Warning) && PartiallyMatchedAttributes.Any())
|
||||
{
|
||||
// Build the list of partial matches that contain attributes not appearing in at least one full match
|
||||
var partialOnlyMatches = PartialMatches.Where(
|
||||
match => match.PresentAttributes.Any(
|
||||
attribute => PartiallyMatchedAttributes.Contains(
|
||||
attribute, StringComparer.OrdinalIgnoreCase)));
|
||||
logger.TagHelperPartialMatches<TMode>(uniqueId, viewPath, partialOnlyMatches);
|
||||
}
|
||||
|
||||
if (logger.IsEnabled(LogLevel.Verbose) && !FullMatches.Any())
|
||||
{
|
||||
logger.TagHelperSkippingProcessing(
|
||||
tagHelper,
|
||||
uniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Microsoft.AspNet.Hosting;
|
|||
using Microsoft.AspNet.Mvc.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Logging;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
|
@ -241,10 +242,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Href = output.Attributes[HrefAttributeName]?.Value as string;
|
||||
|
||||
var modeResult = AttributeMatcher.DetermineMode(context, ModeDetails);
|
||||
Logger.TagHelperModeMatchResult(modeResult, context.UniqueId, ViewContext.View.Path, this);
|
||||
|
||||
modeResult.LogDetails(Logger, this, context.UniqueId, ViewContext.View.Path);
|
||||
|
||||
if (!modeResult.FullMatches.Any())
|
||||
if (modeResult.FullMatches.Count == 0)
|
||||
{
|
||||
// No attributes matched so we have nothing to do
|
||||
return;
|
||||
|
|
@ -266,7 +266,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var builder = new DefaultTagHelperContent();
|
||||
|
||||
// Get the highest matched mode
|
||||
var mode = modeResult.FullMatches.Select(match => match.Mode).Max();
|
||||
var mode = modeResult.FullMatches[0].Mode;
|
||||
for (var i = 1; i < modeResult.FullMatches.Count; i++)
|
||||
{
|
||||
var currentMode = modeResult.FullMatches[i].Mode;
|
||||
if (mode < currentMode)
|
||||
{
|
||||
mode = currentMode;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == Mode.GlobbedHref || mode == Mode.Fallback && !string.IsNullOrEmpty(HrefInclude))
|
||||
{
|
||||
|
|
@ -384,8 +392,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
builder.AppendHtml("<link ");
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
var attributeValue = attribute.Value;
|
||||
if (AppendVersion == true &&
|
||||
string.Equals(attribute.Name, HrefAttributeName, StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
|||
|
|
@ -19,30 +19,54 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Logging
|
|||
"Skipping processing for tag helper '{TagHelper}' with id '{TagHelperId}'.");
|
||||
}
|
||||
|
||||
public static void TagHelperPartialMatches<TMode>(
|
||||
this ILogger logger,
|
||||
string uniqueId,
|
||||
string viewPath,
|
||||
IEnumerable<ModeMatchAttributes<TMode>> partialMatches)
|
||||
public static void TagHelperModeMatchResult<TMode>(
|
||||
this ILogger logger,
|
||||
ModeMatchResult<TMode> modeMatchResult,
|
||||
string uniqueId,
|
||||
string viewPath,
|
||||
ITagHelper tagHelper)
|
||||
{
|
||||
var logValues = new PartialModeMatchLogValues<TMode>(
|
||||
uniqueId,
|
||||
viewPath,
|
||||
partialMatches);
|
||||
if (logger.IsEnabled(LogLevel.Warning) && modeMatchResult.PartiallyMatchedAttributes.Count > 0)
|
||||
{
|
||||
// Build the list of partial matches that contain attributes not appearing in at least one full match
|
||||
var partialOnlyMatches = new List<ModeMatchAttributes<TMode>>();
|
||||
for (var i = 0; i < modeMatchResult.PartialMatches.Count; i++)
|
||||
{
|
||||
var presentAttributes = modeMatchResult.PartialMatches[i].PresentAttributes;
|
||||
for (var j = 0; j < presentAttributes.Count; j++)
|
||||
{
|
||||
var present = presentAttributes[j];
|
||||
var presentIsPartialOnlyMatch = false;
|
||||
for (var k = 0; k < modeMatchResult.PartiallyMatchedAttributes.Count; k++)
|
||||
{
|
||||
var partiallyMatched = modeMatchResult.PartiallyMatchedAttributes[k];
|
||||
if (string.Equals(partiallyMatched, present, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
presentIsPartialOnlyMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogWarning(logValues);
|
||||
}
|
||||
if (presentIsPartialOnlyMatch)
|
||||
{
|
||||
partialOnlyMatches.Add(modeMatchResult.PartialMatches[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void TagHelperSkippingProcessing(
|
||||
this ILogger logger,
|
||||
ITagHelper tagHelper,
|
||||
string uniqueId)
|
||||
{
|
||||
_skippingProcessing(
|
||||
logger,
|
||||
tagHelper,
|
||||
uniqueId,
|
||||
null);
|
||||
var logValues = new PartialModeMatchLogValues<TMode>(
|
||||
uniqueId,
|
||||
viewPath,
|
||||
partialOnlyMatches);
|
||||
|
||||
logger.LogWarning(logValues);
|
||||
}
|
||||
|
||||
if (logger.IsEnabled(LogLevel.Verbose) && modeMatchResult.FullMatches.Count == 0)
|
||||
{
|
||||
_skippingProcessing(logger, tagHelper, uniqueId, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -57,9 +57,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var formContext = ViewContext.FormContext;
|
||||
if (formContext.HasEndOfFormContent)
|
||||
{
|
||||
foreach (var content in formContext.EndOfFormContent)
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < formContext.EndOfFormContent.Count; i++)
|
||||
{
|
||||
output.PostContent.Append(content);
|
||||
output.PostContent.Append(formContext.EndOfFormContent[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Mvc.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Logging;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
|
@ -209,10 +209,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Src = output.Attributes[SrcAttributeName]?.Value as string;
|
||||
|
||||
var modeResult = AttributeMatcher.DetermineMode(context, ModeDetails);
|
||||
Logger.TagHelperModeMatchResult(modeResult, context.UniqueId, ViewContext.View.Path, this);
|
||||
|
||||
modeResult.LogDetails(Logger, this, context.UniqueId, ViewContext.View.Path);
|
||||
|
||||
if (!modeResult.FullMatches.Any())
|
||||
if (modeResult.FullMatches.Count == 0)
|
||||
{
|
||||
// No attributes matched so we have nothing to do
|
||||
return;
|
||||
|
|
@ -234,7 +233,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var builder = new DefaultTagHelperContent();
|
||||
|
||||
// Get the highest matched mode
|
||||
var mode = modeResult.FullMatches.Select(match => match.Mode).Max();
|
||||
var mode = modeResult.FullMatches[0].Mode;
|
||||
for (var i = 1; i < modeResult.FullMatches.Count; i++)
|
||||
{
|
||||
var currentMode = modeResult.FullMatches[i].Mode;
|
||||
if (mode < currentMode)
|
||||
{
|
||||
mode = currentMode;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == Mode.GlobbedSrc || mode == Mode.Fallback && !string.IsNullOrEmpty(SrcInclude))
|
||||
{
|
||||
|
|
@ -290,7 +297,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
EnsureGlobbingUrlBuilder();
|
||||
|
||||
var fallbackSrcs = GlobbingUrlBuilder.BuildUrlList(FallbackSrc, FallbackSrcInclude, FallbackSrcExclude);
|
||||
if (fallbackSrcs.Any())
|
||||
if (fallbackSrcs.Count > 0)
|
||||
{
|
||||
// Build the <script> tag that checks the test method and if it fails, renders the extra script.
|
||||
builder.AppendHtml(Environment.NewLine)
|
||||
|
|
@ -312,8 +319,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
builder.AppendHtml("<script");
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
if (!attribute.Name.Equals(SrcAttributeName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var encodedKey = JavaScriptEncoder.Encode(attribute.Name);
|
||||
|
|
@ -373,8 +382,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
builder.AppendHtml("<script");
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
// Perf: Avoid allocating enumerator
|
||||
for (var i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
var attributeValue = attribute.Value;
|
||||
if (AppendVersion == true &&
|
||||
string.Equals(attribute.Name, SrcAttributeName, StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
|||
|
|
@ -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 Microsoft.AspNet.Mvc.TagHelpers.Logging;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
|
@ -22,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
var viewPath = "Views/Home/Index.cshtml";
|
||||
|
||||
// Act
|
||||
modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath);
|
||||
logger.TagHelperModeMatchResult(modeMatchResult, uniqueId, viewPath, tagHelper.Object);
|
||||
|
||||
// Assert
|
||||
Mock.Get(logger).Verify(l => l.Log(
|
||||
|
|
@ -48,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
var viewPath = "Views/Home/Index.cshtml";
|
||||
|
||||
// Act
|
||||
modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath);
|
||||
logger.TagHelperModeMatchResult(modeMatchResult, uniqueId, viewPath, tagHelper.Object);
|
||||
|
||||
// Assert
|
||||
Mock.Get(logger).Verify(l => l.Log(
|
||||
|
|
@ -79,7 +80,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
var viewPath = "Views/Home/Index.cshtml";
|
||||
|
||||
// Act
|
||||
modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath);
|
||||
logger.TagHelperModeMatchResult(modeMatchResult, uniqueId, viewPath, tagHelper.Object);
|
||||
|
||||
// Assert
|
||||
Mock.Get(logger).Verify(l => l.Log(
|
||||
|
|
@ -110,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|||
var viewPath = "Views/Home/Index.cshtml";
|
||||
|
||||
// Act
|
||||
modeMatchResult.LogDetails(logger, tagHelper.Object, uniqueId, viewPath);
|
||||
logger.TagHelperModeMatchResult(modeMatchResult, uniqueId, viewPath, tagHelper.Object);
|
||||
|
||||
// Assert
|
||||
Mock.Get(logger).Verify(l => l.Log(
|
||||
|
|
|
|||
Loading…
Reference in New Issue