Reduce allocations in GlobbingUrlBuilder
This commit is contained in:
parent
e078259547
commit
200fb23ba5
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNet.FileProviders;
|
using Microsoft.AspNet.FileProviders;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.FileSystemGlobbing;
|
using Microsoft.Extensions.FileSystemGlobbing;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -17,18 +18,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GlobbingUrlBuilder
|
public class GlobbingUrlBuilder
|
||||||
{
|
{
|
||||||
private static readonly char[] PatternSeparator = new[] { ',' };
|
private static readonly IReadOnlyList<string> EmptyList =
|
||||||
|
#if NET451
|
||||||
|
new string[0];
|
||||||
|
#else
|
||||||
|
Array.Empty<string>();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Valid whitespace characters defined by the HTML5 spec.
|
// Valid whitespace characters defined by the HTML5 spec.
|
||||||
private static readonly char[] ValidAttributeWhitespaceChars =
|
private static readonly char[] ValidAttributeWhitespaceChars =
|
||||||
new[] { '\t', '\n', '\u000C', '\r', ' ' };
|
new[] { '\t', '\n', '\u000C', '\r', ' ' };
|
||||||
|
|
||||||
private static readonly PathComparer DefaultPathComparer = new PathComparer();
|
private static readonly PathComparer DefaultPathComparer = new PathComparer();
|
||||||
|
|
||||||
private readonly FileProviderGlobbingDirectory _baseGlobbingDirectory;
|
private readonly FileProviderGlobbingDirectory _baseGlobbingDirectory;
|
||||||
|
|
||||||
public GlobbingUrlBuilder() { }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="GlobbingUrlBuilder"/>.
|
/// Creates a new <see cref="GlobbingUrlBuilder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -42,6 +44,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
throw new ArgumentNullException(nameof(fileProvider));
|
throw new ArgumentNullException(nameof(fileProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cache == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(cache));
|
||||||
|
}
|
||||||
|
|
||||||
FileProvider = fileProvider;
|
FileProvider = fileProvider;
|
||||||
Cache = cache;
|
Cache = cache;
|
||||||
RequestPathBase = requestPathBase;
|
RequestPathBase = requestPathBase;
|
||||||
|
|
@ -73,93 +80,103 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
/// <param name="includePattern">The file globbing include pattern.</param>
|
/// <param name="includePattern">The file globbing include pattern.</param>
|
||||||
/// <param name="excludePattern">The file globbing exclude pattern.</param>
|
/// <param name="excludePattern">The file globbing exclude pattern.</param>
|
||||||
/// <returns>The list of URLs</returns>
|
/// <returns>The list of URLs</returns>
|
||||||
public virtual ICollection<string> BuildUrlList(string staticUrl, string includePattern, string excludePattern)
|
public virtual IReadOnlyList<string> BuildUrlList(
|
||||||
|
string staticUrl,
|
||||||
|
string includePattern,
|
||||||
|
string excludePattern)
|
||||||
{
|
{
|
||||||
var urls = new HashSet<string>(StringComparer.Ordinal);
|
// Get urls that match the globbing patterns specified
|
||||||
|
var globbedUrls = ExpandGlobbedUrl(includePattern, excludePattern);
|
||||||
|
|
||||||
// Add the statically declared url if present
|
if (staticUrl == null)
|
||||||
if (staticUrl != null)
|
|
||||||
{
|
{
|
||||||
urls.Add(staticUrl);
|
return globbedUrls;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add urls that match the globbing patterns specified
|
// The staticUrl always appears first in the sequence.
|
||||||
var matchedUrls = ExpandGlobbedUrl(includePattern, excludePattern);
|
var urls = new List<string>(1 + globbedUrls.Count)
|
||||||
urls.UnionWith(matchedUrls);
|
{
|
||||||
|
staticUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < globbedUrls.Count; i++)
|
||||||
|
{
|
||||||
|
if (!string.Equals(staticUrl, globbedUrls[i], StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
urls.Add(globbedUrls[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> ExpandGlobbedUrl(string include, string exclude)
|
private IReadOnlyList<string> ExpandGlobbedUrl(string include, string exclude)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(include))
|
if (string.IsNullOrEmpty(include))
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<string>();
|
return EmptyList;
|
||||||
}
|
}
|
||||||
|
|
||||||
var includePatterns = include.Split(PatternSeparator, StringSplitOptions.RemoveEmptyEntries);
|
var cacheKey = new GlobbingUrlKey(include, exclude);
|
||||||
var excludePatterns = exclude?.Split(PatternSeparator, StringSplitOptions.RemoveEmptyEntries);
|
List<string> files;
|
||||||
|
if (Cache.TryGetValue(cacheKey, out files))
|
||||||
if (includePatterns.Length == 0)
|
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Cache != null)
|
|
||||||
{
|
|
||||||
var cacheKey = $"{nameof(GlobbingUrlBuilder)}-inc:{include}-exc:{exclude}";
|
|
||||||
IEnumerable<string> files;
|
|
||||||
if (!Cache.TryGetValue(cacheKey, out files))
|
|
||||||
{
|
|
||||||
var options = new MemoryCacheEntryOptions();
|
|
||||||
|
|
||||||
for (var i = 0; i < includePatterns.Length; i++)
|
|
||||||
{
|
|
||||||
var changeToken = FileProvider.Watch(includePatterns[i]);
|
|
||||||
options.AddExpirationToken(changeToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
files = FindFiles(includePatterns, excludePatterns);
|
|
||||||
|
|
||||||
Cache.Set(cacheKey, files, options);
|
|
||||||
}
|
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FindFiles(includePatterns, excludePatterns);
|
var includeTokenizer = new StringTokenizer(include, ',');
|
||||||
}
|
var includeEnumerator = includeTokenizer.GetEnumerator();
|
||||||
|
if (!includeEnumerator.MoveNext())
|
||||||
private IEnumerable<string> FindFiles(string[] includePatterns, string[] excludePatterns)
|
|
||||||
{
|
|
||||||
var matcher = MatcherBuilder != null ? MatcherBuilder() : new Matcher();
|
|
||||||
var trimmedIncludePatterns = new List<string>();
|
|
||||||
for (var i = 0; i < includePatterns.Length; i++)
|
|
||||||
{
|
{
|
||||||
trimmedIncludePatterns.Add(TrimLeadingTildeSlash(includePatterns[i]));
|
return EmptyList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var options = new MemoryCacheEntryOptions();
|
||||||
|
var trimmedIncludePatterns = new List<string>();
|
||||||
|
foreach (var includePattern in includeTokenizer)
|
||||||
|
{
|
||||||
|
var changeToken = FileProvider.Watch(includePattern.Value);
|
||||||
|
options.AddExpirationToken(changeToken);
|
||||||
|
trimmedIncludePatterns.Add(NormalizePath(includePattern));
|
||||||
|
}
|
||||||
|
var matcher = MatcherBuilder != null ? MatcherBuilder() : new Matcher();
|
||||||
matcher.AddIncludePatterns(trimmedIncludePatterns);
|
matcher.AddIncludePatterns(trimmedIncludePatterns);
|
||||||
|
|
||||||
if (excludePatterns != null)
|
if (!string.IsNullOrWhiteSpace(exclude))
|
||||||
{
|
{
|
||||||
|
var excludeTokenizer = new StringTokenizer(exclude, ',');
|
||||||
var trimmedExcludePatterns = new List<string>();
|
var trimmedExcludePatterns = new List<string>();
|
||||||
for (var i = 0; i < excludePatterns.Length; i++)
|
foreach (var excludePattern in excludeTokenizer)
|
||||||
{
|
{
|
||||||
trimmedExcludePatterns.Add(TrimLeadingTildeSlash(excludePatterns[i]));
|
trimmedExcludePatterns.Add(NormalizePath(excludePattern));
|
||||||
}
|
}
|
||||||
matcher.AddExcludePatterns(trimmedExcludePatterns);
|
matcher.AddExcludePatterns(trimmedExcludePatterns);
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches = matcher.Execute(_baseGlobbingDirectory);
|
return Cache.Set<List<string>>(
|
||||||
|
cacheKey,
|
||||||
return matches.Files.Select(ResolveMatchedPath)
|
FindFiles(matcher),
|
||||||
.OrderBy(path => path, DefaultPathComparer);
|
options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveMatchedPath(FilePatternMatch matchedPath)
|
private List<string> FindFiles(Matcher matcher)
|
||||||
{
|
{
|
||||||
// Resolve the path to site root
|
var matches = matcher.Execute(_baseGlobbingDirectory);
|
||||||
var relativePath = new PathString("/" + matchedPath.Path);
|
var matchedUrls = new List<string>();
|
||||||
return RequestPathBase.Add(relativePath).ToString();
|
foreach (var matchedPath in matches.Files)
|
||||||
|
{
|
||||||
|
// Resolve the path to site root
|
||||||
|
var relativePath = new PathString("/" + matchedPath.Path);
|
||||||
|
var matchedUrl = RequestPathBase.Add(relativePath).ToString();
|
||||||
|
var index = matchedUrls.BinarySearch(matchedUrl, DefaultPathComparer);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
// Item doesn't already exist. Insert it.
|
||||||
|
matchedUrls.Insert(~index, matchedUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedUrls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PathComparer : IComparer<string>
|
private class PathComparer : IComparer<string>
|
||||||
|
|
@ -189,60 +206,180 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
yExtIndex = yExtIndex > ySlashIndex ? yExtIndex : -1;
|
yExtIndex = yExtIndex > ySlashIndex ? yExtIndex : -1;
|
||||||
|
|
||||||
// Get paths without their extensions, if they have one
|
// Get paths without their extensions, if they have one
|
||||||
|
var xLength = xExtIndex >= 0 ? xExtIndex : x.Length;
|
||||||
|
var yLength = yExtIndex >= 0 ? yExtIndex : y.Length;
|
||||||
|
var compareLength = Math.Max(xLength, yLength);
|
||||||
|
|
||||||
|
// In the resulting sequence, we want shorter paths to appear prior to longer paths. For paths of equal
|
||||||
|
// depth, we'll compare individual segments. The first segment that differs determines the result.
|
||||||
|
// For e.g.
|
||||||
|
// Foo.cshtml < Foo.xhtml
|
||||||
|
// Bar.cshtml < Foo.cshtml
|
||||||
|
// ZZ/z.txt < A/A/a.txt
|
||||||
|
// ZZ/a/z.txt < ZZ/z/a.txt
|
||||||
|
|
||||||
|
if (string.Compare(x, 0, y, 0, compareLength, StringComparison.Ordinal) == 0)
|
||||||
|
{
|
||||||
|
// Only extension differs so just compare the extension
|
||||||
|
if (xExtIndex >= 0 && yExtIndex >= 0)
|
||||||
|
{
|
||||||
|
var length = x.Length - xExtIndex;
|
||||||
|
return string.Compare(x, xExtIndex, y, yExtIndex, length, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return xExtIndex - yExtIndex;
|
||||||
|
}
|
||||||
|
|
||||||
var xNoExt = xExtIndex >= 0 ? x.Substring(0, xExtIndex) : x;
|
var xNoExt = xExtIndex >= 0 ? x.Substring(0, xExtIndex) : x;
|
||||||
var yNoExt = yExtIndex >= 0 ? y.Substring(0, yExtIndex) : y;
|
var yNoExt = yExtIndex >= 0 ? y.Substring(0, yExtIndex) : y;
|
||||||
|
|
||||||
if (string.Equals(xNoExt, yNoExt, StringComparison.Ordinal))
|
var result = 0;
|
||||||
|
var xEnumerator = new StringTokenizer(xNoExt, '/').GetEnumerator();
|
||||||
|
var yEnumerator = new StringTokenizer(yNoExt, '/').GetEnumerator();
|
||||||
|
StringSegment xSegment;
|
||||||
|
StringSegment ySegment;
|
||||||
|
while (TryGetNextSegment(ref xEnumerator, out xSegment))
|
||||||
{
|
{
|
||||||
// Only extension differs so just compare the extension
|
if (!TryGetNextSegment(ref yEnumerator, out ySegment))
|
||||||
var xExt = xExtIndex >= 0 ? x.Substring(xExtIndex) : string.Empty;
|
|
||||||
var yExt = yExtIndex >= 0 ? y.Substring(yExtIndex) : string.Empty;
|
|
||||||
return string.Compare(xExt, yExt, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
var xSegments = xNoExt.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var ySegments = yNoExt.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
if (xSegments.Length != ySegments.Length)
|
|
||||||
{
|
|
||||||
// Different path depths so shallower path wins
|
|
||||||
return xSegments.Length.CompareTo(ySegments.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Depth is the same so compare each segment
|
|
||||||
for (int i = 0; i < xSegments.Length; i++)
|
|
||||||
{
|
|
||||||
var xSegment = xSegments[i];
|
|
||||||
var ySegment = ySegments[i];
|
|
||||||
|
|
||||||
var xToY = string.Compare(xSegment, ySegment, StringComparison.Ordinal);
|
|
||||||
if (xToY != 0)
|
|
||||||
{
|
{
|
||||||
return xToY;
|
// Different path depths (right is shorter), so shallower path wins.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
// Once we've determined that a segment differs, we need to ensure that the two paths
|
||||||
|
// are of equal depth.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var length = Math.Max(xSegment.Length, ySegment.Length);
|
||||||
|
result = string.Compare(
|
||||||
|
xSegment.Buffer,
|
||||||
|
xSegment.Offset,
|
||||||
|
ySegment.Buffer,
|
||||||
|
ySegment.Offset,
|
||||||
|
length,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetNextSegment(ref yEnumerator, out ySegment))
|
||||||
|
{
|
||||||
|
// Different path depths (left is shorter). Shallower path wins.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Segments are of equal length
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetNextSegment(ref StringTokenizer.Enumerator enumerator, out StringSegment segment)
|
||||||
|
{
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
if (enumerator.Current.HasValue && enumerator.Current.Length > 0)
|
||||||
|
{
|
||||||
|
segment = enumerator.Current;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should't get here, but if we do, hey, they're the same :)
|
segment = default(StringSegment);
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string TrimLeadingTildeSlash(string value)
|
private static string NormalizePath(StringSegment value)
|
||||||
{
|
{
|
||||||
var result = value.Trim(ValidAttributeWhitespaceChars);
|
if (!value.HasValue || value.Length == 0)
|
||||||
|
|
||||||
if (result.StartsWith("~/", StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
result = result.Substring(2);
|
return null;
|
||||||
}
|
}
|
||||||
else if (result.StartsWith("/", StringComparison.Ordinal) ||
|
|
||||||
result.StartsWith("\\", StringComparison.Ordinal))
|
value = Trim(value);
|
||||||
|
if (value.StartsWith("~/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
value = new StringSegment(value.Buffer, value.Offset + 2, value.Length - 2);
|
||||||
|
}
|
||||||
|
else if (value.StartsWith("/", StringComparison.Ordinal) ||
|
||||||
|
value.StartsWith("\\", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// Trim the leading slash as the matcher runs from the provided root only anyway
|
// Trim the leading slash as the matcher runs from the provided root only anyway
|
||||||
result = result.Substring(1);
|
value = new StringSegment(value.Buffer, value.Offset + 1, value.Length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return value.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsWhiteSpace(string value, int index)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < ValidAttributeWhitespaceChars.Length; i++)
|
||||||
|
{
|
||||||
|
if (value[index] == ValidAttributeWhitespaceChars[i])
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringSegment Trim(StringSegment value)
|
||||||
|
{
|
||||||
|
var offset = value.Offset;
|
||||||
|
while (offset < value.Offset + value.Length)
|
||||||
|
{
|
||||||
|
if (!IsWhiteSpace(value.Buffer, offset))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var trimmedEnd = value.Offset + value.Length - 1;
|
||||||
|
while (trimmedEnd >= offset)
|
||||||
|
{
|
||||||
|
if (!IsWhiteSpace(value.Buffer, trimmedEnd))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmedEnd--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StringSegment(value.Buffer, offset, trimmedEnd - offset + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct GlobbingUrlKey : IEquatable<GlobbingUrlKey>
|
||||||
|
{
|
||||||
|
public GlobbingUrlKey(string include, string exclude)
|
||||||
|
{
|
||||||
|
Include = include;
|
||||||
|
Exclude = exclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Include { get; }
|
||||||
|
|
||||||
|
public string Exclude { get; }
|
||||||
|
|
||||||
|
public bool Equals(GlobbingUrlKey other)
|
||||||
|
{
|
||||||
|
return string.Equals(Include, other.Include, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(Exclude, other.Exclude, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||||
|
hashCodeCombiner.Add(Include);
|
||||||
|
hashCodeCombiner.Add(Exclude);
|
||||||
|
|
||||||
|
return hashCodeCombiner.CombinedHash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,43 +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.IO;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Methods for encoding <c>string[]</c>s for use as a JavaScript array literal.
|
|
||||||
/// </summary>
|
|
||||||
public static class JavaScriptStringArrayEncoder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Encodes a <c>string[]</c> for safe use as a JavaScript array literal in many contexts, including
|
|
||||||
/// inline in an HTML file.
|
|
||||||
/// </summary>
|
|
||||||
public static string Encode(JavaScriptEncoder encoder, string[] values)
|
|
||||||
{
|
|
||||||
var writer = new StringWriter();
|
|
||||||
writer.Write('[');
|
|
||||||
|
|
||||||
// Perf: Avoid allocating enumerator
|
|
||||||
var firstAdded = false;
|
|
||||||
for (var i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
if (firstAdded)
|
|
||||||
{
|
|
||||||
writer.Write(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Write('"');
|
|
||||||
encoder.Encode(writer, values[i]);
|
|
||||||
writer.Write('"');
|
|
||||||
firstAdded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Write(']');
|
|
||||||
|
|
||||||
return writer.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,14 +2,16 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNet.Hosting;
|
using Microsoft.AspNet.Hosting;
|
||||||
using Microsoft.AspNet.Mvc.Razor.TagHelpers;
|
using Microsoft.AspNet.Mvc.Razor.TagHelpers;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||||
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
|
@ -288,8 +290,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
|
|
||||||
// Build a <link /> tag for each matched href.
|
// Build a <link /> tag for each matched href.
|
||||||
var urls = GlobbingUrlBuilder.BuildUrlList(null, HrefInclude, HrefExclude);
|
var urls = GlobbingUrlBuilder.BuildUrlList(null, HrefInclude, HrefExclude);
|
||||||
foreach (var url in urls)
|
for (var i = 0; i < urls.Count; i++)
|
||||||
{
|
{
|
||||||
|
var url = urls[i];
|
||||||
|
|
||||||
// "url" values come from bound attributes and globbing. Must always be non-null.
|
// "url" values come from bound attributes and globbing. Must always be non-null.
|
||||||
Debug.Assert(url != null);
|
Debug.Assert(url != null);
|
||||||
|
|
||||||
|
|
@ -307,45 +311,70 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
private void BuildFallbackBlock(TagHelperContent builder)
|
private void BuildFallbackBlock(TagHelperContent builder)
|
||||||
{
|
{
|
||||||
EnsureGlobbingUrlBuilder();
|
EnsureGlobbingUrlBuilder();
|
||||||
var fallbackHrefs =
|
var fallbackHrefs = GlobbingUrlBuilder.BuildUrlList(
|
||||||
GlobbingUrlBuilder.BuildUrlList(FallbackHref, FallbackHrefInclude, FallbackHrefExclude).ToArray();
|
FallbackHref,
|
||||||
|
FallbackHrefInclude,
|
||||||
|
FallbackHrefExclude);
|
||||||
|
|
||||||
if (fallbackHrefs.Length > 0)
|
if (fallbackHrefs.Count == 0)
|
||||||
{
|
{
|
||||||
if (AppendVersion == true)
|
return;
|
||||||
{
|
}
|
||||||
for (var i = 0; i < fallbackHrefs.Length; i++)
|
|
||||||
{
|
|
||||||
// fallbackHrefs come from bound attributes and globbing. Must always be non-null.
|
|
||||||
Debug.Assert(fallbackHrefs[i] != null);
|
|
||||||
|
|
||||||
fallbackHrefs[i] = _fileVersionProvider.AddFileVersionToPath(fallbackHrefs[i]);
|
builder.AppendHtml(HtmlString.NewLine);
|
||||||
}
|
|
||||||
|
// Build the <meta /> tag that's used to test for the presence of the stylesheet
|
||||||
|
builder
|
||||||
|
.AppendHtml("<meta name=\"x-stylesheet-fallback-test\" content=\"\" class=\"")
|
||||||
|
.Append(FallbackTestClass)
|
||||||
|
.AppendHtml("\" />");
|
||||||
|
|
||||||
|
// Build the <script /> tag that checks the effective style of <meta /> tag above and renders the extra
|
||||||
|
// <link /> tag to load the fallback stylesheet if the test CSS property value is found to be false,
|
||||||
|
// indicating that the primary stylesheet failed to load.
|
||||||
|
// GetEmbeddedJavaScript returns JavaScript to which we add '"{0}","{1}",{2});'
|
||||||
|
builder
|
||||||
|
.AppendHtml("<script>")
|
||||||
|
.AppendHtml(JavaScriptResources.GetEmbeddedJavaScript(FallbackJavaScriptResourceName))
|
||||||
|
.AppendHtml("\"")
|
||||||
|
.AppendHtml(JavaScriptEncoder.Encode(FallbackTestProperty))
|
||||||
|
.AppendHtml("\",\"")
|
||||||
|
.AppendHtml(JavaScriptEncoder.Encode(FallbackTestValue))
|
||||||
|
.AppendHtml("\",");
|
||||||
|
|
||||||
|
AppendFallbackHrefs(builder, fallbackHrefs);
|
||||||
|
builder.AppendHtml("</script>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendFallbackHrefs(TagHelperContent builder, IReadOnlyList<string> fallbackHrefs)
|
||||||
|
{
|
||||||
|
builder.AppendHtml("[");
|
||||||
|
var firstAdded = false;
|
||||||
|
// Perf: Avoid allocating enumerator
|
||||||
|
for (var i = 0; i < fallbackHrefs.Count; i++)
|
||||||
|
{
|
||||||
|
if (firstAdded)
|
||||||
|
{
|
||||||
|
builder.AppendHtml(",\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendHtml("\"");
|
||||||
|
firstAdded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendHtml(HtmlString.NewLine);
|
// fallbackHrefs come from bound attributes and globbing. Must always be non-null.
|
||||||
|
Debug.Assert(fallbackHrefs[i] != null);
|
||||||
|
var valueToWrite = fallbackHrefs[i];
|
||||||
|
if (AppendVersion == true)
|
||||||
|
{
|
||||||
|
valueToWrite = _fileVersionProvider.AddFileVersionToPath(fallbackHrefs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
// Build the <meta /> tag that's used to test for the presence of the stylesheet
|
builder.AppendHtml(JavaScriptEncoder.Encode(valueToWrite));
|
||||||
builder
|
builder.AppendHtml("\"");
|
||||||
.AppendHtml("<meta name=\"x-stylesheet-fallback-test\" content=\"\" class=\"")
|
|
||||||
.Append(FallbackTestClass)
|
|
||||||
.AppendHtml("\" />");
|
|
||||||
|
|
||||||
// Build the <script /> tag that checks the effective style of <meta /> tag above and renders the extra
|
|
||||||
// <link /> tag to load the fallback stylesheet if the test CSS property value is found to be false,
|
|
||||||
// indicating that the primary stylesheet failed to load.
|
|
||||||
// GetEmbeddedJavaScript returns JavaScript to which we add '"{0}","{1}",{2});'
|
|
||||||
builder
|
|
||||||
.AppendHtml("<script>")
|
|
||||||
.AppendHtml(JavaScriptResources.GetEmbeddedJavaScript(FallbackJavaScriptResourceName))
|
|
||||||
.AppendHtml("\"")
|
|
||||||
.AppendHtml(JavaScriptEncoder.Encode(FallbackTestProperty))
|
|
||||||
.AppendHtml("\",\"")
|
|
||||||
.AppendHtml(JavaScriptEncoder.Encode(FallbackTestValue))
|
|
||||||
.AppendHtml("\",")
|
|
||||||
.AppendHtml(JavaScriptStringArrayEncoder.Encode(JavaScriptEncoder, fallbackHrefs))
|
|
||||||
.AppendHtml(");</script>");
|
|
||||||
}
|
}
|
||||||
|
builder.AppendHtml("]);");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureGlobbingUrlBuilder()
|
private void EnsureGlobbingUrlBuilder()
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@
|
||||||
"Microsoft.Extensions.PropertyHelper.Sources": {
|
"Microsoft.Extensions.PropertyHelper.Sources": {
|
||||||
"version": "1.0.0-*",
|
"version": "1.0.0-*",
|
||||||
"type": "build"
|
"type": "build"
|
||||||
|
},
|
||||||
|
"Microsoft.Extensions.HashCodeCombiner.Sources": {
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
"type": "build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = MakeFileProvider();
|
var fileProvider = MakeFileProvider();
|
||||||
IMemoryCache cache = null;
|
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var requestPathBase = PathString.Empty;
|
var requestPathBase = PathString.Empty;
|
||||||
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
||||||
IMemoryCache cache = null;
|
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var requestPathBase = PathString.Empty;
|
var requestPathBase = PathString.Empty;
|
||||||
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
||||||
|
|
||||||
|
|
@ -210,7 +210,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = MakeFileProvider(dirStructure);
|
var fileProvider = MakeFileProvider(dirStructure);
|
||||||
IMemoryCache cache = null;
|
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var requestPathBase = PathString.Empty;
|
var requestPathBase = PathString.Empty;
|
||||||
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
||||||
|
|
||||||
|
|
@ -230,7 +230,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
||||||
IMemoryCache cache = null;
|
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var requestPathBase = new PathString(pathBase);
|
var requestPathBase = new PathString(pathBase);
|
||||||
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
||||||
|
|
||||||
|
|
@ -277,10 +277,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
Mock.Get(fileProvider).Setup(f => f.Watch(It.IsAny<string>())).Returns(changeToken.Object);
|
Mock.Get(fileProvider).Setup(f => f.Watch(It.IsAny<string>())).Returns(changeToken.Object);
|
||||||
var cache = MakeCache();
|
var cache = MakeCache();
|
||||||
Mock.Get(cache).Setup(c => c.Set(
|
Mock.Get(cache).Setup(c => c.Set(
|
||||||
/*key*/ It.IsAny<string>(),
|
/*key*/ It.IsAny<object>(),
|
||||||
/*value*/ It.IsAny<object>(),
|
/*value*/ It.IsAny<List<string>>(),
|
||||||
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
|
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
|
||||||
.Returns(new object())
|
.Returns((object key, object value, MemoryCacheEntryOptions options) => value)
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var requestPathBase = PathString.Empty;
|
var requestPathBase = PathString.Empty;
|
||||||
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
||||||
|
|
@ -339,7 +339,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.js", "site2.txt", "site.js"));
|
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.js", "site2.txt", "site.js"));
|
||||||
IMemoryCache cache = null;
|
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var requestPathBase = PathString.Empty;
|
var requestPathBase = PathString.Empty;
|
||||||
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
|
||||||
|
|
||||||
|
|
@ -363,7 +363,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
||||||
IMemoryCache cache = null;
|
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var requestPathBase = PathString.Empty;
|
var requestPathBase = PathString.Empty;
|
||||||
var includePatterns = new List<string>();
|
var includePatterns = new List<string>();
|
||||||
var excludePatterns = new List<string>();
|
var excludePatterns = new List<string>();
|
||||||
|
|
@ -391,7 +391,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
// Arrange
|
// Arrange
|
||||||
var leadingSlashes = $"{prefix}{prefix}";
|
var leadingSlashes = $"{prefix}{prefix}";
|
||||||
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
|
||||||
IMemoryCache cache = null;
|
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var requestPathBase = PathString.Empty;
|
var requestPathBase = PathString.Empty;
|
||||||
var includePatterns = new List<string>();
|
var includePatterns = new List<string>();
|
||||||
var excludePatterns = new List<string>();
|
var excludePatterns = new List<string>();
|
||||||
|
|
@ -448,6 +448,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
var fileProvider = new Mock<IFileProvider>(MockBehavior.Strict);
|
var fileProvider = new Mock<IFileProvider>(MockBehavior.Strict);
|
||||||
fileProvider.Setup(fp => fp.GetDirectoryContents(string.Empty))
|
fileProvider.Setup(fp => fp.GetDirectoryContents(string.Empty))
|
||||||
.Returns(MakeDirectoryContents(rootNode, fileProvider));
|
.Returns(MakeDirectoryContents(rootNode, fileProvider));
|
||||||
|
fileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
|
||||||
|
.Returns(new TestFileChangeToken());
|
||||||
|
|
||||||
return fileProvider.Object;
|
return fileProvider.Object;
|
||||||
}
|
}
|
||||||
|
|
@ -494,13 +496,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
|
||||||
var fileProvider = new Mock<IFileProvider>();
|
var fileProvider = new Mock<IFileProvider>();
|
||||||
fileProvider.Setup(fp => fp.GetDirectoryContents(It.IsAny<string>()))
|
fileProvider.Setup(fp => fp.GetDirectoryContents(It.IsAny<string>()))
|
||||||
.Returns(directoryContents);
|
.Returns(directoryContents);
|
||||||
|
fileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
|
||||||
|
.Returns(new TestFileChangeToken());
|
||||||
return fileProvider.Object;
|
return fileProvider.Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IMemoryCache MakeCache(object result = null)
|
private static IMemoryCache MakeCache(object result = null)
|
||||||
{
|
{
|
||||||
var cache = new Mock<IMemoryCache>();
|
var cache = new Mock<IMemoryCache>();
|
||||||
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out result))
|
cache.Setup(c => c.TryGetValue(It.IsAny<object>(), out result))
|
||||||
.Returns(result != null);
|
.Returns(result != null);
|
||||||
return cache.Object;
|
return cache.Object;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.FileProviders;
|
using Microsoft.AspNet.FileProviders;
|
||||||
using Microsoft.AspNet.Hosting;
|
using Microsoft.AspNet.Hosting;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.Mvc.Razor;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||||
|
|
@ -279,7 +281,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
var output = MakeTagHelperOutput("link");
|
var output = MakeTagHelperOutput("link");
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns(new[] { "/common.css" });
|
.Returns(new[] { "/common.css" });
|
||||||
|
|
||||||
|
|
@ -373,7 +378,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
var output = MakeTagHelperOutput("link");
|
var output = MakeTagHelperOutput("link");
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns(new[] { "/common.css" });
|
.Returns(new[] { "/common.css" });
|
||||||
|
|
||||||
|
|
@ -597,7 +605,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
});
|
});
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
|
||||||
.Returns(new[] { "/base.css" });
|
.Returns(new[] { "/base.css" });
|
||||||
|
|
||||||
|
|
@ -640,7 +651,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
});
|
});
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
|
||||||
.Returns(new[] { "/base.css" });
|
.Returns(new[] { "/base.css" });
|
||||||
|
|
||||||
|
|
@ -761,7 +775,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
});
|
});
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
|
||||||
.Returns(new[] { "/base.css" });
|
.Returns(new[] { "/base.css" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.FileProviders;
|
using Microsoft.AspNet.FileProviders;
|
||||||
using Microsoft.AspNet.Hosting;
|
using Microsoft.AspNet.Hosting;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.Mvc.Razor;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.Routing;
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||||
|
|
@ -272,7 +274,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
var output = MakeTagHelperOutput("script");
|
var output = MakeTagHelperOutput("script");
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns(new[] { "/common.js" });
|
.Returns(new[] { "/common.js" });
|
||||||
|
|
||||||
|
|
@ -366,7 +371,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
var output = MakeTagHelperOutput("script");
|
var output = MakeTagHelperOutput("script");
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns(new[] { "/common.js" });
|
.Returns(new[] { "/common.js" });
|
||||||
|
|
||||||
|
|
@ -573,7 +581,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
|
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.js", null))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.js", null))
|
||||||
.Returns(new[] { "/common.js" });
|
.Returns(new[] { "/common.js" });
|
||||||
|
|
||||||
|
|
@ -612,7 +623,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
|
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.js", null))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.js", null))
|
||||||
.Returns(new[] { "/common.js" });
|
.Returns(new[] { "/common.js" });
|
||||||
|
|
||||||
|
|
@ -762,7 +776,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
|
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
|
||||||
var hostingEnvironment = MakeHostingEnvironment();
|
var hostingEnvironment = MakeHostingEnvironment();
|
||||||
var viewContext = MakeViewContext();
|
var viewContext = MakeViewContext();
|
||||||
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
|
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>(
|
||||||
|
new TestFileProvider(),
|
||||||
|
Mock.Of<IMemoryCache>(),
|
||||||
|
PathString.Empty);
|
||||||
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "*.js", null))
|
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "*.js", null))
|
||||||
.Returns(new[] { "/common.js" });
|
.Returns(new[] { "/common.js" });
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue