parent
8a4d8c0b59
commit
a801a49377
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="RazorProjectItem"/> that does not exist.
|
||||||
|
/// </summary>
|
||||||
|
public class NotFoundProjectItem : RazorProjectItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="NotFoundProjectItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="basePath">The base path.</param>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
public NotFoundProjectItem(string basePath, string path)
|
||||||
|
{
|
||||||
|
BasePath = basePath;
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string BasePath { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Path { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Exists => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string PhysicalPath => throw new NotSupportedException();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Stream Read() => throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -170,6 +170,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAssemblyCouldNotBeResolved"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAssemblyCouldNotBeResolved"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path must begin with a forward slash '/'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string RazorProject_PathMustStartWithForwardSlash
|
||||||
|
{
|
||||||
|
get { return GetString("RazorProject_PathMustStartWithForwardSlash"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path must begin with a forward slash '/'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRazorProject_PathMustStartWithForwardSlash()
|
||||||
|
{
|
||||||
|
return GetString("RazorProject_PathMustStartWithForwardSlash");
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Evolution
|
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||||
{
|
{
|
||||||
|
|
@ -16,5 +19,82 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
||||||
/// <param name="basePath">The base path.</param>
|
/// <param name="basePath">The base path.</param>
|
||||||
/// <returns>The sequence of <see cref="RazorProjectItem"/>.</returns>
|
/// <returns>The sequence of <see cref="RazorProjectItem"/>.</returns>
|
||||||
public abstract IEnumerable<RazorProjectItem> EnumerateItems(string basePath);
|
public abstract IEnumerable<RazorProjectItem> EnumerateItems(string basePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="RazorProjectItem"/> for the specified path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <returns>The <see cref="RazorProjectItem"/>.</returns>
|
||||||
|
public abstract RazorProjectItem GetItem(string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the sequence of files named <paramref name="fileName"/> that are applicable to the specified path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path of a project item.</param>
|
||||||
|
/// <param name="fileName">The file name to seek.</param>
|
||||||
|
/// <returns>A sequence of applicable <see cref="RazorProjectItem"/> instances.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method returns paths starting from the directory of <paramref name="path"/> and
|
||||||
|
/// traverses to the project root.
|
||||||
|
/// e.g.
|
||||||
|
/// /Views/Home/View.cshtml -> [ /Views/Home/FileName.cshtml, /Views/FileName.cshtml, /FileName.cshtml ]
|
||||||
|
/// </remarks>
|
||||||
|
public virtual IEnumerable<RazorProjectItem> FindHierarchicalItems(string path, string fileName)
|
||||||
|
{
|
||||||
|
EnsureValidPath(path);
|
||||||
|
if (string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(path));
|
||||||
|
if (path.Length == 1)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder;
|
||||||
|
var fileNameIndex = path.LastIndexOf('/');
|
||||||
|
var length = path.Length;
|
||||||
|
Debug.Assert(fileNameIndex != -1);
|
||||||
|
if (string.Compare(path, fileNameIndex + 1, fileName, 0, fileName.Length) == 0)
|
||||||
|
{
|
||||||
|
// If the specified path is for the file hierarchy being constructed, then the first file that applies
|
||||||
|
// to it is in a parent directory.
|
||||||
|
builder = new StringBuilder(path, 0, fileNameIndex, fileNameIndex + fileName.Length);
|
||||||
|
length = fileNameIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder = new StringBuilder(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = length;
|
||||||
|
while (index > 0 && (index = path.LastIndexOf('/', index - 1)) != -1)
|
||||||
|
{
|
||||||
|
builder.Length = index + 1;
|
||||||
|
builder.Append(fileName);
|
||||||
|
|
||||||
|
var itemPath = builder.ToString();
|
||||||
|
yield return GetItem(itemPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs validation for paths passed to methods of <see cref="RazorProject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to validate.</param>
|
||||||
|
protected virtual void EnsureValidPath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path[0] != '/')
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.RazorProject_PathMustStartWithForwardSlash, nameof(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
||||||
/// <returns>The <see cref="Stream"/>.</returns>
|
/// <returns>The <see cref="Stream"/>.</returns>
|
||||||
public abstract Stream Read();
|
public abstract Stream Read();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value that determines if the file exists.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool Exists { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The root relative path of the item.
|
/// The root relative path of the item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -147,4 +147,7 @@
|
||||||
<data name="TagHelperAssemblyCouldNotBeResolved" xml:space="preserve">
|
<data name="TagHelperAssemblyCouldNotBeResolved" xml:space="preserve">
|
||||||
<value>The assembly '{0}' could not be resolved or contains no tag helpers.</value>
|
<value>The assembly '{0}' could not be resolved or contains no tag helpers.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
|
||||||
|
<value>Path must begin with a forward slash '/'.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -124,6 +124,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool Exists => true;
|
||||||
|
|
||||||
public override Stream Read()
|
public override Stream Read()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Evolution
|
||||||
|
{
|
||||||
|
public class RazorProjectTest
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(null)]
|
||||||
|
[InlineData("")]
|
||||||
|
public void EnsureValidPath_ThrowsIfPathIsNullOrEmpty(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var project = new TestRazorProject(new Dictionary<string, RazorProjectItem>());
|
||||||
|
|
||||||
|
// Act and Assert
|
||||||
|
ExceptionAssert.ThrowsArgumentNullOrEmptyString(() => project.EnsureValidPath(path), "path");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("foo")]
|
||||||
|
[InlineData("~/foo")]
|
||||||
|
[InlineData("\\foo")]
|
||||||
|
public void EnsureValidPath_ThrowsIfPathDoesNotStartWithForwardSlash(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var project = new TestRazorProject(new Dictionary<string, RazorProjectItem>());
|
||||||
|
|
||||||
|
// Act and Assert
|
||||||
|
ExceptionAssert.ThrowsArgument(
|
||||||
|
() => project.EnsureValidPath(path),
|
||||||
|
"path",
|
||||||
|
"Path must begin with a forward slash '/'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindHierarchicalItems_ReturnsEmptySequenceIfPathIsAtRoot()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var project = new TestRazorProject(new Dictionary<string, RazorProjectItem>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = project.FindHierarchicalItems("/", "File.cshtml");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("_ViewStart.cshtml")]
|
||||||
|
[InlineData("_ViewImports.cshtml")]
|
||||||
|
public void FindHierarchicalItems_ReturnsItemsForPath(string fileName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/Views/Home/Index.cshtml";
|
||||||
|
var items = new Dictionary<string, RazorProjectItem>
|
||||||
|
{
|
||||||
|
{ $"/{fileName}", CreateProjectItem($"/{fileName}") },
|
||||||
|
{ $"/Views/{fileName}", CreateProjectItem($"/Views/{fileName}") },
|
||||||
|
{ $"/Views/Home/{fileName}", CreateProjectItem($"/Views/Home/{fileName}") },
|
||||||
|
};
|
||||||
|
var project = new TestRazorProject(items);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = project.FindHierarchicalItems(path, $"{fileName}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
result,
|
||||||
|
item => Assert.Equal($"/Views/Home/{fileName}", item.Path),
|
||||||
|
item => Assert.Equal($"/Views/{fileName}", item.Path),
|
||||||
|
item => Assert.Equal($"/{fileName}", item.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindHierarchicalItems_ReturnsItemsForPathAtRoot()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/Index.cshtml";
|
||||||
|
var items = new Dictionary<string, RazorProjectItem>
|
||||||
|
{
|
||||||
|
{ "/File.cshtml", CreateProjectItem("/File.cshtml") },
|
||||||
|
};
|
||||||
|
var project = new TestRazorProject(items);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = project.FindHierarchicalItems(path, "File.cshtml");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
result,
|
||||||
|
item => Assert.Equal("/File.cshtml", item.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindHierarchicalItems_DoesNotIncludePassedInItem()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/Areas/MyArea/Views/Home/File.cshtml";
|
||||||
|
var items = new Dictionary<string, RazorProjectItem>
|
||||||
|
{
|
||||||
|
{ "/Areas/MyArea/Views/Home/File.cshtml", CreateProjectItem("/Areas/MyArea/Views/Home/File.cshtml") },
|
||||||
|
{ "/Areas/MyArea/Views/File.cshtml", CreateProjectItem("/Areas/MyArea/Views/File.cshtml") },
|
||||||
|
{ "/Areas/MyArea/File.cshtml", CreateProjectItem("/Areas/MyArea/File.cshtml") },
|
||||||
|
{ "/Areas/File.cshtml", CreateProjectItem("/Areas/File.cshtml") },
|
||||||
|
{ "/File.cshtml", CreateProjectItem("/File.cshtml") },
|
||||||
|
};
|
||||||
|
var project = new TestRazorProject(items);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = project.FindHierarchicalItems(path, "File.cshtml");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
result,
|
||||||
|
item => Assert.Equal("/Areas/MyArea/Views/File.cshtml", item.Path),
|
||||||
|
item => Assert.Equal("/Areas/MyArea/File.cshtml", item.Path),
|
||||||
|
item => Assert.Equal("/Areas/File.cshtml", item.Path),
|
||||||
|
item => Assert.Equal("/File.cshtml", item.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindHierarchicalItems_ReturnsEmptySequenceIfPassedInItemWithFileNameIsAtRoot()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/File.cshtml";
|
||||||
|
var items = new Dictionary<string, RazorProjectItem>
|
||||||
|
{
|
||||||
|
{ "/File.cshtml", CreateProjectItem("/File.cshtml") },
|
||||||
|
};
|
||||||
|
var project = new TestRazorProject(items);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = project.FindHierarchicalItems(path, "File.cshtml");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindHierarchicalItems_IncludesNonExistentFiles()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/Areas/MyArea/Views/Home/Test.cshtml";
|
||||||
|
var items = new Dictionary<string, RazorProjectItem>
|
||||||
|
{
|
||||||
|
{ "/Areas/MyArea/File.cshtml", CreateProjectItem("/Areas/MyArea/File.cshtml") },
|
||||||
|
{ "/File.cshtml", CreateProjectItem("/File.cshtml") },
|
||||||
|
};
|
||||||
|
var project = new TestRazorProject(items);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = project.FindHierarchicalItems(path, "File.cshtml");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
result,
|
||||||
|
item =>
|
||||||
|
{
|
||||||
|
Assert.Equal("/Areas/MyArea/Views/Home/File.cshtml", item.Path);
|
||||||
|
Assert.False(item.Exists);
|
||||||
|
},
|
||||||
|
item =>
|
||||||
|
{
|
||||||
|
Assert.Equal("/Areas/MyArea/Views/File.cshtml", item.Path);
|
||||||
|
Assert.False(item.Exists);
|
||||||
|
},
|
||||||
|
item =>
|
||||||
|
{
|
||||||
|
Assert.Equal("/Areas/MyArea/File.cshtml", item.Path);
|
||||||
|
Assert.True(item.Exists);
|
||||||
|
},
|
||||||
|
item =>
|
||||||
|
{
|
||||||
|
Assert.Equal("/Areas/File.cshtml", item.Path);
|
||||||
|
Assert.False(item.Exists);
|
||||||
|
},
|
||||||
|
item =>
|
||||||
|
{
|
||||||
|
Assert.Equal("/File.cshtml", item.Path);
|
||||||
|
Assert.True(item.Exists);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private RazorProjectItem CreateProjectItem(string path)
|
||||||
|
{
|
||||||
|
var projectItem = new Mock<RazorProjectItem>();
|
||||||
|
projectItem.SetupGet(f => f.Path).Returns(path);
|
||||||
|
projectItem.SetupGet(f => f.Exists).Returns(true);
|
||||||
|
return projectItem.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestRazorProject : RazorProject
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, RazorProjectItem> _items;
|
||||||
|
|
||||||
|
public TestRazorProject(Dictionary<string, RazorProjectItem> items)
|
||||||
|
{
|
||||||
|
_items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override RazorProjectItem GetItem(string path)
|
||||||
|
{
|
||||||
|
if (!_items.TryGetValue(path, out var item))
|
||||||
|
{
|
||||||
|
item = new NotFoundProjectItem("", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void EnsureValidPath(string path) => base.EnsureValidPath(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue