Optimize allocations in Script/Link TagHelpers

Fixes #3618
This commit is contained in:
Pranav K 2016-01-04 17:48:48 -08:00
parent a500a93dfb
commit 9168cd1f37
13 changed files with 125 additions and 736 deletions

View File

@ -13,16 +13,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public static class AttributeMatcher
{
/// <summary>
/// Determines the modes a <see cref="ITagHelper" /> can run in based on which modes have all their required
/// attributes present, non null, non empty, and non whitepsace.
/// Determines the most effective mode a <see cref="ITagHelper" /> can run in based on which modes have
/// all their required attributes present.
/// </summary>
/// <typeparam name="TMode">The type representing the <see cref="ITagHelper" />'s modes.</typeparam>
/// <param name="context">The <see cref="TagHelperContext"/>.</param>
/// <param name="modeInfos">The modes and their required attributes.</param>
/// <returns>The <see cref="ModeMatchResult{TMode}"/>.</returns>
public static ModeMatchResult<TMode> DetermineMode<TMode>(
/// <param name="compare">A comparer delegate.</param>
/// <param name="result">The resulting most effective mode.</param>
/// <returns><c>true</c> if a mode was determined, otherwise <c>false</c>.</returns>
public static bool TryDetermineMode<TMode>(
TagHelperContext context,
IReadOnlyList<ModeAttributes<TMode>> modeInfos)
IReadOnlyList<ModeAttributes<TMode>> modeInfos,
Func<TMode, TMode, int> compare,
out TMode result)
{
if (context == null)
{
@ -34,100 +38,57 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
throw new ArgumentNullException(nameof(modeInfos));
}
// true == full match, false == partial match
var matchedAttributes = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
var result = new ModeMatchResult<TMode>();
if (compare == null)
{
throw new ArgumentNullException(nameof(compare));
}
var foundResult = false;
result = default(TMode);
// 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.Count > 0)
if (!HasMissingAttributes(context, modeInfo.Attributes) &&
compare(result, modeInfo.Mode) <= 0)
{
if (modeAttributes.Missing.Count == 0)
{
// Perf: Avoid allocating enumerator
// A complete match, mark the attribute as fully matched
for (var j = 0; j < modeAttributes.Present.Count; j++)
{
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
for (var j = 0; j < modeAttributes.Present.Count; j++)
{
var attribute = modeAttributes.Present[j];
bool attributeMatch;
if (!matchedAttributes.TryGetValue(attribute, out attributeMatch))
{
matchedAttributes[attribute] = false;
}
}
result.PartialMatches.Add(ModeMatchAttributes.Create(
modeInfo.Mode, modeAttributes.Present, modeAttributes.Missing));
}
foundResult = true;
result = modeInfo.Mode;
}
}
// Build the list of partially matched attributes (those with partial matches but no full matches)
foreach (var attribute in matchedAttributes.Keys)
{
if (!matchedAttributes[attribute])
{
result.PartiallyMatchedAttributes.Add(attribute);
}
}
return result;
return foundResult;
}
private static PresentMissingAttributes GetPresentMissingAttributes(
TagHelperContext context,
string[] requiredAttributes)
private static bool HasMissingAttributes(TagHelperContext context, string[] requiredAttributes)
{
// Check for all attribute values
var presentAttributes = new List<string>();
var missingAttributes = new List<string>();
if (context.AllAttributes.Count < requiredAttributes.Length)
{
// If there are fewer attributes present than required, one or more of them must be missing.
return true;
}
// Check for all attribute values
// Perf: Avoid allocating enumerator
for (var i = 0; i < requiredAttributes.Length; i++)
{
var requiredAttribute = requiredAttributes[i];
IReadOnlyTagHelperAttribute attribute;
if (!context.AllAttributes.TryGetAttribute(requiredAttribute, out attribute))
if (!context.AllAttributes.TryGetAttribute(requiredAttributes[i], out attribute))
{
// Missing attribute.
missingAttributes.Add(requiredAttribute);
continue;
return true;
}
var valueAsString = attribute.Value as string;
if (valueAsString != null && string.IsNullOrEmpty(valueAsString))
{
// Treat attributes with empty values as missing.
missingAttributes.Add(requiredAttribute);
continue;
return true;
}
presentAttributes.Add(requiredAttribute);
}
return new PresentMissingAttributes { Present = presentAttributes, Missing = missingAttributes };
}
private class PresentMissingAttributes
{
public List<string> Present { get; set; }
public List<string> Missing { get; set; }
return false;
}
}
}

View File

@ -1,25 +0,0 @@
// 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;
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
/// <summary>
/// Static creation methods for <see cref="ModeAttributes{TMode}"/>.
/// </summary>
public static class ModeAttributes
{
/// <summary>
/// Creates an <see cref="ModeAttributes{TMode}"/>/
/// </summary>
public static ModeAttributes<TMode> Create<TMode>(TMode mode, string[] attributes)
{
return new ModeAttributes<TMode>
{
Mode = mode,
Attributes = attributes
};
}
}
}

View File

@ -10,13 +10,24 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public class ModeAttributes<TMode>
{
/// <summary>
/// The <see cref="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.
/// Initializes a new instance of <see cref="ModeAttributes{TMode}"/>.
/// </summary>
public TMode Mode { get; set; }
/// <param name="mode">The <see cref="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.</param>
/// <param name="attributes">The names of attributes required for this mode.</param>
public ModeAttributes(TMode mode, string[] attributes)
{
Mode = mode;
Attributes = attributes;
}
/// <summary>
/// The names of attributes required for this mode.
/// Gets the <see cref="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.
/// </summary>
public string[] Attributes { get; set; }
public TMode Mode { get; }
/// <summary>
/// Gets the names of attributes required for this mode.
/// </summary>
public string[] Attributes { get; }
}
}

View File

@ -1,39 +0,0 @@
// 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;
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
/// <summary>
/// Static creation methods for <see cref="ModeMatchAttributes{TMode}"/>.
/// </summary>
public static class ModeMatchAttributes
{
/// <summary>
/// Creates an <see cref="ModeMatchAttributes{TMode}"/>.
/// </summary>
public static ModeMatchAttributes<TMode> Create<TMode>(
TMode mode,
IList<string> presentAttributes)
{
return Create(mode, presentAttributes, missingAttributes: null);
}
/// <summary>
/// Creates an <see cref="ModeMatchAttributes{TMode}"/>.
/// </summary>
public static ModeMatchAttributes<TMode> Create<TMode>(
TMode mode,
IList<string> presentAttributes,
IList<string> missingAttributes)
{
return new ModeMatchAttributes<TMode>
{
Mode = mode,
PresentAttributes = presentAttributes,
MissingAttributes = missingAttributes
};
}
}
}

View File

@ -1,29 +0,0 @@
// 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;
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
/// <summary>
/// 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="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.</typeparam>
public class ModeMatchAttributes<TMode>
{
/// <summary>
/// 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 IList<string> PresentAttributes { get; set; }
/// <summary>
/// The names of attributes that were missing in this match.
/// </summary>
public IList<string> MissingAttributes { get; set; }
}
}

View File

@ -1,30 +0,0 @@
// 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;
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
/// <summary>
/// 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="AspNet.Razor.TagHelpers.ITagHelper"/>'s mode.</typeparam>
public class ModeMatchResult<TMode>
{
/// <summary>
/// Modes that were missing attributes but had at least one attribute present.
/// </summary>
public IList<ModeMatchAttributes<TMode>> PartialMatches { get; } = new List<ModeMatchAttributes<TMode>>();
/// <summary>
/// Modes that had all attributes present.
/// </summary>
public IList<ModeMatchAttributes<TMode>> FullMatches { get; } = new List<ModeMatchAttributes<TMode>>();
/// <summary>
/// Attributes that are present in at least one mode in <see cref="PartialMatches"/>, but in no modes in
/// <see cref="FullMatches"/>.
/// </summary>
public IList<string> PartiallyMatchedAttributes { get; } = new List<string>();
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.Encodings.Web;
using Microsoft.AspNet.Hosting;
@ -11,10 +10,8 @@ using Microsoft.AspNet.Mvc.Razor.TagHelpers;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
using Microsoft.AspNet.Mvc.TagHelpers.Logging;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
@ -35,6 +32,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[HtmlTargetElement("link", Attributes = AppendVersionAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class LinkTagHelper : UrlResolutionTagHelper
{
private static readonly string FallbackJavaScriptResourceName =
typeof(LinkTagHelper).Namespace + ".compiler.resources.LinkTagHelper_FallbackJavaScript.js";
@ -48,19 +46,21 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
private const string FallbackTestValueAttributeName = "asp-fallback-test-value";
private const string AppendVersionAttributeName = "asp-append-version";
private const string HrefAttributeName = "href";
private static readonly Func<Mode, Mode, int> Compare = (a, b) => a - b;
private FileVersionProvider _fileVersionProvider;
private static readonly ModeAttributes<Mode>[] ModeDetails = new[] {
// Regular src with file version alone
ModeAttributes.Create(Mode.AppendVersion, new[] { AppendVersionAttributeName }),
new ModeAttributes<Mode>(Mode.AppendVersion, new[] { AppendVersionAttributeName }),
// Globbed Href (include only) no static href
ModeAttributes.Create(Mode.GlobbedHref, new [] { HrefIncludeAttributeName }),
new ModeAttributes<Mode>(Mode.GlobbedHref, new [] { HrefIncludeAttributeName }),
// Globbed Href (include & exclude), no static href
ModeAttributes.Create(Mode.GlobbedHref, new [] { HrefIncludeAttributeName, HrefExcludeAttributeName }),
new ModeAttributes<Mode>(Mode.GlobbedHref, new [] { HrefIncludeAttributeName, HrefExcludeAttributeName }),
// Fallback with static href
ModeAttributes.Create(
Mode.Fallback, new[]
new ModeAttributes<Mode>(
Mode.Fallback,
new[]
{
FallbackHrefAttributeName,
FallbackTestClassAttributeName,
@ -68,16 +68,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
FallbackTestValueAttributeName
}),
// Fallback with globbed href (include only)
ModeAttributes.Create(
Mode.Fallback, new[] {
new ModeAttributes<Mode>(
Mode.Fallback,
new[]
{
FallbackHrefIncludeAttributeName,
FallbackTestClassAttributeName,
FallbackTestPropertyAttributeName,
FallbackTestValueAttributeName
}),
// Fallback with globbed href (include & exclude)
ModeAttributes.Create(
Mode.Fallback, new[] {
new ModeAttributes<Mode>(
Mode.Fallback,
new[]
{
FallbackHrefIncludeAttributeName,
FallbackHrefExcludeAttributeName,
FallbackTestClassAttributeName,
@ -89,14 +93,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary>
/// Creates a new <see cref="LinkTagHelper"/>.
/// </summary>
/// <param name="logger">The <see cref="ILogger{ScriptTagHelper}"/>.</param>
/// <param name="hostingEnvironment">The <see cref="IHostingEnvironment"/>.</param>
/// <param name="cache">The <see cref="IMemoryCache"/>.</param>
/// <param name="htmlEncoder">The <see cref="HtmlEncoder"/>.</param>
/// <param name="javaScriptEncoder">The <see cref="JavaScriptEncoder"/>.</param>
/// <param name="urlHelperFactory">The <see cref="IUrlHelperFactory"/>.</param>
public LinkTagHelper(
ILogger<LinkTagHelper> logger,
IHostingEnvironment hostingEnvironment,
IMemoryCache cache,
HtmlEncoder htmlEncoder,
@ -104,7 +106,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
IUrlHelperFactory urlHelperFactory)
: base(urlHelperFactory, htmlEncoder)
{
Logger = logger;
HostingEnvironment = hostingEnvironment;
Cache = cache;
JavaScriptEncoder = javaScriptEncoder;
@ -199,8 +200,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[HtmlAttributeName(FallbackTestValueAttributeName)]
public string FallbackTestValue { get; set; }
protected ILogger<LinkTagHelper> Logger { get; }
protected IHostingEnvironment HostingEnvironment { get; }
protected IMemoryCache Cache { get; }
@ -237,10 +236,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// not function properly.
Href = output.Attributes[HrefAttributeName]?.Value as string;
var modeResult = AttributeMatcher.DetermineMode(context, ModeDetails);
Logger.TagHelperModeMatchResult(modeResult, context.UniqueId, ViewContext.View.Path, this);
if (modeResult.FullMatches.Count == 0)
Mode mode;
if (!AttributeMatcher.TryDetermineMode(context, ModeDetails, Compare, out mode))
{
// No attributes matched so we have nothing to do
return;
@ -260,18 +257,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
var builder = new DefaultTagHelperContent();
// Get the highest matched mode
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))
{
BuildGlobbedLinkTags(attributes, builder);

View File

@ -1,127 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNet.Mvc.TagHelpers.Logging
{
internal static class ModeMatchResultLoggerExtensions
{
private static readonly Action<ILogger, ITagHelper, string, Exception> _skippingProcessing;
static ModeMatchResultLoggerExtensions()
{
_skippingProcessing = LoggerMessage.Define<ITagHelper, string>(
LogLevel.Debug,
1,
"Skipping processing for tag helper '{TagHelper}' with id '{TagHelperId}'.");
}
public static void TagHelperModeMatchResult<TMode>(
this ILogger logger,
ModeMatchResult<TMode> modeMatchResult,
string uniqueId,
string viewPath,
ITagHelper tagHelper)
{
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;
}
}
if (presentIsPartialOnlyMatch)
{
partialOnlyMatches.Add(modeMatchResult.PartialMatches[i]);
break;
}
}
}
var logValues = new PartialModeMatchLogValues<TMode>(
uniqueId,
viewPath,
partialOnlyMatches);
logger.LogWarning(logValues);
}
if (logger.IsEnabled(LogLevel.Debug) && modeMatchResult.FullMatches.Count == 0)
{
_skippingProcessing(logger, tagHelper, uniqueId, null);
}
}
/// <summary>
/// Log values for <see cref="AspNet.Razor.TagHelpers.ITagHelper"/> instances that opt out
/// of processing due to missing attributes for one of several possible modes.
/// </summary>
private class PartialModeMatchLogValues<TMode> : ILogValues
{
private readonly IEnumerable<ModeMatchAttributes<TMode>> _partialMatches;
private readonly string _uniqueId;
private readonly string _viewPath;
/// <summary>
/// Creates a new <see cref="PartialModeMatchLogValues{TMode}"/>.
/// </summary>
/// <param name="uniqueId">
/// The unique ID of the HTML element this message applies to.
/// </param>
/// <param name="viewPath">The path to the view.</param>
/// <param name="partialMatches">The set of modes with partial required attributes.</param>
public PartialModeMatchLogValues(
string uniqueId,
string viewPath,
IEnumerable<ModeMatchAttributes<TMode>> partialMatches)
{
if (partialMatches == null)
{
throw new ArgumentNullException(nameof(partialMatches));
}
_uniqueId = uniqueId;
_viewPath = viewPath;
_partialMatches = partialMatches;
}
public IEnumerable<KeyValuePair<string, object>> GetValues()
{
yield return new KeyValuePair<string, object>(
"Message",
"Tag helper had partial matches while determining mode.");
yield return new KeyValuePair<string, object>("UniqueId", _uniqueId);
yield return new KeyValuePair<string, object>("ViewPath", _viewPath);
yield return new KeyValuePair<string, object>("PartialMatches", _partialMatches);
}
public override string ToString()
{
var newLine = Environment.NewLine;
return string.Format(
$"Tag Helper with ID '{_uniqueId}' in view '{_viewPath}' had partial matches " +
$"while determining mode:{newLine}\t{{0}}",
string.Join($"{newLine}\t", _partialMatches.Select(partial =>
string.Format($"Mode '{partial.Mode}' missing attributes:{newLine}\t\t{{0}} ",
string.Join($"{newLine}\t\t", partial.MissingAttributes)))));
}
}
}
}

View File

@ -9,11 +9,8 @@ using Microsoft.AspNet.Mvc.Razor.TagHelpers;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.Routing;
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;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
@ -40,32 +37,36 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
private const string FallbackTestExpressionAttributeName = "asp-fallback-test";
private const string SrcAttributeName = "src";
private const string AppendVersionAttributeName = "asp-append-version";
private static readonly Func<Mode, Mode, int> Compare = (a, b) => a - b;
private FileVersionProvider _fileVersionProvider;
private static readonly ModeAttributes<Mode>[] ModeDetails = new[] {
// Regular src with file version alone
ModeAttributes.Create(Mode.AppendVersion, new[] { AppendVersionAttributeName }),
new ModeAttributes<Mode>(Mode.AppendVersion, new[] { AppendVersionAttributeName }),
// Globbed src (include only)
ModeAttributes.Create(Mode.GlobbedSrc, new [] { SrcIncludeAttributeName }),
new ModeAttributes<Mode>(Mode.GlobbedSrc, new [] { SrcIncludeAttributeName }),
// Globbed src (include & exclude)
ModeAttributes.Create(Mode.GlobbedSrc, new [] { SrcIncludeAttributeName, SrcExcludeAttributeName }),
new ModeAttributes<Mode>(Mode.GlobbedSrc, new [] { SrcIncludeAttributeName, SrcExcludeAttributeName }),
// Fallback with static src
ModeAttributes.Create(
Mode.Fallback, new[]
new ModeAttributes<Mode>(Mode.Fallback,
new[]
{
FallbackSrcAttributeName,
FallbackTestExpressionAttributeName
}),
// Fallback with globbed src (include only)
ModeAttributes.Create(
Mode.Fallback, new[] {
new ModeAttributes<Mode>(
Mode.Fallback,
new[]
{
FallbackSrcIncludeAttributeName,
FallbackTestExpressionAttributeName
}),
// Fallback with globbed src (include & exclude)
ModeAttributes.Create(
Mode.Fallback, new[] {
new ModeAttributes<Mode>(
Mode.Fallback,
new[]
{
FallbackSrcIncludeAttributeName,
FallbackSrcExcludeAttributeName,
FallbackTestExpressionAttributeName
@ -75,14 +76,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary>
/// Creates a new <see cref="ScriptTagHelper"/>.
/// </summary>
/// <param name="logger">The <see cref="ILogger{ScriptTagHelper}"/>.</param>
/// <param name="hostingEnvironment">The <see cref="IHostingEnvironment"/>.</param>
/// <param name="cache">The <see cref="IMemoryCache"/>.</param>
/// <param name="htmlEncoder">The <see cref="HtmlEncoder"/>.</param>
/// <param name="javaScriptEncoder">The <see cref="JavaScriptEncoder"/>.</param>
/// <param name="urlHelperFactory">The <see cref="IUrlHelperFactory"/>.</param>
public ScriptTagHelper(
ILogger<ScriptTagHelper> logger,
IHostingEnvironment hostingEnvironment,
IMemoryCache cache,
HtmlEncoder htmlEncoder,
@ -90,7 +89,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
IUrlHelperFactory urlHelperFactory)
: base(urlHelperFactory, htmlEncoder)
{
Logger = logger;
HostingEnvironment = hostingEnvironment;
Cache = cache;
JavaScriptEncoder = javaScriptEncoder;
@ -167,8 +165,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[HtmlAttributeName(FallbackTestExpressionAttributeName)]
public string FallbackTestExpression { get; set; }
protected ILogger<ScriptTagHelper> Logger { get; }
protected IHostingEnvironment HostingEnvironment { get; }
protected IMemoryCache Cache { get; }
@ -205,10 +201,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// not function properly.
Src = output.Attributes[SrcAttributeName]?.Value as string;
var modeResult = AttributeMatcher.DetermineMode(context, ModeDetails);
Logger.TagHelperModeMatchResult(modeResult, context.UniqueId, ViewContext.View.Path, this);
if (modeResult.FullMatches.Count == 0)
Mode mode;
if (!AttributeMatcher.TryDetermineMode(context, ModeDetails, Compare, out mode))
{
// No attributes matched so we have nothing to do
return;
@ -229,17 +223,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var builder = new DefaultTagHelperContent();
// Get the highest matched mode
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))
{
BuildGlobbedScriptTags(attributes, builder);

View File

@ -10,13 +10,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
public class AttributeMatcherTest
{
[Fact]
public void DetermineMode_FindsFullModeMatchWithSingleAttribute()
private static readonly Func<Mode, Mode, int> Compare = (a, b) => a - b;
[Theory]
[InlineData(new object[] { new[] { "required-attr" } })]
[InlineData(new object[] { new[] { "first-attr", "second-attr" } })]
public void TryDetermineMode_ReturnsFalseIfNoAttributeMatchesAllRequiredAttributes(string[] modeAttributes)
{
// Arrange
var modeInfo = new[]
var modeInfos = new[]
{
ModeAttributes.Create("mode0", new [] { "first-attr" })
new ModeAttributes<Mode>(Mode.A, modeAttributes)
};
var attributes = new TagHelperAttributeList
{
@ -26,25 +30,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var context = MakeTagHelperContext(attributes);
// Act
var modeMatch = AttributeMatcher.DetermineMode(context, modeInfo);
Mode result;
var modeMatch = AttributeMatcher.TryDetermineMode(context, modeInfos, Compare, out result);
// Assert
Assert.Collection(modeMatch.FullMatches, match =>
{
Assert.Equal("mode0", match.Mode);
Assert.Collection(match.PresentAttributes, attribute => Assert.Equal("first-attr", attribute));
});
Assert.Empty(modeMatch.PartialMatches);
Assert.Empty(modeMatch.PartiallyMatchedAttributes);
Assert.False(modeMatch);
}
[Fact]
public void DetermineMode_FindsFullModeMatchWithMultipleAttributes()
public void DetermineMode_SetsModeIfAllAttributesMatch()
{
// Arrange
var modeInfo = new[]
var modeInfos = new[]
{
ModeAttributes.Create("mode0", new [] { "first-attr", "second-attr" })
new ModeAttributes<Mode>(Mode.A, new[] { "a-required-attributes" }),
new ModeAttributes<Mode>(Mode.B, new[] { "first-attr", "second-attr" }),
new ModeAttributes<Mode>(Mode.C, new[] { "first-attr", "third-attr" }),
};
var attributes = new TagHelperAttributeList
{
@ -55,34 +56,28 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var context = MakeTagHelperContext(attributes);
// Act
var modeMatch = AttributeMatcher.DetermineMode(context, modeInfo);
Mode result;
var modeMatch = AttributeMatcher.TryDetermineMode(context, modeInfos, Compare, out result);
// Assert
Assert.Collection(modeMatch.FullMatches, match =>
{
Assert.Equal("mode0", match.Mode);
Assert.Collection(match.PresentAttributes,
attribute => Assert.Equal("first-attr", attribute),
attribute => Assert.Equal("second-attr", attribute)
);
});
Assert.Empty(modeMatch.PartialMatches);
Assert.Empty(modeMatch.PartiallyMatchedAttributes);
Assert.True(modeMatch);
Assert.Equal(Mode.B, result);
}
[Fact]
public void DetermineMode_FindsFullAndPartialModeMatchWithMultipleAttribute()
public void DetermineMode_SetsModeWithHigestValue()
{
// Arrange
var modeInfo = new[]
var modeInfos = new[]
{
ModeAttributes.Create("mode0", new [] { "second-attr" }),
ModeAttributes.Create("mode1", new [] { "first-attr", "third-attr" }),
ModeAttributes.Create("mode2", new [] { "first-attr", "second-attr", "third-attr" }),
ModeAttributes.Create("mode3", new [] { "fourth-attr" })
new ModeAttributes<Mode>(Mode.A, new[] { "first-attr" }),
new ModeAttributes<Mode>(Mode.B, new[] { "first-attr", "second-attr" }),
new ModeAttributes<Mode>(Mode.D, new[] { "second-attr", "third-attr" }),
new ModeAttributes<Mode>(Mode.C, new[] { "first-attr", "second-attr", "third-attr" }),
};
var attributes = new TagHelperAttributeList
{
["first-attr"] = "value",
["second-attr"] = "value",
["third-attr"] = "value",
["not-in-any-mode"] = "value"
@ -90,43 +85,28 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var context = MakeTagHelperContext(attributes);
// Act
var modeMatch = AttributeMatcher.DetermineMode(context, modeInfo);
Mode result;
var modeMatch = AttributeMatcher.TryDetermineMode(context, modeInfos, Compare, out result);
// Assert
Assert.Collection(modeMatch.FullMatches, match =>
{
Assert.Equal("mode0", match.Mode);
Assert.Collection(match.PresentAttributes, attribute => Assert.Equal("second-attr", attribute));
});
Assert.Collection(modeMatch.PartialMatches,
match =>
{
Assert.Equal("mode1", match.Mode);
Assert.Collection(match.PresentAttributes, attribute => Assert.Equal("third-attr", attribute));
Assert.Collection(match.MissingAttributes, attribute => Assert.Equal("first-attr", attribute));
},
match =>
{
Assert.Equal("mode2", match.Mode);
Assert.Collection(match.PresentAttributes,
attribute => Assert.Equal("second-attr", attribute),
attribute => Assert.Equal("third-attr", attribute)
);
Assert.Collection(match.MissingAttributes, attribute => Assert.Equal("first-attr", attribute));
});
Assert.Collection(modeMatch.PartiallyMatchedAttributes, attribute => Assert.Equal("third-attr", attribute));
Assert.True(modeMatch);
Assert.Equal(Mode.D, result);
}
private static TagHelperContext MakeTagHelperContext(
TagHelperAttributeList attributes = null,
string content = null)
private static TagHelperContext MakeTagHelperContext(TagHelperAttributeList attributes)
{
attributes = attributes ?? new TagHelperAttributeList();
return new TagHelperContext(
attributes,
items: new Dictionary<object, object>(),
uniqueId: Guid.NewGuid().ToString("N"));
}
private enum Mode
{
A = 0,
B = 1,
C = 3,
D = 4
};
}
}

View File

@ -1,139 +0,0 @@
// 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.AspNet.Mvc.TagHelpers.Logging;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
public class ModeMatchResultTest
{
[Fact]
public void LogDetails_LogsDebugWhenNoFullMatchesFound()
{
// Arrange
var modeMatchResult = new ModeMatchResult<string>();
var logger = MakeLogger(LogLevel.Debug);
var tagHelper = new Mock<ITagHelper>();
var uniqueId = "id";
var viewPath = "Views/Home/Index.cshtml";
// Act
logger.TagHelperModeMatchResult(modeMatchResult, uniqueId, viewPath, tagHelper.Object);
// Assert
Mock.Get(logger).Verify(l => l.Log(
LogLevel.Debug,
It.IsAny<int>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()), Times.Once);
}
[Fact]
public void LogDetails_DoesNotLogWhenPartialMatchFoundButNoPartiallyMatchedAttributesFound()
{
// Arrange
var modeMatchResult = new ModeMatchResult<string>();
modeMatchResult.FullMatches.Add(
ModeMatchAttributes.Create("mode0", new[] { "first-attr" }));
modeMatchResult.PartialMatches.Add(
ModeMatchAttributes.Create("mode1", new[] { "first-attr" }, new[] { "second-attr" }));
var logger = MakeLogger(LogLevel.Debug);
var tagHelper = new Mock<ITagHelper>();
var uniqueId = "id";
var viewPath = "Views/Home/Index.cshtml";
// Act
logger.TagHelperModeMatchResult(modeMatchResult, uniqueId, viewPath, tagHelper.Object);
// Assert
Mock.Get(logger).Verify(l => l.Log(
LogLevel.Warning,
It.IsAny<int>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()), Times.Never);
Mock.Get(logger).Verify(l => l.Log(
LogLevel.Debug,
It.IsAny<int>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()), Times.Never);
}
[Fact]
public void LogDetails_LogsWhenPartiallyMatchedAttributesFound()
{
// Arrange
var modeMatchResult = new ModeMatchResult<string>();
modeMatchResult.PartialMatches.Add(
ModeMatchAttributes.Create("mode0", new[] { "first-attr" }, new[] { "second-attr" }));
modeMatchResult.PartiallyMatchedAttributes.Add("first-attr");
var logger = MakeLogger(LogLevel.Debug);
var tagHelper = new Mock<ITagHelper>();
var uniqueId = "id";
var viewPath = "Views/Home/Index.cshtml";
// Act
logger.TagHelperModeMatchResult(modeMatchResult, uniqueId, viewPath, tagHelper.Object);
// Assert
Mock.Get(logger).Verify(l => l.Log(
LogLevel.Warning,
It.IsAny<int>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()), Times.Once);
Mock.Get(logger).Verify(l => l.Log(
LogLevel.Debug,
It.IsAny<int>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()), Times.Once);
}
[Fact]
public void LogDetails_DoesNotLogWhenLoggingLevelIsSetAboveWarning()
{
// Arrange
var modeMatchResult = new ModeMatchResult<string>();
modeMatchResult.PartialMatches.Add(
ModeMatchAttributes.Create("mode0", new[] { "first-attr" }, new[] { "second-attr" }));
modeMatchResult.PartiallyMatchedAttributes.Add("first-attr");
var logger = MakeLogger(LogLevel.Critical);
var tagHelper = new Mock<ITagHelper>();
var uniqueId = "id";
var viewPath = "Views/Home/Index.cshtml";
// Act
logger.TagHelperModeMatchResult(modeMatchResult, uniqueId, viewPath, tagHelper.Object);
// Assert
Mock.Get(logger).Verify(l => l.Log(
LogLevel.Warning,
It.IsAny<int>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()), Times.Never);
Mock.Get(logger).Verify(l => l.Log(
LogLevel.Debug,
It.IsAny<int>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()), Times.Never);
}
private static ILogger MakeLogger(LogLevel level)
{
var logger = new Mock<ILogger>();
logger.Setup(l => l.IsEnabled(It.IsAny<LogLevel>())).Returns<LogLevel>(l => l >= level);
return logger.Object;
}
}
}

View File

@ -13,19 +13,18 @@ using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.Extensions.Primitives;
using Microsoft.Extensions.WebEncoders.Testing;
using Moq;
using Xunit;
using Microsoft.AspNet.Mvc.Routing;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
@ -55,7 +54,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "href", hrefOutput },
};
var output = MakeTagHelperOutput("link", outputAttributes);
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var urlHelper = new Mock<IUrlHelper>();
@ -71,7 +69,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(urlHelper.Object);
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -165,12 +162,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
new TagHelperAttribute("rel", new HtmlString("stylesheet"))
}));
var output = MakeTagHelperOutput("link", combinedOutputAttributes);
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -282,7 +277,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange
var context = MakeTagHelperContext(attributes);
var output = MakeTagHelperOutput("link");
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -290,7 +284,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/common.css" });
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -378,7 +371,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange
var context = MakeTagHelperContext(attributes);
var output = MakeTagHelperOutput("link");
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -386,7 +378,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/common.css" });
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -428,12 +419,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "rel", new HtmlString("stylesheet") },
{ "data-extra", new HtmlString("something") },
});
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -538,12 +527,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange
var context = MakeTagHelperContext(attributes);
var output = MakeTagHelperOutput("link");
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -570,12 +557,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange
var context = MakeTagHelperContext();
var output = MakeTagHelperOutput("link");
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -610,7 +595,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "rel", new HtmlString("stylesheet") },
});
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -618,7 +602,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/base.css" });
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -655,7 +638,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
["rel"] = "stylesheet",
});
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -663,7 +645,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/base.css" });
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -701,12 +682,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "rel", new HtmlString("stylesheet") },
});
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -741,12 +720,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "rel", new HtmlString("stylesheet") },
});
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext("/bar");
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -782,7 +759,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "rel", new HtmlString("stylesheet") },
});
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -790,7 +766,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/base.css" });
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),

View File

@ -13,19 +13,19 @@ using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.Extensions.Primitives;
using Microsoft.Extensions.WebEncoders.Testing;
using Moq;
using Xunit;
using Microsoft.AspNet.Mvc.Routing;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
@ -55,7 +55,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "src", srcOutput },
};
var output = MakeTagHelperOutput("script", outputAttributes);
var logger = new Mock<ILogger<ScriptTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var urlHelper = new Mock<IUrlHelper>();
@ -71,7 +70,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(urlHelper.Object);
var helper = new ScriptTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -119,7 +117,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var hostingEnvironment = MakeHostingEnvironment();
var helper = new ScriptTagHelper(
CreateLogger(),
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -273,7 +270,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange
var context = MakeTagHelperContext(attributes);
var output = MakeTagHelperOutput("script");
var logger = CreateLogger();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -281,7 +277,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
CreateLogger(),
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -300,7 +295,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.NotNull(output.TagName);
Assert.False(output.IsContentModified);
Assert.True(output.PostElement.IsModified);
Assert.Empty(logger.Logged);
}
public static TheoryData RunsWhenRequiredAttributesArePresent_NoSrc_Data
@ -370,7 +364,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange
var context = MakeTagHelperContext(attributes);
var output = MakeTagHelperOutput("script");
var logger = CreateLogger();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -378,7 +371,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
CreateLogger(),
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -397,7 +389,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Null(output.TagName);
Assert.True(output.IsContentModified);
Assert.True(output.PostElement.IsModified);
Assert.Empty(logger.Logged);
}
public static TheoryData DoesNotRunWhenARequiredAttributeIsMissing_Data
@ -474,7 +465,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var viewContext = MakeViewContext();
var helper = new ScriptTagHelper(
CreateLogger(),
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -495,54 +485,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.True(output.PostElement.IsEmpty);
}
[Theory]
[MemberData(nameof(DoesNotRunWhenARequiredAttributeIsMissing_Data))]
public async Task LogsWhenARequiredAttributeIsMissing(
TagHelperAttributeList attributes,
Action<ScriptTagHelper> setProperties)
{
// Arrange
var tagHelperContext = MakeTagHelperContext(attributes);
var output = MakeTagHelperOutput("script");
var logger = CreateLogger();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new ScriptTagHelper(
logger,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
new JavaScriptTestEncoder(),
MakeUrlHelperFactory())
{
ViewContext = viewContext,
};
setProperties(helper);
// Act
await helper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal("script", output.TagName);
Assert.False(output.IsContentModified);
Assert.Empty(output.Attributes);
Assert.True(output.PostElement.IsEmpty);
Assert.Equal(2, logger.Logged.Count);
Assert.Equal(LogLevel.Warning, logger.Logged[0].LogLevel);
Assert.IsAssignableFrom<ILogValues>(logger.Logged[0].State);
var loggerData0 = (ILogValues)logger.Logged[0].State;
Assert.Equal(LogLevel.Debug, logger.Logged[1].LogLevel);
Assert.IsAssignableFrom<ILogValues>(logger.Logged[1].State);
Assert.StartsWith("Skipping processing for tag helper 'Microsoft.AspNet.Mvc.TagHelpers.ScriptTagHelper'" +
" with id",
((ILogValues)logger.Logged[1].State).ToString());
}
[Fact]
public async Task DoesNotRunWhenAllRequiredAttributesAreMissing()
{
@ -550,10 +492,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var tagHelperContext = MakeTagHelperContext();
var viewContext = MakeViewContext();
var output = MakeTagHelperOutput("script");
var logger = CreateLogger();
var helper = new ScriptTagHelper(
CreateLogger(),
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
@ -573,41 +513,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.True(output.PostElement.IsEmpty);
}
[Fact]
public async Task LogsWhenAllRequiredAttributesAreMissing()
{
// Arrange
var tagHelperContext = MakeTagHelperContext();
var viewContext = MakeViewContext();
var output = MakeTagHelperOutput("script");
var logger = CreateLogger();
var helper = new ScriptTagHelper(
logger,
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
new JavaScriptTestEncoder(),
MakeUrlHelperFactory())
{
ViewContext = viewContext,
};
// Act
await helper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal("script", output.TagName);
Assert.False(output.IsContentModified);
Assert.Single(logger.Logged);
Assert.Equal(LogLevel.Debug, logger.Logged[0].LogLevel);
Assert.IsAssignableFrom<ILogValues>(logger.Logged[0].State);
Assert.StartsWith("Skipping processing for tag helper 'Microsoft.AspNet.Mvc.TagHelpers.ScriptTagHelper'",
((ILogValues)logger.Logged[0].State).ToString());
}
[Fact]
public async Task PreservesOrderOfNonSrcAttributes()
{
@ -631,11 +536,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
["data-more"] = "else",
});
var logger = CreateLogger();
var hostingEnvironment = MakeHostingEnvironment();
var helper = new ScriptTagHelper(
logger,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -655,7 +558,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal("data-extra", output.Attributes[0].Name);
Assert.Equal("src", output.Attributes[1].Name);
Assert.Equal("data-more", output.Attributes[2].Name);
Assert.Empty(logger.Logged);
}
[Fact]
@ -669,7 +571,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
["asp-src-include"] = "**/*.js"
});
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var logger = new Mock<ILogger<ScriptTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -677,7 +578,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -710,7 +610,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
["asp-src-include"] = "**/*.js"
});
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var logger = new Mock<ILogger<ScriptTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -718,7 +617,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new HtmlTestEncoder(),
@ -752,12 +650,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
});
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var logger = new Mock<ILogger<ScriptTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new ScriptTagHelper(
logger.Object,
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
@ -788,13 +684,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
["asp-append-version"] = "true"
});
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var logger = new Mock<ILogger<ScriptTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext("/bar");
var helper = new ScriptTagHelper(
logger.Object,
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
@ -827,13 +720,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
["asp-append-version"] = "true"
});
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var logger = new Mock<ILogger<ScriptTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var helper = new ScriptTagHelper(
logger.Object,
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
@ -870,7 +760,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
["asp-append-version"] = "true"
});
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var logger = new Mock<ILogger<ScriptTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
@ -878,7 +767,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
logger.Object,
MakeHostingEnvironment(),
MakeCache(),
new HtmlTestEncoder(),
@ -945,11 +833,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
getChildContentAsync: (_) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
}
private TagHelperLogger<ScriptTagHelper> CreateLogger()
{
return new TagHelperLogger<ScriptTagHelper>();
}
private static IHostingEnvironment MakeHostingEnvironment()
{
var emptyDirectoryContents = new Mock<IDirectoryContents>();