Addressing breaking IFileSystem changes

@pranavkm
This commit is contained in:
Praburaj 2014-11-26 19:38:36 -08:00 committed by Pranav K
parent f75140765c
commit cfd5630cf2
16 changed files with 172 additions and 81 deletions

View File

@ -105,8 +105,8 @@ namespace Microsoft.AspNet.Mvc
return path;
}
IFileInfo fileInfo = null;
if (fileSystem.TryGetFileInfo(path, out fileInfo))
var fileInfo = fileSystem.GetFileInfo(path);
if (fileInfo.Exists)
{
// The path is relative and IFileSystem found the file, so return the full
// path.

View File

@ -49,20 +49,27 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
var inheritedChunks = new List<Chunk>();
var templateEngine = new RazorTemplateEngine(_razorHost);
foreach (var viewStart in ViewStartUtility.GetViewStartLocations(_fileSystem, pagePath))
foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(pagePath))
{
CodeTree codeTree;
IFileInfo fileInfo;
if (_parsedCodeTrees.TryGetValue(viewStart, out codeTree))
if (_parsedCodeTrees.TryGetValue(viewStartPath, out codeTree))
{
inheritedChunks.AddRange(codeTree.Chunks);
}
else if (_fileSystem.TryGetFileInfo(viewStart, out fileInfo))
else
{
codeTree = ParseViewFile(templateEngine, fileInfo);
_parsedCodeTrees.Add(viewStart, codeTree);
inheritedChunks.AddRange(codeTree.Chunks);
var fileInfo = _fileSystem.GetFileInfo(viewStartPath);
if (fileInfo.Exists)
{
// 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.
codeTree = ParseViewFile(templateEngine, fileInfo, viewStartPath);
_parsedCodeTrees.Add(viewStartPath, codeTree);
inheritedChunks.AddRange(codeTree.Chunks);
}
}
}
@ -118,18 +125,19 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
}
private static CodeTree ParseViewFile(RazorTemplateEngine engine,
IFileInfo fileInfo)
IFileInfo fileInfo,
string viewStartPath)
{
using (var stream = fileInfo.CreateReadStream())
{
using (var streamReader = new StreamReader(stream))
{
var parseResults = engine.ParseTemplate(streamReader, fileInfo.PhysicalPath);
var parseResults = engine.ParseTemplate(streamReader, viewStartPath);
var className = ParserHelpers.SanitizeClassName(fileInfo.Name);
var language = engine.Host.CodeLanguage;
var codeGenerator = language.CreateCodeGenerator(className,
engine.Host.DefaultNamespace,
fileInfo.PhysicalPath,
viewStartPath,
engine.Host);
codeGenerator.Visit(parseResults);

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNet.FileSystems;
namespace Microsoft.AspNet.Mvc.Razor
{
@ -27,41 +26,54 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <summary>
/// Gets the view start locations that are applicable to the specified path.
/// </summary>
/// <param name="applicationBase">The base of the application.</param>
/// <param name="path">The path to locate view starts for.</param>
/// <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="path"/> and moves
/// upwards until it hits the application root.
/// 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(IFileSystem fileSystem, string path)
public static IEnumerable<string> GetViewStartLocations(string applicationRelativePath)
{
if (string.IsNullOrEmpty(path))
if (string.IsNullOrEmpty(applicationRelativePath))
{
return Enumerable.Empty<string>();
}
if (path.StartsWith("~/", StringComparison.Ordinal))
if (applicationRelativePath.StartsWith("~/", StringComparison.Ordinal))
{
path = path.Substring(1);
applicationRelativePath = applicationRelativePath.Substring(2);
}
if (string.Equals(ViewStartFileName, Path.GetFileName(path), StringComparison.OrdinalIgnoreCase))
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.
if (!fileSystem.TryGetParentPath(path, out path))
applicationRelativePath = Path.GetDirectoryName(applicationRelativePath);
if (string.IsNullOrEmpty(applicationRelativePath))
{
return Enumerable.Empty<string>();
}
}
var viewStartLocations = new List<string>();
while (fileSystem.TryGetParentPath(path, out path))
while (!string.IsNullOrEmpty(applicationRelativePath))
{
var viewStartPath = Path.Combine(path, ViewStartFileName);
applicationRelativePath = Path.GetDirectoryName(applicationRelativePath);
var viewStartPath = Path.Combine(applicationRelativePath, ViewStartFileName);
viewStartLocations.Add(viewStartPath);
}

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
else
{
_fileSystem.TryGetFileInfo(virtualPath, out fileInfo);
fileInfo = _fileSystem.GetFileInfo(virtualPath);
expiringFileInfo = new ExpiringFileInfo()
{

View File

@ -77,10 +77,10 @@ namespace Microsoft.AspNet.Mvc.Razor
private IEnumerable<RelativeFileInfo> GetFileInfosRecursive(string currentPath)
{
IEnumerable<IFileInfo> fileInfos;
string path = currentPath;
if (!_fileSystem.TryGetDirectoryContents(path, out fileInfos))
var fileInfos = _fileSystem.GetDirectoryContents(path);
if (!fileInfos.Exists)
{
yield break;
}

View File

@ -4,28 +4,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.FileSystems;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <inheritdoc />
public class ViewStartProvider : IViewStartProvider
{
private readonly IFileSystem _fileSystem;
private readonly IRazorPageFactory _pageFactory;
public ViewStartProvider(IRazorPageFactory pageFactory,
IOptions<RazorViewEngineOptions> optionsAccessor)
public ViewStartProvider(IRazorPageFactory pageFactory)
{
_fileSystem = optionsAccessor.Options.FileSystem;
_pageFactory = pageFactory;
}
/// <inheritdoc />
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path)
{
var viewStartLocations = ViewStartUtility.GetViewStartLocations(_fileSystem, path);
var viewStartLocations = ViewStartUtility.GetViewStartLocations(path);
var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance)
.Where(p => p != null)
.ToArray();

View File

@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var fileInfo = _fileInfoCache.GetFileInfo(relativePath);
if (fileInfo != null)
if (fileInfo.Exists)
{
var relativeFileInfo = new RelativeFileInfo()
{

View File

@ -215,5 +215,26 @@ component-content";
// Assert
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()
{
// Arrange
var expected =
@"<view-start>Hello Controller-Person</view-start>
<page>Hello Controller-Person</page>";
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
var target = "http://localhost/NestedViewStarts/NestedViewStartUsingParentDirectives";
// Act
var body = await client.GetStringAsync(target);
// Assert
Assert.Equal(expected, body.Trim());
}
}
}

View File

@ -13,10 +13,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
{
// Arrange
var fileSystem = new TestFileSystem();
fileSystem.AddFile(@"x:\myapproot\views\accounts\_viewstart.cshtml", "@using AccountModels");
fileSystem.AddFile(@"x:\myapproot\views\Shared\_viewstart.cshtml", "@inject SharedHelper Shared");
fileSystem.AddFile(@"x:\myapproot\views\home\_viewstart.cshtml", "@using MyNamespace");
fileSystem.AddFile(@"x:\myapproot\views\_viewstart.cshtml",
fileSystem.AddFile(@"views\accounts\_viewstart.cshtml", "@using AccountModels");
fileSystem.AddFile(@"views\Shared\_viewstart.cshtml", "@inject SharedHelper Shared");
fileSystem.AddFile(@"views\home\_viewstart.cshtml", "@using MyNamespace");
fileSystem.AddFile(@"views\_viewstart.cshtml",
@"@inject MyHelper<TModel> Helper
@inherits MyBaseType
@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
var utility = new ChunkInheritanceUtility(host, fileSystem, new Chunk[0]);
// Act
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
var chunks = utility.GetInheritedChunks(@"views\home\Index.cshtml");
// Assert
Assert.Equal(8, chunks.Count);
@ -57,14 +57,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
{
// Arrange
var fileSystem = new TestFileSystem();
fileSystem.AddFile(@"x:\myapproot\_viewstart.cs", string.Empty);
fileSystem.AddFile(@"x:\myapproot\views\_Layout.cshtml", string.Empty);
fileSystem.AddFile(@"x:\myapproot\views\home\_not-viewstart.cshtml", string.Empty);
fileSystem.AddFile(@"_viewstart.cs", string.Empty);
fileSystem.AddFile(@"views\_Layout.cshtml", string.Empty);
fileSystem.AddFile(@"views\home\_not-viewstart.cshtml", string.Empty);
var host = new MvcRazorHost(fileSystem);
var utility = new ChunkInheritanceUtility(host, fileSystem, new Chunk[0]);
// Act
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
var chunks = utility.GetInheritedChunks(@"views\home\Index.cshtml");
// Assert
Assert.Empty(chunks);
@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
{
// Arrange
var fileSystem = new TestFileSystem();
fileSystem.AddFile(@"x:\myapproot\views\_viewstart.cshtml",
fileSystem.AddFile(@"views\_viewstart.cshtml",
"@inject DifferentHelper<TModel> Html");
var host = new MvcRazorHost(fileSystem);
var defaultChunks = new Chunk[]
@ -86,7 +86,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks);
// Act
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
var chunks = utility.GetInheritedChunks(@"views\Home\Index.cshtml");
// Assert
Assert.Equal(4, chunks.Count);

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.Razor
private readonly Dictionary<string, IFileInfo> _lookup =
new Dictionary<string, IFileInfo>(StringComparer.Ordinal);
public bool TryGetDirectoryContents(string subpath, out IEnumerable<IFileInfo> contents)
public IDirectoryContents GetDirectoryContents(string subpath)
{
throw new NotImplementedException();
}
@ -29,6 +29,8 @@ namespace Microsoft.AspNet.Mvc.Razor
.Returns(path);
fileInfo.SetupGet(f => f.Name)
.Returns(Path.GetFileName(path));
fileInfo.SetupGet(f => f.Exists)
.Returns(true);
AddFile(path, fileInfo.Object);
}
@ -37,15 +39,16 @@ namespace Microsoft.AspNet.Mvc.Razor
_lookup.Add(path, contents);
}
public bool TryGetFileInfo(string subpath, out IFileInfo fileInfo)
public IFileInfo GetFileInfo(string subpath)
{
return _lookup.TryGetValue(subpath, out fileInfo);
}
public bool TryGetParentPath(string subpath, out string parentPath)
{
parentPath = Path.GetDirectoryName(subpath);
return !string.IsNullOrEmpty(parentPath);
if (_lookup.ContainsKey(subpath))
{
return _lookup[subpath];
}
else
{
return new NotFoundFileInfo(subpath);
}
}
}
}

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.Razor
public void GetViewStartLocations_ReturnsEmptySequenceIfViewPathIsEmpty(string viewPath)
{
// Act
var result = ViewStartUtility.GetViewStartLocations(new TestFileSystem(), viewPath);
var result = ViewStartUtility.GetViewStartLocations(viewPath);
// Assert
Assert.Empty(result);
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Razor
[InlineData("/views/Home/MyView.cshtml")]
[InlineData("~/views/Home/MyView.cshtml")]
[InlineData("views/Home/MyView.cshtml")]
public void GetViewStartLocations_ReturnsPotentialViewStartLocations(string inputPath)
public void GetViewStartLocations_ReturnsPotentialViewStartLocations_PathStartswithSlash(string inputPath)
{
// Arrange
var expected = new[]
@ -36,10 +36,9 @@ namespace Microsoft.AspNet.Mvc.Razor
@"views\_viewstart.cshtml",
@"_viewstart.cshtml"
};
var fileSystem = new PhysicalFileSystem(GetTestFileSystemBase());
// Act
var result = ViewStartUtility.GetViewStartLocations(fileSystem, inputPath);
var result = ViewStartUtility.GetViewStartLocations(inputPath);
// Assert
Assert.Equal(expected, result);
@ -60,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var fileSystem = new PhysicalFileSystem(GetTestFileSystemBase());
// Act
var result = ViewStartUtility.GetViewStartLocations(fileSystem, inputPath);
var result = ViewStartUtility.GetViewStartLocations(inputPath);
// Assert
Assert.Equal(expected, result);
@ -69,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.Razor
[Theory]
[InlineData("Test.cshtml")]
[InlineData("ViewStart.cshtml")]
public void GetViewStartLocations_ReturnsPotentialViewStartLocations_IfPathIsAbsolute(string fileName)
public void GetViewStartLocations_ReturnsPotentialViewStartLocations(string fileName)
{
// Arrange
var expected = new[]
@ -81,12 +80,10 @@ namespace Microsoft.AspNet.Mvc.Razor
@"Areas\_viewstart.cshtml",
@"_viewstart.cshtml",
};
var appBase = GetTestFileSystemBase();
var viewPath = Path.Combine(appBase, "Areas", "MyArea", "Sub", "Views", "Admin", fileName);
var fileSystem = new PhysicalFileSystem(appBase);
var viewPath = Path.Combine("Areas", "MyArea", "Sub", "Views", "Admin", fileName);
// Act
var result = ViewStartUtility.GetViewStartLocations(fileSystem, viewPath);
var result = ViewStartUtility.GetViewStartLocations(viewPath);
// Assert
Assert.Equal(expected, result);
@ -95,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.Razor
[Theory]
[InlineData("_ViewStart.cshtml")]
[InlineData("_viewstart.cshtml")]
public void GetViewStartLocations_SkipsCurrentPath_IfAbsolutePathIsAViewStartFile(string fileName)
public void GetViewStartLocations_SkipsCurrentPath_IfPathIsAViewStartFile(string fileName)
{
// Arrange
var expected = new[]
@ -106,12 +103,10 @@ namespace Microsoft.AspNet.Mvc.Razor
@"Areas\_viewstart.cshtml",
@"_viewstart.cshtml",
};
var appBase = GetTestFileSystemBase();
var viewPath = Path.Combine(appBase, "Areas", "MyArea", "Sub", "Views", "Admin", fileName);
var fileSystem = new PhysicalFileSystem(appBase);
var viewPath = Path.Combine("Areas", "MyArea", "Sub", "Views", "Admin", fileName);
// Act
var result = ViewStartUtility.GetViewStartLocations(fileSystem, viewPath);
var result = ViewStartUtility.GetViewStartLocations(viewPath);
// Assert
Assert.Equal(expected, result);
@ -123,10 +118,23 @@ namespace Microsoft.AspNet.Mvc.Razor
// Arrange
var appBase = GetTestFileSystemBase();
var viewPath = "_viewstart.cshtml";
var fileSystem = new PhysicalFileSystem(appBase);
// Act
var result = ViewStartUtility.GetViewStartLocations(fileSystem, viewPath);
var result = ViewStartUtility.GetViewStartLocations(viewPath);
// Assert
Assert.Empty(result);
}
[Fact]
public void GetViewStartLocations_ReturnsEmptySequence_IfPathIsRooted()
{
// Arrange
var appBase = GetTestFileSystemBase();
var absolutePath = Path.Combine(Directory.GetCurrentDirectory(), "Index.cshtml");
// Act
var result = ViewStartUtility.GetViewStartLocations(absolutePath);
// Assert
Assert.Empty(result);

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNet.FileSystems;
using Microsoft.Framework.Expiration.Interfaces;
using Microsoft.Framework.OptionsModel;
using Moq;
using Xunit;
@ -354,28 +355,25 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
public bool TryGetDirectoryContents(string subpath, out IEnumerable<IFileInfo> contents)
public IDirectoryContents GetDirectoryContents(string subpath)
{
throw new NotImplementedException();
}
public bool TryGetFileInfo(string subpath, out IFileInfo fileInfo)
public IFileInfo GetFileInfo(string subpath)
{
IFileInfo knownInfo;
if (_fileInfos.TryGetValue(subpath, out knownInfo))
{
fileInfo = new DummyFileInfo()
return new DummyFileInfo()
{
Name = knownInfo.Name,
LastModified = knownInfo.LastModified,
};
return true;
}
else
{
fileInfo = null;
return false;
return new NotFoundFileInfo(subpath);
}
}
@ -393,7 +391,39 @@ namespace Microsoft.AspNet.Mvc.Razor
public long Length { get { throw new NotImplementedException(); } }
public bool IsDirectory { get { throw new NotImplementedException(); } }
public string PhysicalPath { get { throw new NotImplementedException(); } }
public bool Exists
{
get
{
throw new NotImplementedException();
}
}
public bool IsReadOnly
{
get
{
throw new NotImplementedException();
}
}
public Stream CreateReadStream() { throw new NotImplementedException(); }
public void WriteContent(byte[] content)
{
throw new NotImplementedException();
}
public void Delete()
{
throw new NotImplementedException();
}
public IExpirationTrigger CreateFileChangeTrigger()
{
throw new NotImplementedException();
}
}
}
}

View File

@ -11,5 +11,15 @@ 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 @@
<page>@MyInjectedHelper.Greet(Model)</page>

View File

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

View File

@ -0,0 +1 @@
@inject InjectedHelper MyInjectedHelper