* Introduce _GlobalImports to inherit directives.

* Remove inheritance from _ViewStarts

Fixes #825
This commit is contained in:
Pranav K 2015-02-16 08:59:45 -08:00
parent 966bfeb8c1
commit eb7b0d6ae3
47 changed files with 541 additions and 407 deletions

View File

@ -1,5 +1,3 @@
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers" @{
@{
Layout = "_Layout"; Layout = "_Layout";
} }

View File

@ -13,8 +13,7 @@ using Microsoft.AspNet.Razor.Parser;
namespace Microsoft.AspNet.Mvc.Razor.Directives namespace Microsoft.AspNet.Mvc.Razor.Directives
{ {
/// <summary> /// <summary>
/// A utility type for supporting inheritance of tag helpers and chunks into a page from applicable _ViewStart /// A utility type for supporting inheritance of directives into a page from applicable <c>_GlobalImport</c> pages.
/// pages.
/// </summary> /// </summary>
public class ChunkInheritanceUtility public class ChunkInheritanceUtility
{ {
@ -25,9 +24,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="ChunkInheritanceUtility"/>. /// Initializes a new instance of <see cref="ChunkInheritanceUtility"/>.
/// </summary> /// </summary>
/// <param name="razorHost">The <see cref="MvcRazorHost"/> used to parse _ViewStart pages.</param> /// <param name="razorHost">The <see cref="MvcRazorHost"/> used to parse <c>_GlobalImport</c> pages.</param>
/// <param name="codeTreeCache"><see cref="ICodeTreeCache"/> that caches _ViewStart <see cref="CodeTree"/> /// <param name="codeTreeCache"><see cref="ICodeTreeCache"/> that caches <see cref="CodeTree"/> instances.
/// instances.</param> /// </param>
/// <param name="defaultInheritedChunks">Sequence of <see cref="Chunk"/>s inherited by default.</param> /// <param name="defaultInheritedChunks">Sequence of <see cref="Chunk"/>s inherited by default.</param>
public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost, public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost,
[NotNull] ICodeTreeCache codeTreeCache, [NotNull] ICodeTreeCache codeTreeCache,
@ -39,25 +38,28 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
} }
/// <summary> /// <summary>
/// Gets an ordered <see cref="IReadOnlyList{T}"/> of parsed <see cref="CodeTree"/> for each _ViewStart that /// Gets an ordered <see cref="IReadOnlyList{T}"/> of parsed <see cref="CodeTree"/> for each
/// is applicable to the page located at <paramref name="pagePath"/>. The list is ordered so that the /// <c>_GlobalImport</c> that is applicable to the page located at <paramref name="pagePath"/>. The list is
/// <see cref="CodeTree"/> for the _ViewStart closest to the <paramref name="pagePath"/> in the fileProvider /// ordered so that the <see cref="CodeTree"/> for the <c>_GlobalImport</c> closest to the
/// appears first. /// <paramref name="pagePath"/> in the file system appears first.
/// </summary> /// </summary>
/// <param name="pagePath">The path of the page to locate inherited chunks for.</param> /// <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) public IReadOnlyList<CodeTree> GetInheritedCodeTrees([NotNull] string pagePath)
{ {
var inheritedCodeTrees = new List<CodeTree>(); var inheritedCodeTrees = new List<CodeTree>();
var templateEngine = new RazorTemplateEngine(_razorHost); 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. // globalImportPath contains the app-relative path of the _GlobalImport.
// Since the parsing of a _ViewStart would cause parent _ViewStarts to be parsed // 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 GetViewStartLocations // we need to ensure the paths are app-relative to allow the GetGlobalFileLocations
// for the current _ViewStart to succeed. // for the current _GlobalImport to succeed.
var codeTree = _codeTreeCache.GetOrAdd(viewStartPath, var codeTree = _codeTreeCache.GetOrAdd(globalImportPath,
fileInfo => ParseViewFile(templateEngine, fileInfo, viewStartPath)); fileInfo => ParseViewFile(templateEngine,
fileInfo,
globalImportPath));
if (codeTree != null) if (codeTree != null)
{ {
@ -70,10 +72,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
/// <summary> /// <summary>
/// Merges <see cref="Chunk"/> inherited by default and <see cref="CodeTree"/> instances produced by parsing /// 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> /// </summary>
/// <param name="codeTree">The <see cref="CodeTree"/> to merge in to.</param> /// <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> /// files.</param>
/// <param name="defaultModel">The list of chunks to merge.</param> /// <param name="defaultModel">The list of chunks to merge.</param>
public void MergeInheritedCodeTrees([NotNull] CodeTree codeTree, 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. // 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 // During this phase, the merger can either add to the CodeTree or ignore the chunk based on the merging
// rules. // 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 // and the furthest one last. This allows the merger to ignore a directive like @model that was previously
// seen. // seen.
var chunksToMerge = inheritedCodeTrees.SelectMany(tree => tree.Chunks) var chunksToMerge = inheritedCodeTrees.SelectMany(tree => tree.Chunks)

View File

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

View File

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

View File

@ -57,17 +57,17 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
} }
// Set up ViewStarts // Set up _GlobalImports
foreach (var entry in cacheEntries) foreach (var entry in cacheEntries)
{ {
var viewStartLocations = ViewStartUtility.GetViewStartLocations(entry.RelativePath); var globalFileLocations = ViewHierarchyUtility.GetGlobalImportLocations(entry.RelativePath);
foreach (var location in viewStartLocations) foreach (var location in globalFileLocations)
{ {
var viewStartEntry = _cache.Get<CompilerCacheEntry>(location); var globalFileEntry = _cache.Get<CompilerCacheEntry>(location);
if (viewStartEntry != null) if (globalFileEntry != null)
{ {
// Add the the composite _ViewStart entry as a dependency. // Add the the composite _GlobalImport entry as a dependency.
entry.AssociatedViewStartEntry = viewStartEntry; entry.AssociatedGlobalFileEntry = globalFileEntry;
break; break;
} }
} }
@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Razor
else if (cacheEntry.IsPreCompiled && !cacheEntry.IsValidatedPreCompiled) 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 // 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 // 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. // rely on expiration triggers ensuring the validity of the entry.
@ -123,9 +123,9 @@ namespace Microsoft.AspNet.Mvc.Razor
return OnCacheMiss(relativeFileInfo, normalizedPath, compile); 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); return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
} }
@ -192,8 +192,8 @@ namespace Microsoft.AspNet.Mvc.Razor
var entry = (CompilerCacheEntry)cacheSetContext.State; var entry = (CompilerCacheEntry)cacheSetContext.State;
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(entry.RelativePath)); cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(entry.RelativePath));
var viewStartLocations = ViewStartUtility.GetViewStartLocations(cacheSetContext.Key); var globalImportPaths = ViewHierarchyUtility.GetGlobalImportLocations(cacheSetContext.Key);
foreach (var location in viewStartLocations) foreach (var location in globalImportPaths)
{ {
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(location)); cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(location));
} }
@ -201,31 +201,31 @@ namespace Microsoft.AspNet.Mvc.Razor
return entry; return entry;
} }
private bool AssociatedViewStartsChanged(CompilerCacheEntry entry, private bool AssociatedGlobalFilesChanged(CompilerCacheEntry entry,
Func<RelativeFileInfo, CompilationResult> compile) Func<RelativeFileInfo, CompilationResult> compile)
{ {
var viewStartEntry = GetCompositeViewStartEntry(entry.RelativePath, compile); var globalFileEntry = GetCompositeGlobalFileEntry(entry.RelativePath, compile);
return entry.AssociatedViewStartEntry != viewStartEntry; return entry.AssociatedGlobalFileEntry != globalFileEntry;
} }
// Returns the entry for the nearest _ViewStart that the file inherits directives from. Since _ViewStart // Returns the entry for the nearest _GlobalImport that the file inherits directives from. Since _GlobalImport
// entries are affected by other _ViewStart entries that are in the path hierarchy, the returned value // 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 _ViewStart entries. // represents the composite result of performing a cache check on individual _GlobalImport entries.
private CompilerCacheEntry GetCompositeViewStartEntry(string relativePath, private CompilerCacheEntry GetCompositeGlobalFileEntry(string relativePath,
Func<RelativeFileInfo, CompilationResult> compile) Func<RelativeFileInfo, CompilationResult> compile)
{ {
var viewStartLocations = ViewStartUtility.GetViewStartLocations(relativePath); var globalImportLocations = ViewHierarchyUtility.GetGlobalImportLocations(relativePath);
foreach (var viewStartLocation in viewStartLocations) foreach (var globalImport in globalImportLocations)
{ {
var getOrAddResult = GetOrAddCore(viewStartLocation, compile); var getOrAddResult = GetOrAddCore(globalImport, compile);
if (getOrAddResult != null) if (getOrAddResult != null)
{ {
// This is the nearest _ViewStart that exists on disk. // This is the nearest _GlobalImport that exists on disk.
return getOrAddResult.CompilerCacheEntry; return getOrAddResult.CompilerCacheEntry;
} }
} }
// No _ViewStarts discovered. // No _GlobalImports discovered.
return null; return null;
} }
@ -240,8 +240,7 @@ namespace Microsoft.AspNet.Mvc.Razor
return path; return path;
} }
internal static IEnumerable<RazorFileInfoCollection> internal static IEnumerable<RazorFileInfoCollection> GetFileInfos(IEnumerable<Assembly> assemblies)
GetFileInfos(IEnumerable<Assembly> assemblies)
{ {
return assemblies.SelectMany(a => a.ExportedTypes) return assemblies.SelectMany(a => a.ExportedTypes)
.Where(Match) .Where(Match)

View File

@ -78,10 +78,10 @@ namespace Microsoft.AspNet.Mvc.Razor
public bool IsPreCompiled { get; } public bool IsPreCompiled { get; }
/// <summary> /// <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. /// depends on.
/// </summary> /// </summary>
public CompilerCacheEntry AssociatedViewStartEntry { get; set; } public CompilerCacheEntry AssociatedGlobalFileEntry { get; set; }
/// <summary> /// <summary>
/// Gets or sets a flag that determines if the validity of this cache entry was performed at runtime. /// Gets or sets a flag that determines if the validity of this cache entry was performed at runtime.

View File

@ -121,9 +121,9 @@ namespace Microsoft.AspNet.Mvc.Razor
if (entry != null) if (entry != null)
{ {
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(fileInfo.RelativePath)); 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));
} }
} }

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path) public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path)
{ {
var viewStartLocations = ViewStartUtility.GetViewStartLocations(path); var viewStartLocations = ViewHierarchyUtility.GetViewStartLocations(path);
var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance) var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance)
.Where(p => p != null) .Where(p => p != null)
.ToArray(); .ToArray();

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient(); var client = server.CreateClient();
// Act // Act
var body = await client.GetStringAsync("http://localhost/Directives/ViewInheritsInjectAndUsingsFromViewStarts"); var body = await client.GetStringAsync("http://localhost/Directives/ViewInheritsInjectAndUsingsFromGlobalImports");
// Assert // Assert
Assert.Equal(expected, body.Trim()); Assert.Equal(expected, body.Trim());
@ -37,7 +37,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient(); var client = server.CreateClient();
// Act // Act
var body = await client.GetStringAsync("http://localhost/Directives/ViewInheritsBasePageFromViewStarts"); var body = await client.GetStringAsync("http://localhost/Directives/ViewInheritsBasePageFromGlobalImports");
// Assert // Assert
Assert.Equal(expected, body.Trim()); Assert.Equal(expected, body.Trim());

View File

@ -32,6 +32,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var layoutContent = File.ReadAllText(Path.Combine(viewsDirectory, "Layout.cshtml")); var layoutContent = File.ReadAllText(Path.Combine(viewsDirectory, "Layout.cshtml"));
var indexContent = File.ReadAllText(Path.Combine(viewsDirectory, "Index.cshtml")); var indexContent = File.ReadAllText(Path.Combine(viewsDirectory, "Index.cshtml"));
var viewstartContent = File.ReadAllText(Path.Combine(viewsDirectory, "_ViewStart.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 server = TestServer.Create(_services, _app);
var client = server.CreateClient(); var client = server.CreateClient();
@ -65,57 +66,54 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(assemblyName, response2.Index); Assert.Equal(assemblyName, response2.Index);
// Act - 3 // 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"); await TouchFile(viewsDirectory, "_ViewStart.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index"); responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 3 // Assert - 3
var response3 = new ParsedResponse(responseContent); var response3 = new ParsedResponse(responseContent);
Assert.NotEqual(assemblyName, response3.ViewStart); Assert.NotEqual(assemblyName, response3.ViewStart);
Assert.NotEqual(assemblyName, response3.Index); Assert.Equal(assemblyName, response3.Index);
Assert.NotEqual(response2.Layout, response3.Layout); Assert.Equal(response2.Layout, response3.Layout);
// Act - 4 // Act - 4
// Touch Index file and verify it is the only page that recompiles. // Touch the _GlobalImport file and verify it causes all files to recompile.
await TouchFile(viewsDirectory, "Index.cshtml"); await TouchFile(viewsDirectory, "_GlobalImport.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index"); responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 4 // Assert - 4
var response4 = new ParsedResponse(responseContent); var response4 = new ParsedResponse(responseContent);
// Layout and _ViewStart should not have changed. Assert.NotEqual(response3.ViewStart, response4.ViewStart);
Assert.Equal(response3.Layout, response4.Layout);
Assert.Equal(response3.ViewStart, response4.ViewStart);
Assert.NotEqual(response3.Index, response4.Index); Assert.NotEqual(response3.Index, response4.Index);
Assert.NotEqual(response3.Layout, response4.Layout);
// Act - 5 // Act - 5
// Touch the _ViewStart file. This time, we'll verify the Non-precompiled -> Non-precompiled workflow. // Touch Index file and verify it is the only page that recompiles.
await TouchFile(viewsDirectory, "_ViewStart.cshtml"); await TouchFile(viewsDirectory, "Index.cshtml");
responseContent = await client.GetStringAsync("http://localhost/Home/Index"); responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 5 // Assert - 5
var response5 = new ParsedResponse(responseContent); var response5 = new ParsedResponse(responseContent);
// Everything should've recompiled. // Layout and _ViewStart should not have changed.
Assert.NotEqual(response4.ViewStart, response5.ViewStart); Assert.Equal(response4.Layout, response5.Layout);
Assert.Equal(response4.ViewStart, response5.ViewStart);
Assert.NotEqual(response4.Index, response5.Index); Assert.NotEqual(response4.Index, response5.Index);
Assert.NotEqual(response4.Layout, response5.Layout);
// Act - 6 // Act - 6
// Add a new _ViewStart file // Touch the _GlobalImport file. This time, we'll verify the Non-precompiled -> Non-precompiled workflow.
File.WriteAllText(Path.Combine(viewsDirectory, "..", "_ViewStart.cshtml"), string.Empty); await TouchFile(viewsDirectory, "_GlobalImport.cshtml");
await Task.Delay(_cacheDelayInterval);
responseContent = await client.GetStringAsync("http://localhost/Home/Index"); responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 6 // Assert - 6
// Everything should've recompiled.
var response6 = new ParsedResponse(responseContent); var response6 = new ParsedResponse(responseContent);
// Everything should've recompiled.
Assert.NotEqual(response5.ViewStart, response6.ViewStart); Assert.NotEqual(response5.ViewStart, response6.ViewStart);
Assert.NotEqual(response5.Index, response6.Index); Assert.NotEqual(response5.Index, response6.Index);
Assert.NotEqual(response5.Layout, response6.Layout); Assert.NotEqual(response5.Layout, response6.Layout);
// Act - 7 // Act - 7
// Remove new _ViewStart file // Add a new _GlobalImport file
File.Delete(Path.Combine(viewsDirectory, "..", "_ViewStart.cshtml")); File.WriteAllText(Path.Combine(viewsDirectory, "..", "_GlobalImport.cshtml"), string.Empty);
await Task.Delay(_cacheDelayInterval);
responseContent = await client.GetStringAsync("http://localhost/Home/Index"); responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 7 // Assert - 7
@ -126,20 +124,33 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.NotEqual(response6.Layout, response7.Layout); Assert.NotEqual(response6.Layout, response7.Layout);
// Act - 8 // 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 // Refetch and verify we get cached types
responseContent = await client.GetStringAsync("http://localhost/Home/Index"); responseContent = await client.GetStringAsync("http://localhost/Home/Index");
// Assert - 7 // Assert - 9
var response8 = new ParsedResponse(responseContent); var response9 = new ParsedResponse(responseContent);
Assert.Equal(response7.ViewStart, response8.ViewStart); Assert.Equal(response8.ViewStart, response9.ViewStart);
Assert.Equal(response7.Index, response8.Index); Assert.Equal(response8.Index, response9.Index);
Assert.Equal(response7.Layout, response8.Layout); Assert.Equal(response8.Layout, response9.Layout);
} }
finally finally
{ {
File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent); File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent.TrimEnd(' '));
File.WriteAllText(Path.Combine(viewsDirectory, "Index.cshtml"), indexContent); File.WriteAllText(Path.Combine(viewsDirectory, "Index.cshtml"), indexContent.TrimEnd(' '));
File.WriteAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"), viewstartContent); 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] [Fact]
public async Task DeletingPrecompiledViewStart_PriorToFirstRequestToAView_CausesViewToBeRecompiled() public async Task DeletingPrecompiledGlobalFile_PriorToFirstRequestToAView_CausesViewToBeRecompiled()
{ {
// Arrange // Arrange
var expected = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString(); var expected = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString();
@ -177,9 +188,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var applicationEnvironment = _services.GetRequiredService<IApplicationEnvironment>(); var applicationEnvironment = _services.GetRequiredService<IApplicationEnvironment>();
var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath, "Views", "ViewStartDelete"); var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath,
var viewStartPath = Path.Combine(viewsDirectory, "_ViewStart.cshtml"); "Views",
var viewStartContent = File.ReadAllText(viewStartPath); "GlobalImportDelete");
var globalPath = Path.Combine(viewsDirectory, "_GlobalImport.cshtml");
var globalContent = File.ReadAllText(globalPath);
// Act - 1 // Act - 1
// Query the Test view so we know the compiler cache gets populated. // Query the Test view so we know the compiler cache gets populated.
@ -191,14 +204,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
try try
{ {
// Act - 2 // 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 - 2
Assert.NotEqual(expected, response2.Trim()); Assert.NotEqual(expected, response2.Trim());
} }
finally finally
{ {
File.WriteAllText(viewStartPath, viewStartContent); File.WriteAllText(globalPath, globalContent);
} }
} }

View File

@ -286,11 +286,8 @@ View With Layout
Assert.Equal(expected, body.Trim()); 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] [Fact]
public async Task ViewStartsCanUseDirectivesInjectedFromParentViewStarts() public async Task ViewStartsCanUseDirectivesInjectedFromParentGlobals()
{ {
// Arrange // Arrange
var expected = var expected =
@ -298,7 +295,7 @@ View With Layout
<page>Hello Controller-Person</page>"; <page>Hello Controller-Person</page>";
var server = TestServer.Create(_provider, _app); var server = TestServer.Create(_provider, _app);
var client = server.CreateClient(); var client = server.CreateClient();
var target = "http://localhost/NestedViewStarts/NestedViewStartUsingParentDirectives"; var target = "http://localhost/NestedGlobalImports";
// Act // Act
var body = await client.GetStringAsync(target); var body = await client.GetStringAsync(target);

View File

@ -9,14 +9,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
public class ChunkInheritanceUtilityTest public class ChunkInheritanceUtilityTest
{ {
[Fact] [Fact]
public void GetInheritedChunks_ReadsChunksFromViewStartsInPath() public void GetInheritedChunks_ReadsChunksFromGlobalFilesInPath()
{ {
// Arrange // Arrange
var fileProvider = new TestFileProvider(); var fileProvider = new TestFileProvider();
fileProvider.AddFile(@"Views\accounts\_ViewStart.cshtml", "@using AccountModels"); fileProvider.AddFile(@"Views\accounts\_GlobalImport.cshtml", "@using AccountModels");
fileProvider.AddFile(@"Views\Shared\_ViewStart.cshtml", "@inject SharedHelper Shared"); fileProvider.AddFile(@"Views\Shared\_GlobalImport.cshtml", "@inject SharedHelper Shared");
fileProvider.AddFile(@"Views\home\_ViewStart.cshtml", "@using MyNamespace"); fileProvider.AddFile(@"Views\home\_GlobalImport.cshtml", "@using MyNamespace");
fileProvider.AddFile(@"Views\_ViewStart.cshtml", fileProvider.AddFile(@"Views\_GlobalImport.cshtml",
@"@inject MyHelper<TModel> Helper @"@inject MyHelper<TModel> Helper
@inherits MyBaseType @inherits MyBaseType
@ -64,13 +64,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
} }
[Fact] [Fact]
public void GetInheritedChunks_ReturnsEmptySequenceIfNoViewStartsArePresent() public void GetInheritedChunks_ReturnsEmptySequenceIfNoGlobalsArePresent()
{ {
// Arrange // Arrange
var fileProvider = new TestFileProvider(); 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\_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 cache = new DefaultCodeTreeCache(fileProvider);
var host = new MvcRazorHost(cache); var host = new MvcRazorHost(cache);
var defaultChunks = new Chunk[] var defaultChunks = new Chunk[]
@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
{ {
// Arrange // Arrange
var fileProvider = new TestFileProvider(); var fileProvider = new TestFileProvider();
fileProvider.AddFile(@"Views\_ViewStart.cshtml", fileProvider.AddFile(@"Views\_GlobalImport.cshtml",
"@inject DifferentHelper<TModel> Html"); "@inject DifferentHelper<TModel> Html");
var cache = new DefaultCodeTreeCache(fileProvider); var cache = new DefaultCodeTreeCache(fileProvider);
var host = new MvcRazorHost(cache); var host = new MvcRazorHost(cache);

View File

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

View File

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

View File

@ -7,7 +7,6 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Microsoft.AspNet.FileProviders;
using Moq; using Moq;
using Xunit; using Xunit;
@ -271,10 +270,10 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
[Fact] [Fact]
public void GetOrAdd_UsesValueFromCache_IfViewStartHasNotChanged() public void GetOrAdd_UsesValueFromCache_IfGlobalHasNotChanged()
{ {
// Arrange // Arrange
var instance = (View)Activator.CreateInstance(typeof(PreCompile)); var instance = new PreCompile();
var length = Encoding.UTF8.GetByteCount(instance.Content); var length = Encoding.UTF8.GetByteCount(instance.Content);
var fileProvider = new TestFileProvider(); var fileProvider = new TestFileProvider();
@ -288,25 +287,25 @@ namespace Microsoft.AspNet.Mvc.Razor
}; };
fileProvider.AddFile(ViewPath, fileInfo); fileProvider.AddFile(ViewPath, fileInfo);
var viewStartContent = "viewstart-content"; var globalContent = "global-content";
var viewStartFileInfo = new TestFileInfo var globalFileInfo = new TestFileInfo
{ {
Content = viewStartContent, Content = globalContent,
LastModified = DateTime.UtcNow LastModified = DateTime.UtcNow
}; };
fileProvider.AddFile("_ViewStart.cshtml", viewStartFileInfo); fileProvider.AddFile("_GlobalImport.cshtml", globalFileInfo);
var viewStartRazorFileInfo = new RazorFileInfo var globalRazorFileInfo = new RazorFileInfo
{ {
Hash = Crc32.Calculate(GetMemoryStream(viewStartContent)).ToString(CultureInfo.InvariantCulture), Hash = Crc32.Calculate(GetMemoryStream(globalContent)).ToString(CultureInfo.InvariantCulture),
HashAlgorithmVersion = 1, HashAlgorithmVersion = 1,
LastModified = viewStartFileInfo.LastModified, LastModified = globalFileInfo.LastModified,
Length = viewStartFileInfo.Length, Length = globalFileInfo.Length,
RelativePath = "_ViewStart.cshtml", RelativePath = "_GlobalImport.cshtml",
FullTypeName = typeof(RuntimeCompileIdentical).FullName FullTypeName = typeof(RuntimeCompileIdentical).FullName
}; };
var precompiledViews = new ViewCollection(); var precompiledViews = new ViewCollection();
precompiledViews.Add(viewStartRazorFileInfo); precompiledViews.Add(globalRazorFileInfo);
var cache = new CompilerCache(new[] { precompiledViews }, fileProvider); var cache = new CompilerCache(new[] { precompiledViews }, fileProvider);
// Act // Act
@ -378,7 +377,7 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
[Fact] [Fact]
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewStartWasAdedSinceTheCacheWasCreated() public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButGlobalImportWasAdedSinceTheCacheWasCreated()
{ {
// Arrange // Arrange
var expectedType = typeof(RuntimeCompileDifferent); var expectedType = typeof(RuntimeCompileDifferent);
@ -408,8 +407,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Assert.Equal(typeof(PreCompile), actual1.CompiledType); Assert.Equal(typeof(PreCompile), actual1.CompiledType);
// Act 2 // Act 2
var viewStartTrigger = fileProvider.GetTrigger("Views\\_ViewStart.cshtml"); var globalTrigger = fileProvider.GetTrigger("Views\\_GlobalImport.cshtml");
viewStartTrigger.IsExpired = true; globalTrigger.IsExpired = true;
var result2 = cache.GetOrAdd(testFile.PhysicalPath, var result2 = cache.GetOrAdd(testFile.PhysicalPath,
compile: _ => CompilationResult.Successful(expectedType)); compile: _ => CompilationResult.Successful(expectedType));
@ -421,7 +420,7 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
[Fact] [Fact]
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewStartWasDeletedSinceCacheWasCreated() public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButGlobalWasDeletedSinceCacheWasCreated()
{ {
// Arrange // Arrange
var expectedType = typeof(RuntimeCompileDifferent); var expectedType = typeof(RuntimeCompileDifferent);
@ -439,24 +438,24 @@ namespace Microsoft.AspNet.Mvc.Razor
}; };
fileProvider.AddFile(viewFileInfo.PhysicalPath, viewFileInfo); fileProvider.AddFile(viewFileInfo.PhysicalPath, viewFileInfo);
var viewStartFileInfo = new TestFileInfo var globalFileInfo = new TestFileInfo
{ {
PhysicalPath = "Views\\_ViewStart.cshtml", PhysicalPath = "Views\\_GlobalImport.cshtml",
Content = "viewstart-content", Content = "viewstart-content",
LastModified = lastModified LastModified = lastModified
}; };
var viewStart = new RazorFileInfo var globalFile = new RazorFileInfo
{ {
FullTypeName = typeof(RuntimeCompileIdentical).FullName, FullTypeName = typeof(RuntimeCompileIdentical).FullName,
RelativePath = viewStartFileInfo.PhysicalPath, RelativePath = globalFileInfo.PhysicalPath,
LastModified = viewStartFileInfo.LastModified, LastModified = globalFileInfo.LastModified,
Hash = RazorFileHash.GetHash(viewStartFileInfo, hashAlgorithmVersion: 1), Hash = RazorFileHash.GetHash(globalFileInfo, hashAlgorithmVersion: 1),
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); var cache = new CompilerCache(new[] { viewCollection }, fileProvider);
// Act 1 // Act 1
@ -470,7 +469,7 @@ namespace Microsoft.AspNet.Mvc.Razor
Assert.Equal(typeof(PreCompile), actual1.CompiledType); Assert.Equal(typeof(PreCompile), actual1.CompiledType);
// Act 2 // Act 2
var trigger = fileProvider.GetTrigger(viewStartFileInfo.PhysicalPath); var trigger = fileProvider.GetTrigger(globalFileInfo.PhysicalPath);
trigger.IsExpired = true; trigger.IsExpired = true;
var result2 = cache.GetOrAdd(viewFileInfo.PhysicalPath, var result2 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
compile: _ => CompilationResult.Successful(expectedType)); compile: _ => CompilationResult.Successful(expectedType));
@ -482,15 +481,15 @@ namespace Microsoft.AspNet.Mvc.Razor
Assert.Equal(expectedType, actual2.CompiledType); Assert.Equal(expectedType, actual2.CompiledType);
} }
public static IEnumerable<object[]> GetOrAdd_IgnoresCachedValue_IfViewStartWasChangedSinceCacheWasCreatedData public static IEnumerable<object[]> GetOrAdd_IgnoresCachedValue_IfGlobalWasChangedSinceCacheWasCreatedData
{ {
get get
{ {
var viewStartContent = "viewstart-content"; var globalContent = "global-content";
var contentStream = GetMemoryStream(viewStartContent); var contentStream = GetMemoryStream(globalContent);
var lastModified = DateTime.UtcNow; var lastModified = DateTime.UtcNow;
int length = Encoding.UTF8.GetByteCount(viewStartContent); int length = Encoding.UTF8.GetByteCount(globalContent);
var path = "Views\\_ViewStart.cshtml"; var path = "Views\\_GlobalImport.cshtml";
var razorFileInfo = new RazorFileInfo var razorFileInfo = new RazorFileInfo
{ {
@ -522,9 +521,9 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
[Theory] [Theory]
[MemberData(nameof(GetOrAdd_IgnoresCachedValue_IfViewStartWasChangedSinceCacheWasCreatedData))] [MemberData(nameof(GetOrAdd_IgnoresCachedValue_IfGlobalWasChangedSinceCacheWasCreatedData))]
public void GetOrAdd_IgnoresCachedValue_IfViewStartWasChangedSinceCacheWasCreated( public void GetOrAdd_IgnoresCachedValue_IfGlobalFileWasChangedSinceCacheWasCreated(
RazorFileInfo viewStartRazorFileInfo, TestFileInfo viewStartFileInfo) RazorFileInfo viewStartRazorFileInfo, TestFileInfo globalFileInfo)
{ {
// Arrange // Arrange
var expectedType = typeof(RuntimeCompileDifferent); var expectedType = typeof(RuntimeCompileDifferent);
@ -541,7 +540,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var fileProvider = new TestFileProvider(); var fileProvider = new TestFileProvider();
fileProvider.AddFile(fileInfo.PhysicalPath, fileInfo); fileProvider.AddFile(fileInfo.PhysicalPath, fileInfo);
fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, viewStartFileInfo); fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, globalFileInfo);
var viewCollection = new ViewCollection(); var viewCollection = new ViewCollection();
var cache = new CompilerCache(new[] { viewCollection }, fileProvider); var cache = new CompilerCache(new[] { viewCollection }, fileProvider);

View File

@ -0,0 +1 @@
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"

View File

@ -17,9 +17,9 @@ namespace PrecompilationWebSite.Controllers
return View("~/Views/ViewsConsumingCompilationOptions/Index"); return View("~/Views/ViewsConsumingCompilationOptions/Index");
} }
public IActionResult ViewStartDeletedPriorToFirstRequest() public IActionResult GlobalDeletedPriorToFirstRequest()
{ {
return View("~/Views/ViewStartDelete/Index"); return View("~/Views/GlobalImportDelete/Index");
} }
[HttpGet("/Test")] [HttpGet("/Test")]

View File

@ -1 +1 @@
index:@GetType().GetTypeInfo().Assembly.GetName() index:@GetType().GetTypeInfo().Assembly.GetName()

View File

@ -1,2 +1,2 @@
Layout:@GetType().GetTypeInfo().Assembly.FullName Layout:@GetType().GetTypeInfo().Assembly.FullName
@RenderBody() @RenderBody()

View File

@ -0,0 +1 @@
@using System.Reflection

View File

@ -1,4 +1,2 @@
@using System.Reflection
@{ Layout = "/Views/Home/Layout.cshtml";} @{ Layout = "/Views/Home/Layout.cshtml";}
_viewstart:@GetType().GetTypeInfo().Assembly.FullName _viewstart:@GetType().GetTypeInfo().Assembly.FullName

View File

@ -7,14 +7,14 @@ namespace RazorWebSite
{ {
public class DirectivesController : Controller public class DirectivesController : Controller
{ {
public ViewResult ViewInheritsInjectAndUsingsFromViewStarts() public ViewResult ViewInheritsInjectAndUsingsFromGlobalImports()
{ {
return View(new Person { Name = "Person1" }); 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" }); new Person { Name = "Person2" });
} }
} }

View File

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

View File

@ -11,15 +11,5 @@ namespace RazorWebSite.Controllers
{ {
return View("NestedViewStarts/Index"); return View("NestedViewStarts/Index");
} }
public ViewResult NestedViewStartUsingParentDirectives()
{
var model = new Person
{
Name = "Controller-Person"
};
return View("~/Views/NestedViewStartUsingParentDirectives/Nested/Index.cshtml", model);
}
} }
} }

View File

@ -0,0 +1,4 @@
@inherits MyBasePage<TModel>
@{
Layout = "/Views/Directives/Scoped/_Layout.cshtml";
}

View File

@ -1,4 +1,3 @@
@inherits MyBasePage<TModel> @{
@{
Layout = "/Views/Directives/Scoped/_Layout.cshtml"; Layout = "/Views/Directives/Scoped/_Layout.cshtml";
} }

View File

@ -0,0 +1 @@
@model Person

View File

@ -0,0 +1 @@
<view-start>@MyInjectedHelper.Greet(Model)</view-start>

View File

@ -1,2 +0,0 @@
@model Person
<view-start>@MyInjectedHelper.Greet(Model)</view-start>

View File

@ -42,7 +42,7 @@ namespace TagHelpersWebSite.Controllers
public ViewResult ViewWithInheritedRemoveTagHelper() public ViewResult ViewWithInheritedRemoveTagHelper()
{ {
return View("/Views/RemoveTagHelperViewStart/ViewWithInheritedRemoveTagHelper.cshtml"); return View("/Views/RemoveInheritedTagHelpers/ViewWithInheritedRemoveTagHelper.cshtml");
} }
} }
} }

View File

@ -0,0 +1,2 @@
@removeTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
@addTagHelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"

View File

@ -0,0 +1,3 @@
@{
Layout = "~/Views/Shared/_LayoutWithRootTagHelper.cshtml";
}

View File

@ -1,5 +0,0 @@
@{
Layout = "~/Views/Shared/_LayoutWithRootTagHelper.cshtml";
}
@removeTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
@addTagHelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"

View File

@ -0,0 +1 @@
@addTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"

View File

@ -1,4 +1,3 @@
@{ @{
Layout = "/Views/Shared/_Layout.cshtml"; Layout = "/Views/Shared/_Layout.cshtml";
} }
@addTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"