* 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";
}

View File

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

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)
{
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)

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.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);

View File

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

View File

@ -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")]

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
@RenderBody()
@RenderBody()

View File

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

View File

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

View File

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

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

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()
{
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";
}
@addTagHelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"