* Introduce _GlobalImports to inherit directives.
* Remove inheritance from _ViewStarts Fixes #825
This commit is contained in:
parent
966bfeb8c1
commit
eb7b0d6ae3
|
|
@ -1,5 +1,3 @@
|
|||
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
|
||||
|
||||
@{
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
|
@ -13,8 +13,7 @@ using Microsoft.AspNet.Razor.Parser;
|
|||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility type for supporting inheritance of tag helpers and chunks into a page from applicable _ViewStart
|
||||
/// pages.
|
||||
/// A utility type for supporting inheritance of directives into a page from applicable <c>_GlobalImport</c> pages.
|
||||
/// </summary>
|
||||
public class ChunkInheritanceUtility
|
||||
{
|
||||
|
|
@ -25,9 +24,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ChunkInheritanceUtility"/>.
|
||||
/// </summary>
|
||||
/// <param name="razorHost">The <see cref="MvcRazorHost"/> used to parse _ViewStart pages.</param>
|
||||
/// <param name="codeTreeCache"><see cref="ICodeTreeCache"/> that caches _ViewStart <see cref="CodeTree"/>
|
||||
/// instances.</param>
|
||||
/// <param name="razorHost">The <see cref="MvcRazorHost"/> used to parse <c>_GlobalImport</c> pages.</param>
|
||||
/// <param name="codeTreeCache"><see cref="ICodeTreeCache"/> that caches <see cref="CodeTree"/> instances.
|
||||
/// </param>
|
||||
/// <param name="defaultInheritedChunks">Sequence of <see cref="Chunk"/>s inherited by default.</param>
|
||||
public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost,
|
||||
[NotNull] ICodeTreeCache codeTreeCache,
|
||||
|
|
@ -39,25 +38,28 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ordered <see cref="IReadOnlyList{T}"/> of parsed <see cref="CodeTree"/> for each _ViewStart that
|
||||
/// is applicable to the page located at <paramref name="pagePath"/>. The list is ordered so that the
|
||||
/// <see cref="CodeTree"/> for the _ViewStart closest to the <paramref name="pagePath"/> in the fileProvider
|
||||
/// appears first.
|
||||
/// Gets an ordered <see cref="IReadOnlyList{T}"/> of parsed <see cref="CodeTree"/> for each
|
||||
/// <c>_GlobalImport</c> that is applicable to the page located at <paramref name="pagePath"/>. The list is
|
||||
/// ordered so that the <see cref="CodeTree"/> for the <c>_GlobalImport</c> closest to the
|
||||
/// <paramref name="pagePath"/> in the file system appears first.
|
||||
/// </summary>
|
||||
/// <param name="pagePath">The path of the page to locate inherited chunks for.</param>
|
||||
/// <returns>A <see cref="IReadOnlyList{CodeTree}"/> of parsed _ViewStart <see cref="CodeTree"/>s.</returns>
|
||||
/// <returns>A <see cref="IReadOnlyList{CodeTree}"/> of parsed <c>_GlobalImport</c>
|
||||
/// <see cref="CodeTree"/>s.</returns>
|
||||
public IReadOnlyList<CodeTree> GetInheritedCodeTrees([NotNull] string pagePath)
|
||||
{
|
||||
var inheritedCodeTrees = new List<CodeTree>();
|
||||
var templateEngine = new RazorTemplateEngine(_razorHost);
|
||||
foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(pagePath))
|
||||
foreach (var globalImportPath in ViewHierarchyUtility.GetGlobalImportLocations(pagePath))
|
||||
{
|
||||
// viewStartPath contains the app-relative path of the ViewStart.
|
||||
// Since the parsing of a _ViewStart would cause parent _ViewStarts to be parsed
|
||||
// we need to ensure the paths are app-relative to allow the GetViewStartLocations
|
||||
// for the current _ViewStart to succeed.
|
||||
var codeTree = _codeTreeCache.GetOrAdd(viewStartPath,
|
||||
fileInfo => ParseViewFile(templateEngine, fileInfo, viewStartPath));
|
||||
// globalImportPath contains the app-relative path of the _GlobalImport.
|
||||
// Since the parsing of a _GlobalImport would cause parent _GlobalImports to be parsed
|
||||
// we need to ensure the paths are app-relative to allow the GetGlobalFileLocations
|
||||
// for the current _GlobalImport to succeed.
|
||||
var codeTree = _codeTreeCache.GetOrAdd(globalImportPath,
|
||||
fileInfo => ParseViewFile(templateEngine,
|
||||
fileInfo,
|
||||
globalImportPath));
|
||||
|
||||
if (codeTree != null)
|
||||
{
|
||||
|
|
@ -70,10 +72,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
|
||||
/// <summary>
|
||||
/// Merges <see cref="Chunk"/> inherited by default and <see cref="CodeTree"/> instances produced by parsing
|
||||
/// _ViewStart files into the specified <paramref name="codeTree"/>.
|
||||
/// <c>_GlobalImport</c> files into the specified <paramref name="codeTree"/>.
|
||||
/// </summary>
|
||||
/// <param name="codeTree">The <see cref="CodeTree"/> to merge in to.</param>
|
||||
/// <param name="inheritedCodeTrees"><see cref="IReadOnlyList{CodeTree}"/> inherited from _ViewStart
|
||||
/// <param name="inheritedCodeTrees"><see cref="IReadOnlyList{CodeTree}"/> inherited from <c>_GlobalImport</c>
|
||||
/// files.</param>
|
||||
/// <param name="defaultModel">The list of chunks to merge.</param>
|
||||
public void MergeInheritedCodeTrees([NotNull] CodeTree codeTree,
|
||||
|
|
@ -96,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
// In the second phase we invoke IChunkMerger.Merge for each chunk that has a mapped merger.
|
||||
// During this phase, the merger can either add to the CodeTree or ignore the chunk based on the merging
|
||||
// rules.
|
||||
// Read the chunks outside in - that is chunks from the _ViewStart closest to the page get merged in first
|
||||
// Read the chunks outside in - that is chunks from the _GlobalImport closest to the page get merged in first
|
||||
// and the furthest one last. This allows the merger to ignore a directive like @model that was previously
|
||||
// seen.
|
||||
var chunksToMerge = inheritedCodeTrees.SelectMany(tree => tree.Chunks)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods to locate <c>_ViewStart.cshtml</c> and <c>_GlobalImport.cshtml</c>
|
||||
/// </summary>
|
||||
public static class ViewHierarchyUtility
|
||||
{
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
private const string GlobalImportFileName = "_GlobalImport.cshtml";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view start locations that are applicable to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="applicationRelativePath">The application relative path of the file to locate
|
||||
/// <c>_ViewStart</c>s for.</param>
|
||||
/// <returns>A sequence of paths that represent potential view start locations.</returns>
|
||||
/// <remarks>
|
||||
/// This method returns paths starting from the directory of <paramref name="applicationRelativePath"/> and
|
||||
/// moves upwards until it hits the application root.
|
||||
/// e.g.
|
||||
/// /Views/Home/View.cshtml -> [ /Views/Home/_ViewStart.cshtml, /Views/_ViewStart.cshtml, /_ViewStart.cshtml ]
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetViewStartLocations(string applicationRelativePath)
|
||||
{
|
||||
return GetHierarchicalPath(applicationRelativePath, ViewStartFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the locations for <c>_GlobalImport</c>s that are applicable to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="applicationRelativePath">The application relative path of the file to locate
|
||||
/// <c>_GlobalImport</c>s for.</param>
|
||||
/// <returns>A sequence of paths that represent potential <c>_GlobalImport</c> locations.</returns>
|
||||
/// <remarks>
|
||||
/// This method returns paths starting from the directory of <paramref name="applicationRelativePath"/> and
|
||||
/// moves upwards until it hits the application root.
|
||||
/// e.g.
|
||||
/// /Views/Home/View.cshtml -> [ /Views/Home/_GlobalImport.cshtml, /Views/_GlobalImport.cshtml,
|
||||
/// /_GlobalImport.cshtml ]
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetGlobalImportLocations(string applicationRelativePath)
|
||||
{
|
||||
return GetHierarchicalPath(applicationRelativePath, GlobalImportFileName);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetHierarchicalPath(string relativePath, string fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
if (relativePath.StartsWith("~/", StringComparison.Ordinal))
|
||||
{
|
||||
relativePath = relativePath.Substring(2);
|
||||
}
|
||||
|
||||
if (relativePath.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
relativePath = relativePath.Substring(1);
|
||||
}
|
||||
|
||||
if (Path.IsPathRooted(relativePath))
|
||||
{
|
||||
// If the path looks like it's not app relative, don't attempt to construct paths.
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
if (string.Equals(Path.GetFileName(relativePath), fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// If the specified path is for the file hierarchy being constructed, then the first file that applies
|
||||
// to it is in a parent directory.
|
||||
relativePath = Path.GetDirectoryName(relativePath);
|
||||
if (string.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
var locations = new List<string>();
|
||||
while (!string.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
relativePath = Path.GetDirectoryName(relativePath);
|
||||
var path = Path.Combine(relativePath, fileName);
|
||||
locations.Add(path);
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to locate <c>_ViewStart.cshtml</c>
|
||||
/// </summary>
|
||||
public static class ViewStartUtility
|
||||
{
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the given path represents a view start file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to inspect.</param>
|
||||
/// <returns>True if the path is a view start file, false otherwise.</returns>
|
||||
public static bool IsViewStart([NotNull] string path)
|
||||
{
|
||||
var fileName = Path.GetFileName(path);
|
||||
return string.Equals(ViewStartFileName, fileName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view start locations that are applicable to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="applicationRelativePath">The application relative path of the file to locate
|
||||
/// <c>_ViewStart</c>s for.</param>
|
||||
/// <returns>A sequence of paths that represent potential view start locations.</returns>
|
||||
/// <remarks>
|
||||
/// This method returns paths starting from the directory of <paramref name="applicationRelativePath"/> and
|
||||
/// moves upwards until it hits the application root.
|
||||
/// e.g.
|
||||
/// /Views/Home/View.cshtml -> [ /Views/Home/_ViewStart.cshtml, /Views/_ViewStart.cshtml, /_ViewStart.cshtml ]
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetViewStartLocations(string applicationRelativePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(applicationRelativePath))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
if (applicationRelativePath.StartsWith("~/", StringComparison.Ordinal))
|
||||
{
|
||||
applicationRelativePath = applicationRelativePath.Substring(2);
|
||||
}
|
||||
|
||||
if (applicationRelativePath.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
applicationRelativePath = applicationRelativePath.Substring(1);
|
||||
}
|
||||
|
||||
if (Path.IsPathRooted(applicationRelativePath))
|
||||
{
|
||||
// If the path looks like it's app relative, don't attempt to construct _ViewStart paths.
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
if (IsViewStart(applicationRelativePath))
|
||||
{
|
||||
// If the specified path is a ViewStart file, then the first view start that applies to it is the
|
||||
// parent view start.
|
||||
applicationRelativePath = Path.GetDirectoryName(applicationRelativePath);
|
||||
if (string.IsNullOrEmpty(applicationRelativePath))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
var viewStartLocations = new List<string>();
|
||||
while (!string.IsNullOrEmpty(applicationRelativePath))
|
||||
{
|
||||
applicationRelativePath = Path.GetDirectoryName(applicationRelativePath);
|
||||
var viewStartPath = Path.Combine(applicationRelativePath, ViewStartFileName);
|
||||
viewStartLocations.Add(viewStartPath);
|
||||
}
|
||||
|
||||
return viewStartLocations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,17 +57,17 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// Set up ViewStarts
|
||||
// Set up _GlobalImports
|
||||
foreach (var entry in cacheEntries)
|
||||
{
|
||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(entry.RelativePath);
|
||||
foreach (var location in viewStartLocations)
|
||||
var globalFileLocations = ViewHierarchyUtility.GetGlobalImportLocations(entry.RelativePath);
|
||||
foreach (var location in globalFileLocations)
|
||||
{
|
||||
var viewStartEntry = _cache.Get<CompilerCacheEntry>(location);
|
||||
if (viewStartEntry != null)
|
||||
var globalFileEntry = _cache.Get<CompilerCacheEntry>(location);
|
||||
if (globalFileEntry != null)
|
||||
{
|
||||
// Add the the composite _ViewStart entry as a dependency.
|
||||
entry.AssociatedViewStartEntry = viewStartEntry;
|
||||
// Add the the composite _GlobalImport entry as a dependency.
|
||||
entry.AssociatedGlobalFileEntry = globalFileEntry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
else if (cacheEntry.IsPreCompiled && !cacheEntry.IsValidatedPreCompiled)
|
||||
{
|
||||
// For precompiled views, the first time the entry is read, we need to ensure that no changes were made
|
||||
// either to the file associated with this entry, or any _ViewStart associated with it between the time
|
||||
// either to the file associated with this entry, or any _GlobalImport associated with it between the time
|
||||
// the View was precompiled and the time EnsureInitialized was called. For later iterations, we can
|
||||
// rely on expiration triggers ensuring the validity of the entry.
|
||||
|
||||
|
|
@ -123,9 +123,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
|
||||
}
|
||||
|
||||
if (AssociatedViewStartsChanged(cacheEntry, compile))
|
||||
if (AssociatedGlobalFilesChanged(cacheEntry, compile))
|
||||
{
|
||||
// Recompile if the view starts have changed since the entry was created.
|
||||
// Recompile if _GlobalImports have changed since the entry was created.
|
||||
return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
|
||||
}
|
||||
|
||||
|
|
@ -192,8 +192,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
var entry = (CompilerCacheEntry)cacheSetContext.State;
|
||||
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(entry.RelativePath));
|
||||
|
||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(cacheSetContext.Key);
|
||||
foreach (var location in viewStartLocations)
|
||||
var globalImportPaths = ViewHierarchyUtility.GetGlobalImportLocations(cacheSetContext.Key);
|
||||
foreach (var location in globalImportPaths)
|
||||
{
|
||||
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(location));
|
||||
}
|
||||
|
|
@ -201,31 +201,31 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return entry;
|
||||
}
|
||||
|
||||
private bool AssociatedViewStartsChanged(CompilerCacheEntry entry,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
private bool AssociatedGlobalFilesChanged(CompilerCacheEntry entry,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
{
|
||||
var viewStartEntry = GetCompositeViewStartEntry(entry.RelativePath, compile);
|
||||
return entry.AssociatedViewStartEntry != viewStartEntry;
|
||||
var globalFileEntry = GetCompositeGlobalFileEntry(entry.RelativePath, compile);
|
||||
return entry.AssociatedGlobalFileEntry != globalFileEntry;
|
||||
}
|
||||
|
||||
// Returns the entry for the nearest _ViewStart that the file inherits directives from. Since _ViewStart
|
||||
// entries are affected by other _ViewStart entries that are in the path hierarchy, the returned value
|
||||
// represents the composite result of performing a cache check on individual _ViewStart entries.
|
||||
private CompilerCacheEntry GetCompositeViewStartEntry(string relativePath,
|
||||
// Returns the entry for the nearest _GlobalImport that the file inherits directives from. Since _GlobalImport
|
||||
// entries are affected by other _GlobalImport entries that are in the path hierarchy, the returned value
|
||||
// represents the composite result of performing a cache check on individual _GlobalImport entries.
|
||||
private CompilerCacheEntry GetCompositeGlobalFileEntry(string relativePath,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
{
|
||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(relativePath);
|
||||
foreach (var viewStartLocation in viewStartLocations)
|
||||
var globalImportLocations = ViewHierarchyUtility.GetGlobalImportLocations(relativePath);
|
||||
foreach (var globalImport in globalImportLocations)
|
||||
{
|
||||
var getOrAddResult = GetOrAddCore(viewStartLocation, compile);
|
||||
var getOrAddResult = GetOrAddCore(globalImport, compile);
|
||||
if (getOrAddResult != null)
|
||||
{
|
||||
// This is the nearest _ViewStart that exists on disk.
|
||||
// This is the nearest _GlobalImport that exists on disk.
|
||||
return getOrAddResult.CompilerCacheEntry;
|
||||
}
|
||||
}
|
||||
|
||||
// No _ViewStarts discovered.
|
||||
// No _GlobalImports discovered.
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -240,8 +240,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return path;
|
||||
}
|
||||
|
||||
internal static IEnumerable<RazorFileInfoCollection>
|
||||
GetFileInfos(IEnumerable<Assembly> assemblies)
|
||||
internal static IEnumerable<RazorFileInfoCollection> GetFileInfos(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
return assemblies.SelectMany(a => a.ExportedTypes)
|
||||
.Where(Match)
|
||||
|
|
|
|||
|
|
@ -78,10 +78,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public bool IsPreCompiled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="CompilerCacheEntry"/> for the nearest ViewStart that the compiled type
|
||||
/// Gets or sets the <see cref="CompilerCacheEntry"/> for the nearest _GlobalImport that the compiled type
|
||||
/// depends on.
|
||||
/// </summary>
|
||||
public CompilerCacheEntry AssociatedViewStartEntry { get; set; }
|
||||
public CompilerCacheEntry AssociatedGlobalFileEntry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag that determines if the validity of this cache entry was performed at runtime.
|
||||
|
|
|
|||
|
|
@ -121,9 +121,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
if (entry != null)
|
||||
{
|
||||
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(fileInfo.RelativePath));
|
||||
foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(fileInfo.RelativePath))
|
||||
foreach (var path in ViewHierarchyUtility.GetGlobalImportLocations(fileInfo.RelativePath))
|
||||
{
|
||||
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(viewStartPath));
|
||||
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <inheritdoc />
|
||||
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path)
|
||||
{
|
||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(path);
|
||||
var viewStartLocations = ViewHierarchyUtility.GetViewStartLocations(path);
|
||||
var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance)
|
||||
.Where(p => p != null)
|
||||
.ToArray();
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var body = await client.GetStringAsync("http://localhost/Directives/ViewInheritsInjectAndUsingsFromViewStarts");
|
||||
var body = await client.GetStringAsync("http://localhost/Directives/ViewInheritsInjectAndUsingsFromGlobalImports");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, body.Trim());
|
||||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var body = await client.GetStringAsync("http://localhost/Directives/ViewInheritsBasePageFromViewStarts");
|
||||
var body = await client.GetStringAsync("http://localhost/Directives/ViewInheritsBasePageFromGlobalImports");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, body.Trim());
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var layoutContent = File.ReadAllText(Path.Combine(viewsDirectory, "Layout.cshtml"));
|
||||
var indexContent = File.ReadAllText(Path.Combine(viewsDirectory, "Index.cshtml"));
|
||||
var viewstartContent = File.ReadAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"));
|
||||
var globalContent = File.ReadAllText(Path.Combine(viewsDirectory, "_GlobalImport.cshtml"));
|
||||
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -65,57 +66,54 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(assemblyName, response2.Index);
|
||||
|
||||
// Act - 3
|
||||
// Touch the _ViewStart file and verify it causes all files to recompile.
|
||||
// Touch the _ViewStart file and verify it is is dynamically compiled.
|
||||
await TouchFile(viewsDirectory, "_ViewStart.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 3
|
||||
var response3 = new ParsedResponse(responseContent);
|
||||
Assert.NotEqual(assemblyName, response3.ViewStart);
|
||||
Assert.NotEqual(assemblyName, response3.Index);
|
||||
Assert.NotEqual(response2.Layout, response3.Layout);
|
||||
Assert.Equal(assemblyName, response3.Index);
|
||||
Assert.Equal(response2.Layout, response3.Layout);
|
||||
|
||||
// Act - 4
|
||||
// Touch Index file and verify it is the only page that recompiles.
|
||||
await TouchFile(viewsDirectory, "Index.cshtml");
|
||||
// Touch the _GlobalImport file and verify it causes all files to recompile.
|
||||
await TouchFile(viewsDirectory, "_GlobalImport.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 4
|
||||
var response4 = new ParsedResponse(responseContent);
|
||||
// Layout and _ViewStart should not have changed.
|
||||
Assert.Equal(response3.Layout, response4.Layout);
|
||||
Assert.Equal(response3.ViewStart, response4.ViewStart);
|
||||
Assert.NotEqual(response3.ViewStart, response4.ViewStart);
|
||||
Assert.NotEqual(response3.Index, response4.Index);
|
||||
Assert.NotEqual(response3.Layout, response4.Layout);
|
||||
|
||||
// Act - 5
|
||||
// Touch the _ViewStart file. This time, we'll verify the Non-precompiled -> Non-precompiled workflow.
|
||||
await TouchFile(viewsDirectory, "_ViewStart.cshtml");
|
||||
// Touch Index file and verify it is the only page that recompiles.
|
||||
await TouchFile(viewsDirectory, "Index.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 5
|
||||
var response5 = new ParsedResponse(responseContent);
|
||||
// Everything should've recompiled.
|
||||
Assert.NotEqual(response4.ViewStart, response5.ViewStart);
|
||||
// Layout and _ViewStart should not have changed.
|
||||
Assert.Equal(response4.Layout, response5.Layout);
|
||||
Assert.Equal(response4.ViewStart, response5.ViewStart);
|
||||
Assert.NotEqual(response4.Index, response5.Index);
|
||||
Assert.NotEqual(response4.Layout, response5.Layout);
|
||||
|
||||
// Act - 6
|
||||
// Add a new _ViewStart file
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "..", "_ViewStart.cshtml"), string.Empty);
|
||||
await Task.Delay(_cacheDelayInterval);
|
||||
// Touch the _GlobalImport file. This time, we'll verify the Non-precompiled -> Non-precompiled workflow.
|
||||
await TouchFile(viewsDirectory, "_GlobalImport.cshtml");
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 6
|
||||
// Everything should've recompiled.
|
||||
var response6 = new ParsedResponse(responseContent);
|
||||
// Everything should've recompiled.
|
||||
Assert.NotEqual(response5.ViewStart, response6.ViewStart);
|
||||
Assert.NotEqual(response5.Index, response6.Index);
|
||||
Assert.NotEqual(response5.Layout, response6.Layout);
|
||||
|
||||
// Act - 7
|
||||
// Remove new _ViewStart file
|
||||
File.Delete(Path.Combine(viewsDirectory, "..", "_ViewStart.cshtml"));
|
||||
await Task.Delay(_cacheDelayInterval);
|
||||
// Add a new _GlobalImport file
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "..", "_GlobalImport.cshtml"), string.Empty);
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 7
|
||||
|
|
@ -126,20 +124,33 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.NotEqual(response6.Layout, response7.Layout);
|
||||
|
||||
// Act - 8
|
||||
// Remove new _GlobalImport file
|
||||
File.Delete(Path.Combine(viewsDirectory, "..", "_GlobalImport.cshtml"));
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 8
|
||||
// Everything should've recompiled.
|
||||
var response8 = new ParsedResponse(responseContent);
|
||||
Assert.NotEqual(response6.ViewStart, response7.ViewStart);
|
||||
Assert.NotEqual(response6.Index, response7.Index);
|
||||
Assert.NotEqual(response6.Layout, response7.Layout);
|
||||
|
||||
// Act - 9
|
||||
// Refetch and verify we get cached types
|
||||
responseContent = await client.GetStringAsync("http://localhost/Home/Index");
|
||||
|
||||
// Assert - 7
|
||||
var response8 = new ParsedResponse(responseContent);
|
||||
Assert.Equal(response7.ViewStart, response8.ViewStart);
|
||||
Assert.Equal(response7.Index, response8.Index);
|
||||
Assert.Equal(response7.Layout, response8.Layout);
|
||||
// Assert - 9
|
||||
var response9 = new ParsedResponse(responseContent);
|
||||
Assert.Equal(response8.ViewStart, response9.ViewStart);
|
||||
Assert.Equal(response8.Index, response9.Index);
|
||||
Assert.Equal(response8.Layout, response9.Layout);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent);
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "Index.cshtml"), indexContent);
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"), viewstartContent);
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent.TrimEnd(' '));
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "Index.cshtml"), indexContent.TrimEnd(' '));
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"), viewstartContent.TrimEnd(' '));
|
||||
File.WriteAllText(Path.Combine(viewsDirectory, "_GlobalImport.cshtml"), globalContent.TrimEnd(' '));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +179,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeletingPrecompiledViewStart_PriorToFirstRequestToAView_CausesViewToBeRecompiled()
|
||||
public async Task DeletingPrecompiledGlobalFile_PriorToFirstRequestToAView_CausesViewToBeRecompiled()
|
||||
{
|
||||
// Arrange
|
||||
var expected = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString();
|
||||
|
|
@ -177,9 +188,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
|
||||
var applicationEnvironment = _services.GetRequiredService<IApplicationEnvironment>();
|
||||
|
||||
var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath, "Views", "ViewStartDelete");
|
||||
var viewStartPath = Path.Combine(viewsDirectory, "_ViewStart.cshtml");
|
||||
var viewStartContent = File.ReadAllText(viewStartPath);
|
||||
var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath,
|
||||
"Views",
|
||||
"GlobalImportDelete");
|
||||
var globalPath = Path.Combine(viewsDirectory, "_GlobalImport.cshtml");
|
||||
var globalContent = File.ReadAllText(globalPath);
|
||||
|
||||
// Act - 1
|
||||
// Query the Test view so we know the compiler cache gets populated.
|
||||
|
|
@ -191,14 +204,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
try
|
||||
{
|
||||
// Act - 2
|
||||
var response2 = await client.GetStringAsync("http://localhost/Home/ViewStartDeletedPriorToFirstRequest");
|
||||
File.Delete(globalPath);
|
||||
var response2 = await client.GetStringAsync("http://localhost/Home/GlobalDeletedPriorToFirstRequest");
|
||||
|
||||
// Assert - 2
|
||||
Assert.NotEqual(expected, response2.Trim());
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.WriteAllText(viewStartPath, viewStartContent);
|
||||
File.WriteAllText(globalPath, globalContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -286,11 +286,8 @@ View With Layout
|
|||
Assert.Equal(expected, body.Trim());
|
||||
}
|
||||
|
||||
// Inheritance of chunks in _ViewStart is affected by paths being app-relative and not absolute.
|
||||
// This change ensures that _ViewStart files correctly inherits directives from parent _ViewStarts
|
||||
// which guarantees that paths flow correctly through MvcRazorHost.
|
||||
[Fact]
|
||||
public async Task ViewStartsCanUseDirectivesInjectedFromParentViewStarts()
|
||||
public async Task ViewStartsCanUseDirectivesInjectedFromParentGlobals()
|
||||
{
|
||||
// Arrange
|
||||
var expected =
|
||||
|
|
@ -298,7 +295,7 @@ View With Layout
|
|||
<page>Hello Controller-Person</page>";
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
var target = "http://localhost/NestedViewStarts/NestedViewStartUsingParentDirectives";
|
||||
var target = "http://localhost/NestedGlobalImports";
|
||||
|
||||
// Act
|
||||
var body = await client.GetStringAsync(target);
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
public class ChunkInheritanceUtilityTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetInheritedChunks_ReadsChunksFromViewStartsInPath()
|
||||
public void GetInheritedChunks_ReadsChunksFromGlobalFilesInPath()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(@"Views\accounts\_ViewStart.cshtml", "@using AccountModels");
|
||||
fileProvider.AddFile(@"Views\Shared\_ViewStart.cshtml", "@inject SharedHelper Shared");
|
||||
fileProvider.AddFile(@"Views\home\_ViewStart.cshtml", "@using MyNamespace");
|
||||
fileProvider.AddFile(@"Views\_ViewStart.cshtml",
|
||||
fileProvider.AddFile(@"Views\accounts\_GlobalImport.cshtml", "@using AccountModels");
|
||||
fileProvider.AddFile(@"Views\Shared\_GlobalImport.cshtml", "@inject SharedHelper Shared");
|
||||
fileProvider.AddFile(@"Views\home\_GlobalImport.cshtml", "@using MyNamespace");
|
||||
fileProvider.AddFile(@"Views\_GlobalImport.cshtml",
|
||||
@"@inject MyHelper<TModel> Helper
|
||||
@inherits MyBaseType
|
||||
|
||||
|
|
@ -64,13 +64,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInheritedChunks_ReturnsEmptySequenceIfNoViewStartsArePresent()
|
||||
public void GetInheritedChunks_ReturnsEmptySequenceIfNoGlobalsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(@"_ViewStart.cs", string.Empty);
|
||||
fileProvider.AddFile(@"_GlobalImport.cs", string.Empty);
|
||||
fileProvider.AddFile(@"Views\_Layout.cshtml", string.Empty);
|
||||
fileProvider.AddFile(@"Views\home\_not-viewstart.cshtml", string.Empty);
|
||||
fileProvider.AddFile(@"Views\home\_not-globalimport.cshtml", string.Empty);
|
||||
var cache = new DefaultCodeTreeCache(fileProvider);
|
||||
var host = new MvcRazorHost(cache);
|
||||
var defaultChunks = new Chunk[]
|
||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(@"Views\_ViewStart.cshtml",
|
||||
fileProvider.AddFile(@"Views\_GlobalImport.cshtml",
|
||||
"@inject DifferentHelper<TModel> Html");
|
||||
var cache = new DefaultCodeTreeCache(fileProvider);
|
||||
var host = new MvcRazorHost(cache);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,249 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class ViewHierarchyUtilityTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public void GetViewStartLocations_ReturnsEmptySequenceIfViewPathIsEmpty(string viewPath)
|
||||
{
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public void GetGlobalImportLocations_ReturnsEmptySequenceIfViewPathIsEmpty(string viewPath)
|
||||
{
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetGlobalImportLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/MyView.cshtml")]
|
||||
[InlineData("~/Views/Home/MyView.cshtml")]
|
||||
[InlineData("Views/Home/MyView.cshtml")]
|
||||
public void GetViewStartLocations_ReturnsPotentialViewStartLocations_PathStartswithSlash(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Views\Home\_ViewStart.cshtml",
|
||||
@"Views\_ViewStart.cshtml",
|
||||
@"_ViewStart.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/MyView.cshtml")]
|
||||
[InlineData("~/Views/Home/MyView.cshtml")]
|
||||
[InlineData("Views/Home/MyView.cshtml")]
|
||||
public void GetGlobalImportLocations_ReturnsPotentialViewStartLocations_PathStartswithSlash(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Views\Home\_GlobalImport.cshtml",
|
||||
@"Views\_GlobalImport.cshtml",
|
||||
@"_GlobalImport.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetGlobalImportLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("~/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("Views/Home/_ViewStart.cshtml")]
|
||||
public void GetViewStartLocations_SkipsCurrentPath_IfCurrentIsViewStart(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Views\_ViewStart.cshtml",
|
||||
@"_ViewStart.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("~/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("Views/Home/_ViewStart.cshtml")]
|
||||
public void GetGlobalImportLocations_WhenCurrentIsViewStart(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Views\Home\_GlobalImport.cshtml",
|
||||
@"Views\_GlobalImport.cshtml",
|
||||
@"_GlobalImport.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetGlobalImportLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/_GlobalImport.cshtml")]
|
||||
[InlineData("~/Views/Home/_GlobalImport.cshtml")]
|
||||
[InlineData("Views/Home/_GlobalImport.cshtml")]
|
||||
public void GetGlobalImportLocations_SkipsCurrentPath_IfCurrentIsGlobalImport(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Views\_GlobalImport.cshtml",
|
||||
@"_GlobalImport.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetGlobalImportLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Test.cshtml")]
|
||||
[InlineData("ViewStart.cshtml")]
|
||||
public void GetViewStartLocations_ReturnsPotentialViewStartLocations(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Areas\MyArea\Sub\Views\Admin\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\Sub\Views\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\Sub\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\_ViewStart.cshtml",
|
||||
@"Areas\_ViewStart.cshtml",
|
||||
@"_ViewStart.cshtml",
|
||||
};
|
||||
var viewPath = Path.Combine("Areas", "MyArea", "Sub", "Views", "Admin", fileName);
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Test.cshtml")]
|
||||
[InlineData("Global.cshtml")]
|
||||
[InlineData("_ViewStart.cshtml")]
|
||||
public void GetGlobalImportLocations_ReturnsPotentialGlobalLocations(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Areas\MyArea\Sub\Views\Admin\_GlobalImport.cshtml",
|
||||
@"Areas\MyArea\Sub\Views\_GlobalImport.cshtml",
|
||||
@"Areas\MyArea\Sub\_GlobalImport.cshtml",
|
||||
@"Areas\MyArea\_GlobalImport.cshtml",
|
||||
@"Areas\_GlobalImport.cshtml",
|
||||
@"_GlobalImport.cshtml",
|
||||
};
|
||||
var viewPath = Path.Combine("Areas", "MyArea", "Sub", "Views", "Admin", fileName);
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetGlobalImportLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("_ViewStart.cshtml")]
|
||||
[InlineData("_viewstart.cshtml")]
|
||||
public void GetViewStartLocations_SkipsCurrentPath_IfPathIsAViewStartFile(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Areas\MyArea\Sub\Views\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\Sub\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\_ViewStart.cshtml",
|
||||
@"Areas\_ViewStart.cshtml",
|
||||
@"_ViewStart.cshtml",
|
||||
};
|
||||
var viewPath = Path.Combine("Areas", "MyArea", "Sub", "Views", "Admin", fileName);
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetViewStartLocations_ReturnsEmptySequence_IfViewStartIsAtRoot()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "_ViewStart.cshtml";
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetViewStartLocations_ReturnsEmptySequence_IfPathIsRooted()
|
||||
{
|
||||
// Arrange
|
||||
var absolutePath = Path.Combine(Directory.GetCurrentDirectory(), "Index.cshtml");
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(absolutePath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetGlobalImportLocations_ReturnsEmptySequence_IfPathIsRooted()
|
||||
{
|
||||
// Arrange
|
||||
var absolutePath = Path.Combine(Directory.GetCurrentDirectory(), "Index.cshtml");
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetGlobalImportLocations(absolutePath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.FileProviders;
|
||||
using Microsoft.Framework.Runtime;
|
||||
using Microsoft.Framework.Runtime.Infrastructure;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class ViewStartUtilityTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public void GetViewStartLocations_ReturnsEmptySequenceIfViewPathIsEmpty(string viewPath)
|
||||
{
|
||||
// Act
|
||||
var result = ViewStartUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/MyView.cshtml")]
|
||||
[InlineData("~/Views/Home/MyView.cshtml")]
|
||||
[InlineData("Views/Home/MyView.cshtml")]
|
||||
public void GetViewStartLocations_ReturnsPotentialViewStartLocations_PathStartswithSlash(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Views\Home\_ViewStart.cshtml",
|
||||
@"Views\_ViewStart.cshtml",
|
||||
@"_ViewStart.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewStartUtility.GetViewStartLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("~/Views/Home/_Viewstart.cshtml")]
|
||||
[InlineData("Views/Home/_Viewstart.cshtml")]
|
||||
public void GetViewStartLocations_SkipsCurrentPath_IfCurrentIsViewStart(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Views\_ViewStart.cshtml",
|
||||
@"_ViewStart.cshtml"
|
||||
};
|
||||
var fileProvider = new PhysicalFileProvider(GetTestFileProviderBase());
|
||||
|
||||
// Act
|
||||
var result = ViewStartUtility.GetViewStartLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Test.cshtml")]
|
||||
[InlineData("ViewStart.cshtml")]
|
||||
public void GetViewStartLocations_ReturnsPotentialViewStartLocations(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Areas\MyArea\Sub\Views\Admin\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\Sub\Views\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\Sub\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\_ViewStart.cshtml",
|
||||
@"Areas\_ViewStart.cshtml",
|
||||
@"_ViewStart.cshtml",
|
||||
};
|
||||
var viewPath = Path.Combine("Areas", "MyArea", "Sub", "Views", "Admin", fileName);
|
||||
|
||||
// Act
|
||||
var result = ViewStartUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("_ViewStart.cshtml")]
|
||||
[InlineData("_viewstart.cshtml")]
|
||||
public void GetViewStartLocations_SkipsCurrentPath_IfPathIsAViewStartFile(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
@"Areas\MyArea\Sub\Views\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\Sub\_ViewStart.cshtml",
|
||||
@"Areas\MyArea\_ViewStart.cshtml",
|
||||
@"Areas\_ViewStart.cshtml",
|
||||
@"_ViewStart.cshtml",
|
||||
};
|
||||
var viewPath = Path.Combine("Areas", "MyArea", "Sub", "Views", "Admin", fileName);
|
||||
|
||||
// Act
|
||||
var result = ViewStartUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetViewStartLocations_ReturnsEmptySequence_IfViewStartIsAtRoot()
|
||||
{
|
||||
// Arrange
|
||||
var appBase = GetTestFileProviderBase();
|
||||
var viewPath = "_ViewStart.cshtml";
|
||||
|
||||
// Act
|
||||
var result = ViewStartUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetViewStartLocations_ReturnsEmptySequence_IfPathIsRooted()
|
||||
{
|
||||
// Arrange
|
||||
var appBase = GetTestFileProviderBase();
|
||||
var absolutePath = Path.Combine(Directory.GetCurrentDirectory(), "Index.cshtml");
|
||||
|
||||
// Act
|
||||
var result = ViewStartUtility.GetViewStartLocations(absolutePath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
private static string GetTestFileProviderBase()
|
||||
{
|
||||
var serviceProvider = CallContextServiceLocator.Locator.ServiceProvider;
|
||||
var appEnv = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment));
|
||||
return Path.Combine(appEnv.ApplicationBasePath, "TestFiles", "ViewStartUtilityFiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ using System.Globalization;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -271,10 +270,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_UsesValueFromCache_IfViewStartHasNotChanged()
|
||||
public void GetOrAdd_UsesValueFromCache_IfGlobalHasNotChanged()
|
||||
{
|
||||
// Arrange
|
||||
var instance = (View)Activator.CreateInstance(typeof(PreCompile));
|
||||
var instance = new PreCompile();
|
||||
var length = Encoding.UTF8.GetByteCount(instance.Content);
|
||||
var fileProvider = new TestFileProvider();
|
||||
|
||||
|
|
@ -288,25 +287,25 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
};
|
||||
fileProvider.AddFile(ViewPath, fileInfo);
|
||||
|
||||
var viewStartContent = "viewstart-content";
|
||||
var viewStartFileInfo = new TestFileInfo
|
||||
var globalContent = "global-content";
|
||||
var globalFileInfo = new TestFileInfo
|
||||
{
|
||||
Content = viewStartContent,
|
||||
Content = globalContent,
|
||||
LastModified = DateTime.UtcNow
|
||||
};
|
||||
fileProvider.AddFile("_ViewStart.cshtml", viewStartFileInfo);
|
||||
var viewStartRazorFileInfo = new RazorFileInfo
|
||||
fileProvider.AddFile("_GlobalImport.cshtml", globalFileInfo);
|
||||
var globalRazorFileInfo = new RazorFileInfo
|
||||
{
|
||||
Hash = Crc32.Calculate(GetMemoryStream(viewStartContent)).ToString(CultureInfo.InvariantCulture),
|
||||
Hash = Crc32.Calculate(GetMemoryStream(globalContent)).ToString(CultureInfo.InvariantCulture),
|
||||
HashAlgorithmVersion = 1,
|
||||
LastModified = viewStartFileInfo.LastModified,
|
||||
Length = viewStartFileInfo.Length,
|
||||
RelativePath = "_ViewStart.cshtml",
|
||||
LastModified = globalFileInfo.LastModified,
|
||||
Length = globalFileInfo.Length,
|
||||
RelativePath = "_GlobalImport.cshtml",
|
||||
FullTypeName = typeof(RuntimeCompileIdentical).FullName
|
||||
};
|
||||
|
||||
var precompiledViews = new ViewCollection();
|
||||
precompiledViews.Add(viewStartRazorFileInfo);
|
||||
precompiledViews.Add(globalRazorFileInfo);
|
||||
var cache = new CompilerCache(new[] { precompiledViews }, fileProvider);
|
||||
|
||||
// Act
|
||||
|
|
@ -378,7 +377,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewStartWasAdedSinceTheCacheWasCreated()
|
||||
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButGlobalImportWasAdedSinceTheCacheWasCreated()
|
||||
{
|
||||
// Arrange
|
||||
var expectedType = typeof(RuntimeCompileDifferent);
|
||||
|
|
@ -408,8 +407,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
var viewStartTrigger = fileProvider.GetTrigger("Views\\_ViewStart.cshtml");
|
||||
viewStartTrigger.IsExpired = true;
|
||||
var globalTrigger = fileProvider.GetTrigger("Views\\_GlobalImport.cshtml");
|
||||
globalTrigger.IsExpired = true;
|
||||
var result2 = cache.GetOrAdd(testFile.PhysicalPath,
|
||||
compile: _ => CompilationResult.Successful(expectedType));
|
||||
|
||||
|
|
@ -421,7 +420,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewStartWasDeletedSinceCacheWasCreated()
|
||||
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButGlobalWasDeletedSinceCacheWasCreated()
|
||||
{
|
||||
// Arrange
|
||||
var expectedType = typeof(RuntimeCompileDifferent);
|
||||
|
|
@ -439,24 +438,24 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
};
|
||||
fileProvider.AddFile(viewFileInfo.PhysicalPath, viewFileInfo);
|
||||
|
||||
var viewStartFileInfo = new TestFileInfo
|
||||
var globalFileInfo = new TestFileInfo
|
||||
{
|
||||
PhysicalPath = "Views\\_ViewStart.cshtml",
|
||||
PhysicalPath = "Views\\_GlobalImport.cshtml",
|
||||
Content = "viewstart-content",
|
||||
LastModified = lastModified
|
||||
};
|
||||
var viewStart = new RazorFileInfo
|
||||
var globalFile = new RazorFileInfo
|
||||
{
|
||||
FullTypeName = typeof(RuntimeCompileIdentical).FullName,
|
||||
RelativePath = viewStartFileInfo.PhysicalPath,
|
||||
LastModified = viewStartFileInfo.LastModified,
|
||||
Hash = RazorFileHash.GetHash(viewStartFileInfo, hashAlgorithmVersion: 1),
|
||||
RelativePath = globalFileInfo.PhysicalPath,
|
||||
LastModified = globalFileInfo.LastModified,
|
||||
Hash = RazorFileHash.GetHash(globalFileInfo, hashAlgorithmVersion: 1),
|
||||
HashAlgorithmVersion = 1,
|
||||
Length = viewStartFileInfo.Length
|
||||
Length = globalFileInfo.Length
|
||||
};
|
||||
fileProvider.AddFile(viewStartFileInfo.PhysicalPath, viewStartFileInfo);
|
||||
fileProvider.AddFile(globalFileInfo.PhysicalPath, globalFileInfo);
|
||||
|
||||
viewCollection.Add(viewStart);
|
||||
viewCollection.Add(globalFile);
|
||||
var cache = new CompilerCache(new[] { viewCollection }, fileProvider);
|
||||
|
||||
// Act 1
|
||||
|
|
@ -470,7 +469,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
var trigger = fileProvider.GetTrigger(viewStartFileInfo.PhysicalPath);
|
||||
var trigger = fileProvider.GetTrigger(globalFileInfo.PhysicalPath);
|
||||
trigger.IsExpired = true;
|
||||
var result2 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
|
||||
compile: _ => CompilationResult.Successful(expectedType));
|
||||
|
|
@ -482,15 +481,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
Assert.Equal(expectedType, actual2.CompiledType);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> GetOrAdd_IgnoresCachedValue_IfViewStartWasChangedSinceCacheWasCreatedData
|
||||
public static IEnumerable<object[]> GetOrAdd_IgnoresCachedValue_IfGlobalWasChangedSinceCacheWasCreatedData
|
||||
{
|
||||
get
|
||||
{
|
||||
var viewStartContent = "viewstart-content";
|
||||
var contentStream = GetMemoryStream(viewStartContent);
|
||||
var globalContent = "global-content";
|
||||
var contentStream = GetMemoryStream(globalContent);
|
||||
var lastModified = DateTime.UtcNow;
|
||||
int length = Encoding.UTF8.GetByteCount(viewStartContent);
|
||||
var path = "Views\\_ViewStart.cshtml";
|
||||
int length = Encoding.UTF8.GetByteCount(globalContent);
|
||||
var path = "Views\\_GlobalImport.cshtml";
|
||||
|
||||
var razorFileInfo = new RazorFileInfo
|
||||
{
|
||||
|
|
@ -522,9 +521,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetOrAdd_IgnoresCachedValue_IfViewStartWasChangedSinceCacheWasCreatedData))]
|
||||
public void GetOrAdd_IgnoresCachedValue_IfViewStartWasChangedSinceCacheWasCreated(
|
||||
RazorFileInfo viewStartRazorFileInfo, TestFileInfo viewStartFileInfo)
|
||||
[MemberData(nameof(GetOrAdd_IgnoresCachedValue_IfGlobalWasChangedSinceCacheWasCreatedData))]
|
||||
public void GetOrAdd_IgnoresCachedValue_IfGlobalFileWasChangedSinceCacheWasCreated(
|
||||
RazorFileInfo viewStartRazorFileInfo, TestFileInfo globalFileInfo)
|
||||
{
|
||||
// Arrange
|
||||
var expectedType = typeof(RuntimeCompileDifferent);
|
||||
|
|
@ -541,7 +540,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(fileInfo.PhysicalPath, fileInfo);
|
||||
fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, viewStartFileInfo);
|
||||
fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, globalFileInfo);
|
||||
var viewCollection = new ViewCollection();
|
||||
var cache = new CompilerCache(new[] { viewCollection }, fileProvider);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
|
||||
|
|
@ -17,9 +17,9 @@ namespace PrecompilationWebSite.Controllers
|
|||
return View("~/Views/ViewsConsumingCompilationOptions/Index");
|
||||
}
|
||||
|
||||
public IActionResult ViewStartDeletedPriorToFirstRequest()
|
||||
public IActionResult GlobalDeletedPriorToFirstRequest()
|
||||
{
|
||||
return View("~/Views/ViewStartDelete/Index");
|
||||
return View("~/Views/GlobalImportDelete/Index");
|
||||
}
|
||||
|
||||
[HttpGet("/Test")]
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
index:@GetType().GetTypeInfo().Assembly.GetName()
|
||||
index:@GetType().GetTypeInfo().Assembly.GetName()
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
Layout:@GetType().GetTypeInfo().Assembly.FullName
|
||||
@RenderBody()
|
||||
@RenderBody()
|
||||
|
|
@ -0,0 +1 @@
|
|||
@using System.Reflection
|
||||
|
|
@ -1,4 +1,2 @@
|
|||
@using System.Reflection
|
||||
@{ Layout = "/Views/Home/Layout.cshtml";}
|
||||
_viewstart:@GetType().GetTypeInfo().Assembly.FullName
|
||||
|
||||
|
|
@ -7,14 +7,14 @@ namespace RazorWebSite
|
|||
{
|
||||
public class DirectivesController : Controller
|
||||
{
|
||||
public ViewResult ViewInheritsInjectAndUsingsFromViewStarts()
|
||||
public ViewResult ViewInheritsInjectAndUsingsFromGlobalImports()
|
||||
{
|
||||
return View(new Person { Name = "Person1" });
|
||||
}
|
||||
|
||||
public ViewResult ViewInheritsBasePageFromViewStarts()
|
||||
public ViewResult ViewInheritsBasePageFromGlobalImports()
|
||||
{
|
||||
return View("/views/directives/scoped/ViewInheritsBasePageFromViewStarts.cshtml",
|
||||
return View("/views/directives/scoped/ViewInheritsBasePageFromGlobalImports.cshtml",
|
||||
new Person { Name = "Person2" });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace RazorWebSite.Controllers
|
||||
{
|
||||
public class NestedGlobalImportsController : Controller
|
||||
{
|
||||
public ViewResult Index()
|
||||
{
|
||||
var model = new Person
|
||||
{
|
||||
Name = "Controller-Person"
|
||||
};
|
||||
|
||||
return View("~/Views/NestedGlobalImports/Nested/Index.cshtml", model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,15 +11,5 @@ namespace RazorWebSite.Controllers
|
|||
{
|
||||
return View("NestedViewStarts/Index");
|
||||
}
|
||||
|
||||
public ViewResult NestedViewStartUsingParentDirectives()
|
||||
{
|
||||
var model = new Person
|
||||
{
|
||||
Name = "Controller-Person"
|
||||
};
|
||||
|
||||
return View("~/Views/NestedViewStartUsingParentDirectives/Nested/Index.cshtml", model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@inherits MyBasePage<TModel>
|
||||
@{
|
||||
Layout = "/Views/Directives/Scoped/_Layout.cshtml";
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
@inherits MyBasePage<TModel>
|
||||
@{
|
||||
@{
|
||||
Layout = "/Views/Directives/Scoped/_Layout.cshtml";
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
@model Person
|
||||
|
|
@ -0,0 +1 @@
|
|||
<view-start>@MyInjectedHelper.Greet(Model)</view-start>
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@model Person
|
||||
<view-start>@MyInjectedHelper.Greet(Model)</view-start>
|
||||
|
|
@ -42,7 +42,7 @@ namespace TagHelpersWebSite.Controllers
|
|||
|
||||
public ViewResult ViewWithInheritedRemoveTagHelper()
|
||||
{
|
||||
return View("/Views/RemoveTagHelperViewStart/ViewWithInheritedRemoveTagHelper.cshtml");
|
||||
return View("/Views/RemoveInheritedTagHelpers/ViewWithInheritedRemoveTagHelper.cshtml");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@removeTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
|
||||
@addTagHelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "~/Views/Shared/_LayoutWithRootTagHelper.cshtml";
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
@{
|
||||
Layout = "~/Views/Shared/_LayoutWithRootTagHelper.cshtml";
|
||||
}
|
||||
@removeTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
|
||||
@addTagHelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"
|
||||
|
|
@ -0,0 +1 @@
|
|||
@addTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
@addTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
|
||||
|
|
|
|||
Loading…
Reference in New Issue