Reduce allocations in GlobbingUrlBuilder

This commit is contained in:
Pranav K 2016-01-08 18:14:21 -08:00
parent e078259547
commit 200fb23ba5
7 changed files with 360 additions and 195 deletions

View File

@ -3,11 +3,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
@ -17,18 +18,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
/// </summary>
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.
private static readonly char[] ValidAttributeWhitespaceChars =
new[] { '\t', '\n', '\u000C', '\r', ' ' };
private static readonly PathComparer DefaultPathComparer = new PathComparer();
private readonly FileProviderGlobbingDirectory _baseGlobbingDirectory;
public GlobbingUrlBuilder() { }
/// <summary>
/// Creates a new <see cref="GlobbingUrlBuilder"/>.
/// </summary>
@ -42,6 +44,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
throw new ArgumentNullException(nameof(fileProvider));
}
if (cache == null)
{
throw new ArgumentNullException(nameof(cache));
}
FileProvider = fileProvider;
Cache = cache;
RequestPathBase = requestPathBase;
@ -73,93 +80,103 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
/// <param name="includePattern">The file globbing include pattern.</param>
/// <param name="excludePattern">The file globbing exclude pattern.</param>
/// <returns>The list of URLs</returns>
public virtual 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
var matchedUrls = ExpandGlobbedUrl(includePattern, excludePattern);
urls.UnionWith(matchedUrls);
// The staticUrl always appears first in the sequence.
var urls = new List<string>(1 + globbedUrls.Count)
{
staticUrl
};
for (var i = 0; i < globbedUrls.Count; i++)
{
if (!string.Equals(staticUrl, globbedUrls[i], StringComparison.Ordinal))
{
urls.Add(globbedUrls[i]);
}
}
return urls;
}
private IEnumerable<string> ExpandGlobbedUrl(string include, string exclude)
private IReadOnlyList<string> ExpandGlobbedUrl(string include, string exclude)
{
if (string.IsNullOrEmpty(include))
{
return Enumerable.Empty<string>();
return EmptyList;
}
var includePatterns = include.Split(PatternSeparator, StringSplitOptions.RemoveEmptyEntries);
var excludePatterns = exclude?.Split(PatternSeparator, StringSplitOptions.RemoveEmptyEntries);
if (includePatterns.Length == 0)
var cacheKey = new GlobbingUrlKey(include, exclude);
List<string> files;
if (Cache.TryGetValue(cacheKey, out files))
{
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 FindFiles(includePatterns, excludePatterns);
}
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++)
var includeTokenizer = new StringTokenizer(include, ',');
var includeEnumerator = includeTokenizer.GetEnumerator();
if (!includeEnumerator.MoveNext())
{
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);
if (excludePatterns != null)
if (!string.IsNullOrWhiteSpace(exclude))
{
var excludeTokenizer = new StringTokenizer(exclude, ',');
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);
}
var matches = matcher.Execute(_baseGlobbingDirectory);
return matches.Files.Select(ResolveMatchedPath)
.OrderBy(path => path, DefaultPathComparer);
return Cache.Set<List<string>>(
cacheKey,
FindFiles(matcher),
options);
}
private string ResolveMatchedPath(FilePatternMatch matchedPath)
private List<string> FindFiles(Matcher matcher)
{
// Resolve the path to site root
var relativePath = new PathString("/" + matchedPath.Path);
return RequestPathBase.Add(relativePath).ToString();
var matches = matcher.Execute(_baseGlobbingDirectory);
var matchedUrls = new List<string>();
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>
@ -189,60 +206,180 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
yExtIndex = yExtIndex > ySlashIndex ? yExtIndex : -1;
// 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 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
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)
if (!TryGetNextSegment(ref yEnumerator, out ySegment))
{
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 :)
return 0;
segment = default(StringSegment);
return false;
}
}
private static string TrimLeadingTildeSlash(string value)
private static string NormalizePath(StringSegment value)
{
var result = value.Trim(ValidAttributeWhitespaceChars);
if (result.StartsWith("~/", StringComparison.Ordinal))
if (!value.HasValue || value.Length == 0)
{
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
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;
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -2,14 +2,16 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNet.Hosting;
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.ViewFeatures;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
@ -288,8 +290,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Build a <link /> tag for each matched href.
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.
Debug.Assert(url != null);
@ -307,45 +311,70 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
private void BuildFallbackBlock(TagHelperContent builder)
{
EnsureGlobbingUrlBuilder();
var fallbackHrefs =
GlobbingUrlBuilder.BuildUrlList(FallbackHref, FallbackHrefInclude, FallbackHrefExclude).ToArray();
var fallbackHrefs = GlobbingUrlBuilder.BuildUrlList(
FallbackHref,
FallbackHrefInclude,
FallbackHrefExclude);
if (fallbackHrefs.Length > 0)
if (fallbackHrefs.Count == 0)
{
if (AppendVersion == true)
{
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);
return;
}
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("<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(JavaScriptEncoder.Encode(valueToWrite));
builder.AppendHtml("\"");
}
builder.AppendHtml("]);");
}
private void EnsureGlobbingUrlBuilder()

View File

@ -21,6 +21,10 @@
"Microsoft.Extensions.PropertyHelper.Sources": {
"version": "1.0.0-*",
"type": "build"
},
"Microsoft.Extensions.HashCodeCombiner.Sources": {
"version": "1.0.0-*",
"type": "build"
}
},
"frameworks": {

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
// Arrange
var fileProvider = MakeFileProvider();
IMemoryCache cache = null;
var cache = new MemoryCache(new MemoryCacheOptions());
var requestPathBase = PathString.Empty;
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
// Arrange
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
IMemoryCache cache = null;
var cache = new MemoryCache(new MemoryCacheOptions());
var requestPathBase = PathString.Empty;
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
@ -210,7 +210,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
// Arrange
var fileProvider = MakeFileProvider(dirStructure);
IMemoryCache cache = null;
var cache = new MemoryCache(new MemoryCacheOptions());
var requestPathBase = PathString.Empty;
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
@ -230,7 +230,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
// Arrange
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
IMemoryCache cache = null;
var cache = new MemoryCache(new MemoryCacheOptions());
var requestPathBase = new PathString(pathBase);
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);
var cache = MakeCache();
Mock.Get(cache).Setup(c => c.Set(
/*key*/ It.IsAny<string>(),
/*value*/ It.IsAny<object>(),
/*key*/ It.IsAny<object>(),
/*value*/ It.IsAny<List<string>>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns(new object())
.Returns((object key, object value, MemoryCacheEntryOptions options) => value)
.Verifiable();
var requestPathBase = PathString.Empty;
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
@ -339,7 +339,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
// Arrange
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 globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);
@ -363,7 +363,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
// Arrange
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
IMemoryCache cache = null;
var cache = new MemoryCache(new MemoryCacheOptions());
var requestPathBase = PathString.Empty;
var includePatterns = new List<string>();
var excludePatterns = new List<string>();
@ -391,7 +391,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
// Arrange
var leadingSlashes = $"{prefix}{prefix}";
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
IMemoryCache cache = null;
var cache = new MemoryCache(new MemoryCacheOptions());
var requestPathBase = PathString.Empty;
var includePatterns = 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);
fileProvider.Setup(fp => fp.GetDirectoryContents(string.Empty))
.Returns(MakeDirectoryContents(rootNode, fileProvider));
fileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
.Returns(new TestFileChangeToken());
return fileProvider.Object;
}
@ -494,13 +496,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var fileProvider = new Mock<IFileProvider>();
fileProvider.Setup(fp => fp.GetDirectoryContents(It.IsAny<string>()))
.Returns(directoryContents);
fileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
.Returns(new TestFileChangeToken());
return fileProvider.Object;
}
private static IMemoryCache MakeCache(object result = null)
{
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);
return cache.Object;
}

View File

@ -9,9 +9,11 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
@ -279,7 +281,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var output = MakeTagHelperOutput("link");
var hostingEnvironment = MakeHostingEnvironment();
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>()))
.Returns(new[] { "/common.css" });
@ -373,7 +378,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var output = MakeTagHelperOutput("link");
var hostingEnvironment = MakeHostingEnvironment();
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>()))
.Returns(new[] { "/common.css" });
@ -597,7 +605,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
});
var hostingEnvironment = MakeHostingEnvironment();
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))
.Returns(new[] { "/base.css" });
@ -640,7 +651,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
});
var hostingEnvironment = MakeHostingEnvironment();
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))
.Returns(new[] { "/base.css" });
@ -761,7 +775,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
});
var hostingEnvironment = MakeHostingEnvironment();
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))
.Returns(new[] { "/base.css" });

View File

@ -9,9 +9,11 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
@ -272,7 +274,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var output = MakeTagHelperOutput("script");
var hostingEnvironment = MakeHostingEnvironment();
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>()))
.Returns(new[] { "/common.js" });
@ -366,7 +371,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var output = MakeTagHelperOutput("script");
var hostingEnvironment = MakeHostingEnvironment();
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>()))
.Returns(new[] { "/common.js" });
@ -573,7 +581,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var hostingEnvironment = MakeHostingEnvironment();
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))
.Returns(new[] { "/common.js" });
@ -612,7 +623,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var hostingEnvironment = MakeHostingEnvironment();
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))
.Returns(new[] { "/common.js" });
@ -762,7 +776,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
var hostingEnvironment = MakeHostingEnvironment();
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))
.Returns(new[] { "/common.js" });