diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs index 65534a2a13..83a4a3c3a4 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs @@ -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 _routeValues; /// /// Creates a new . @@ -98,8 +98,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// Additional parameters for the route. /// [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] - public IDictionary RouteValues { get; set; } = - new Dictionary(StringComparer.OrdinalIgnoreCase); + public IDictionary RouteValues + { + get + { + if (_routeValues == null) + { + _routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _routeValues; + } + set + { + _routeValues = value; + } + } /// /// Does nothing if user provides an href attribute. @@ -148,11 +162,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } else { - // Convert from Dictionary to Dictionary. - var routeValues = RouteValues.ToDictionary( - kvp => kvp.Key, - kvp => (object)kvp.Value, - StringComparer.OrdinalIgnoreCase); + IDictionary routeValues = null; + if (_routeValues != null && _routeValues.Count > 0) + { + // Convert from Dictionary to Dictionary. + routeValues = new Dictionary(_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) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs index 6a194bc9fe..c692ed32bf 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs @@ -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 /// /// Gets or sets a value that determines if the cached result is to be varied by the Identity for the logged in - /// . + /// . /// [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( + private static void AddStringCollectionKey( StringBuilder builder, string keyName, string value, - T sourceCollection, - Func accessor) + TSourceCollection sourceCollection, + Func 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 Tokenize(string value) + private static IList 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(); + + for (var i = 0; i < values.Length; i++) + { + var trimmedValue = values[i].Trim(); + + if (trimmedValue.Length > 0) + { + trimmedValues.Add(trimmedValue); + } + } + + return trimmedValues; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/EnvironmentTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/EnvironmentTagHelper.cs index 3b15c4b686..80b3250107 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/EnvironmentTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/EnvironmentTagHelper.cs @@ -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(); + + 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(); } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs index 6142500b5d..5809222c88 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs @@ -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 _routeValues; /// /// Creates a new . @@ -85,8 +85,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// Additional parameters for the route. /// [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] - public IDictionary RouteValues { get; set; } = - new Dictionary(StringComparer.OrdinalIgnoreCase); + public IDictionary RouteValues + { + get + { + if (_routeValues == null) + { + _routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _routeValues; + } + set + { + _routeValues = value; + } + } /// /// @@ -133,11 +147,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } else { - // Convert from Dictionary to Dictionary. - var routeValues = RouteValues.ToDictionary( - kvp => kvp.Key, - kvp => (object)kvp.Value, - StringComparer.OrdinalIgnoreCase); + IDictionary routeValues = null; + if (_routeValues != null && _routeValues.Count > 0) + { + // Convert from Dictionary to Dictionary. + routeValues = new Dictionary(_routeValues.Count, StringComparer.OrdinalIgnoreCase); + foreach (var routeValue in _routeValues) + { + routeValues.Add(routeValue.Key, routeValue.Value); + } + } TagBuilder tagBuilder; if (Route == null) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs index c428ab4fb0..0ed5c44bd7 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs @@ -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 generated just below. var htmlAttributes = new Dictionary(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 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. We want to search for T, which should handle diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs index d049e5b81d..60469457f7 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/AttributeMatcher.cs @@ -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 /// The . public static ModeMatchResult DetermineMode( TagHelperContext context, - IEnumerable> modeInfos) + IReadOnlyList> modeInfos) { if (context == null) { @@ -39,27 +38,32 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal var matchedAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); var result = new ModeMatchResult(); - 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 requiredAttributes) + string[] requiredAttributes) { // Check for all attribute values var presentAttributes = new List(); var missingAttributes = new List(); - 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 Present { get; set; } + public List Present { get; set; } - public IEnumerable Missing { get; set; } + public List Missing { get; set; } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs index b2586c1332..d3fc0f8091 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs @@ -73,7 +73,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal /// The file globbing include pattern. /// The file globbing exclude pattern. /// The list of URLs - public virtual IEnumerable BuildUrlList(string staticUrl, string includePattern, string excludePattern) + public virtual ICollection BuildUrlList(string staticUrl, string includePattern, string excludePattern) { var urls = new HashSet(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 FindFiles(IEnumerable includePatterns, IEnumerable excludePatterns) + private IEnumerable FindFiles(string[] includePatterns, string[] excludePatterns) { var matcher = MatcherBuilder != null ? MatcherBuilder() : new Matcher(); - - matcher.AddIncludePatterns(includePatterns.Select(pattern => TrimLeadingTildeSlash(pattern))); + var trimmedIncludePatterns = new List(); + 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(); + for (var i = 0; i < excludePatterns.Length; i++) + { + trimmedExcludePatterns.Add(TrimLeadingTildeSlash(excludePatterns[i])); + } + matcher.AddExcludePatterns(trimmedExcludePatterns); } var matches = matcher.Execute(_baseGlobbingDirectory); diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/JavaScriptStringArrayEncoder.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/JavaScriptStringArrayEncoder.cs index fcc3a0940c..104c7c7ef8 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/JavaScriptStringArrayEncoder.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/JavaScriptStringArrayEncoder.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal /// /// Encodes a .NET string array for safe use as a JavaScript array literal, including inline in an HTML file. /// - public static string Encode(JavaScriptEncoder encoder, IEnumerable 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; } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeAttributes.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeAttributes.cs index 09496a0829..b2020cea37 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeAttributes.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeAttributes.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal /// /// Creates an / /// - public static ModeAttributes Create(TMode mode, IEnumerable attributes) + public static ModeAttributes Create(TMode mode, string[] attributes) { return new ModeAttributes { diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeAttributesOfT.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeAttributesOfT.cs index 022d30721d..997bf87926 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeAttributesOfT.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeAttributesOfT.cs @@ -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 { /// - /// A mapping of a mode to its required attributes. + /// A mapping of a mode to its required attributes. /// - /// The type representing the 's mode. + /// The type representing the 's mode. public class ModeAttributes { /// - /// The 's mode. + /// The 's mode. /// public TMode Mode { get; set; } /// /// The names of attributes required for this mode. /// - public IEnumerable Attributes { get; set; } + public string[] Attributes { get; set; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchAttributes.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchAttributes.cs index 168d451cb6..b10f3c6fa0 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchAttributes.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchAttributes.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal /// public static ModeMatchAttributes Create( TMode mode, - IEnumerable presentAttributes) + IList presentAttributes) { return Create(mode, presentAttributes, missingAttributes: null); } @@ -25,8 +25,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal /// public static ModeMatchAttributes Create( TMode mode, - IEnumerable presentAttributes, - IEnumerable missingAttributes) + IList presentAttributes, + IList missingAttributes) { return new ModeMatchAttributes { diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchAttributesOfT.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchAttributesOfT.cs index 38924c1f6e..6238181b4b 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchAttributesOfT.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchAttributesOfT.cs @@ -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 { /// - /// A mapping of a mode to its missing and present attributes. + /// A mapping of a mode to its missing and present attributes. /// - /// The type representing the 's mode. + /// The type representing the 's mode. public class ModeMatchAttributes { /// - /// The 's mode. + /// The 's mode. /// public TMode Mode { get; set; } /// /// The names of attributes that were present in this match. /// - public IEnumerable PresentAttributes { get; set; } + public IList PresentAttributes { get; set; } /// /// The names of attributes that were missing in this match. /// - public IEnumerable MissingAttributes { get; set; } + public IList MissingAttributes { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchResult.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchResult.cs index c70f5d1443..94528fb3cb 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchResult.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/ModeMatchResult.cs @@ -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 { /// - /// Result of determining the mode an will run in. + /// Result of determining the mode an will run in. /// - /// The type representing the 's mode. + /// The type representing the 's mode. public class ModeMatchResult { /// @@ -32,47 +26,5 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal /// . /// public IList PartiallyMatchedAttributes { get; } = new List(); - - /// - /// Logs the details of the . - /// - /// The . - /// The . - /// The value of . - /// The path to the view the is on. - public void LogDetails( - 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(uniqueId, viewPath, partialOnlyMatches); - } - - if (logger.IsEnabled(LogLevel.Verbose) && !FullMatches.Any()) - { - logger.TagHelperSkippingProcessing( - tagHelper, - uniqueId); - } - } } } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs index 7a47f26338..6ba1d316c4 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/LinkTagHelper.cs @@ -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("( - this ILogger logger, - string uniqueId, - string viewPath, - IEnumerable> partialMatches) + public static void TagHelperModeMatchResult( + this ILogger logger, + ModeMatchResult modeMatchResult, + string uniqueId, + string viewPath, + ITagHelper tagHelper) { - var logValues = new PartialModeMatchLogValues( - 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>(); + 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( + uniqueId, + viewPath, + partialOnlyMatches); + + logger.LogWarning(logValues); + } + + if (logger.IsEnabled(LogLevel.Verbose) && modeMatchResult.FullMatches.Count == 0) + { + _skippingProcessing(logger, tagHelper, uniqueId, null); + } } /// diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs index 07697deb1d..6e7a5650fb 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs @@ -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]); } } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs index 50ffeb72ec..303b1548ab 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ScriptTagHelper.cs @@ -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