This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #5353
- Loading branch information
Showing
3 changed files
with
394 additions
and
0 deletions.
There are no files selected for viewing
59 changes: 59 additions & 0 deletions
59
src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProject.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// 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 System.IO; | ||
using Microsoft.AspNetCore.Razor.Evolution; | ||
using Microsoft.Extensions.FileProviders; | ||
|
||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure | ||
{ | ||
public class DefaultRazorProject : RazorProject | ||
{ | ||
private const string RazorFileExtension = ".cshtml"; | ||
private readonly IFileProvider _provider; | ||
|
||
public DefaultRazorProject(IFileProvider provider) | ||
{ | ||
_provider = provider; | ||
} | ||
|
||
public override IEnumerable<RazorProjectItem> EnumerateItems(string path) | ||
{ | ||
if (path == null) | ||
{ | ||
throw new ArgumentNullException(nameof(path)); | ||
} | ||
|
||
if (path.Length == 0 || path[0] != '/') | ||
{ | ||
throw new ArgumentException(Resources.RazorProject_PathMustStartWithForwardSlash); | ||
} | ||
|
||
return EnumerateFiles(_provider.GetDirectoryContents(path), path, ""); | ||
} | ||
|
||
private IEnumerable<RazorProjectItem> EnumerateFiles(IDirectoryContents directory, string basePath, string prefix) | ||
{ | ||
if (directory.Exists) | ||
{ | ||
foreach (var file in directory) | ||
{ | ||
if (file.IsDirectory) | ||
{ | ||
var children = EnumerateFiles(_provider.GetDirectoryContents(file.PhysicalPath), basePath, prefix + "/" + file.Name); | ||
foreach (var child in children) | ||
{ | ||
yield return child; | ||
} | ||
} | ||
else if (string.Equals(RazorFileExtension, Path.GetExtension(file.Name), StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
yield return new DefaultRazorProjectItem(file, basePath, prefix + "/" + file.Name); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProjectItem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// 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.IO; | ||
using Microsoft.AspNetCore.Razor.Evolution; | ||
using Microsoft.Extensions.FileProviders; | ||
|
||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure | ||
{ | ||
public class DefaultRazorProjectItem : RazorProjectItem | ||
{ | ||
private readonly IFileInfo _fileInfo; | ||
|
||
public DefaultRazorProjectItem(IFileInfo fileInfo, string basePath, string path) | ||
{ | ||
_fileInfo = fileInfo; | ||
BasePath = basePath; | ||
Path = path; | ||
} | ||
|
||
public override string BasePath { get; } | ||
|
||
public override string Path { get; } | ||
|
||
public override string PhysicalPath => _fileInfo.PhysicalPath; | ||
|
||
public override Stream Read() | ||
{ | ||
return _fileInfo.CreateReadStream(); | ||
} | ||
} | ||
} |
303 changes: 303 additions & 0 deletions
303
test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorProviderTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,303 @@ | ||
// 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 Microsoft.AspNetCore.Mvc.Abstractions; | ||
using Microsoft.AspNetCore.Mvc.Filters; | ||
using Microsoft.AspNetCore.Mvc.Razor; | ||
using Microsoft.AspNetCore.Mvc.ApplicationModels; | ||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; | ||
using Microsoft.Extensions.Options; | ||
using Moq; | ||
using Xunit; | ||
using Microsoft.AspNetCore.Razor.Evolution; | ||
|
||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal | ||
{ | ||
public class PageActionDescriptorProviderTest | ||
{ | ||
[Fact] | ||
public void GetDescriptors_DoesNotAddDescriptorsForFilesWithoutDirectives() | ||
{ | ||
// Arrange | ||
var razorProject = new Mock<RazorProject>(); | ||
razorProject.Setup(p => p.EnumerateItems("/")) | ||
.Returns(new[] | ||
{ | ||
GetProjectItem("/", "/Index.cshtml", "<h1>Hello world</h1>"), | ||
}); | ||
var provider = new PageActionDescriptorProvider( | ||
razorProject.Object, | ||
GetAccessor<MvcOptions>(), | ||
GetAccessor<RazorPagesOptions>()); | ||
var context = new ActionDescriptorProviderContext(); | ||
|
||
// Act | ||
provider.OnProvidersExecuting(context); | ||
|
||
// Assert | ||
Assert.Empty(context.Results); | ||
} | ||
|
||
[Fact] | ||
public void GetDescriptors_AddsDescriptorsForFileWithPageDirective() | ||
{ | ||
// Arrange | ||
var razorProject = new Mock<RazorProject>(); | ||
razorProject.Setup(p => p.EnumerateItems("/")) | ||
.Returns(new[] | ||
{ | ||
GetProjectItem("/", "/Test.cshtml", $"@page{Environment.NewLine}<h1>Hello world</h1>"), | ||
}); | ||
var provider = new PageActionDescriptorProvider( | ||
razorProject.Object, | ||
GetAccessor<MvcOptions>(), | ||
GetAccessor<RazorPagesOptions>()); | ||
var context = new ActionDescriptorProviderContext(); | ||
|
||
// Act | ||
provider.OnProvidersExecuting(context); | ||
|
||
// Assert | ||
var result = Assert.Single(context.Results); | ||
var descriptor = Assert.IsType<PageActionDescriptor>(result); | ||
Assert.Equal("/Test.cshtml", descriptor.RelativePath); | ||
Assert.Equal("/Test", descriptor.RouteValues["page"]); | ||
Assert.Equal("Test", descriptor.AttributeRouteInfo.Template); | ||
} | ||
|
||
[Fact] | ||
public void GetDescriptors_AddsDescriptorsForFileWithPageDirectiveAndRouteTemplate() | ||
{ | ||
// Arrange | ||
var razorProject = new Mock<RazorProject>(); | ||
razorProject.Setup(p => p.EnumerateItems("/")) | ||
.Returns(new[] | ||
{ | ||
GetProjectItem("/", "/Test.cshtml", $"@page Home {Environment.NewLine}<h1>Hello world</h1>"), | ||
}); | ||
var provider = new PageActionDescriptorProvider( | ||
razorProject.Object, | ||
GetAccessor<MvcOptions>(), | ||
GetAccessor<RazorPagesOptions>()); | ||
var context = new ActionDescriptorProviderContext(); | ||
|
||
// Act | ||
provider.OnProvidersExecuting(context); | ||
|
||
// Assert | ||
var result = Assert.Single(context.Results); | ||
var descriptor = Assert.IsType<PageActionDescriptor>(result); | ||
Assert.Equal("/Test.cshtml", descriptor.RelativePath); | ||
Assert.Equal("/Test", descriptor.RouteValues["page"]); | ||
Assert.Equal("Test/Home", descriptor.AttributeRouteInfo.Template); | ||
} | ||
|
||
[Theory] | ||
[InlineData("/Path1")] | ||
[InlineData("~/Path1")] | ||
public void GetDescriptors_ThrowsIfRouteTemplatesAreOverriden(string template) | ||
{ | ||
// Arrange | ||
var razorProject = new Mock<RazorProject>(); | ||
razorProject.Setup(p => p.EnumerateItems("/")) | ||
.Returns(new[] | ||
{ | ||
GetProjectItem("/", "/Test.cshtml", $"@page {template} {Environment.NewLine}<h1>Hello world</h1>"), | ||
}); | ||
var provider = new PageActionDescriptorProvider( | ||
razorProject.Object, | ||
GetAccessor<MvcOptions>(), | ||
GetAccessor<RazorPagesOptions>()); | ||
var context = new ActionDescriptorProviderContext(); | ||
|
||
// Act and Assert | ||
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context)); | ||
Assert.Equal( | ||
"The @page directive for the Razor page at /Test.cshtml cannot override the relative path prefix.", | ||
ex.Message); | ||
} | ||
|
||
[Fact] | ||
public void GetDescriptors_WithEmptyPageDirective_MapsIndexToEmptySegment() | ||
{ | ||
// Arrange | ||
var razorProject = new Mock<RazorProject>(); | ||
razorProject.Setup(p => p.EnumerateItems("/")) | ||
.Returns(new[] | ||
{ | ||
GetProjectItem("/About", "/Index.cshtml", $"@page {Environment.NewLine}"), | ||
}); | ||
var provider = new PageActionDescriptorProvider( | ||
razorProject.Object, | ||
GetAccessor<MvcOptions>(), | ||
GetAccessor<RazorPagesOptions>()); | ||
var context = new ActionDescriptorProviderContext(); | ||
|
||
// Act | ||
provider.OnProvidersExecuting(context); | ||
|
||
// Assert | ||
Assert.Collection(context.Results, | ||
result => | ||
{ | ||
var descriptor = Assert.IsType<PageActionDescriptor>(result); | ||
Assert.Equal("/About/Index.cshtml", descriptor.RelativePath); | ||
Assert.Equal("/Index", descriptor.RouteValues["page"]); | ||
Assert.Equal("About/Index", descriptor.AttributeRouteInfo.Template); | ||
}, | ||
result => | ||
{ | ||
var descriptor = Assert.IsType<PageActionDescriptor>(result); | ||
Assert.Equal("/About/Index.cshtml", descriptor.RelativePath); | ||
Assert.Equal("/Index", descriptor.RouteValues["page"]); | ||
Assert.Equal("About", descriptor.AttributeRouteInfo.Template); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void GetDescriptors_WithNonEmptyPageDirective_MapsIndexToEmptySegment() | ||
{ | ||
// Arrange | ||
var razorProject = new Mock<RazorProject>(); | ||
razorProject.Setup(p => p.EnumerateItems("/")) | ||
.Returns(new[] | ||
{ | ||
GetProjectItem("/Catalog/Details", "/Index.cshtml", $"@page {{id:int?}} {Environment.NewLine}"), | ||
}); | ||
var provider = new PageActionDescriptorProvider( | ||
razorProject.Object, | ||
GetAccessor<MvcOptions>(), | ||
GetAccessor<RazorPagesOptions>()); | ||
var context = new ActionDescriptorProviderContext(); | ||
|
||
// Act | ||
provider.OnProvidersExecuting(context); | ||
|
||
// Assert | ||
Assert.Collection(context.Results, | ||
result => | ||
{ | ||
var descriptor = Assert.IsType<PageActionDescriptor>(result); | ||
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath); | ||
Assert.Equal("/Index", descriptor.RouteValues["page"]); | ||
Assert.Equal("Catalog/Details/Index/{id:int?}", descriptor.AttributeRouteInfo.Template); | ||
}, | ||
result => | ||
{ | ||
var descriptor = Assert.IsType<PageActionDescriptor>(result); | ||
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath); | ||
Assert.Equal("/Index", descriptor.RouteValues["page"]); | ||
Assert.Equal("Catalog/Details/{id:int?}", descriptor.AttributeRouteInfo.Template); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void GetDescriptors_AddsGlobalFilters() | ||
{ | ||
// Arrange | ||
var filter1 = Mock.Of<IFilterMetadata>(); | ||
var filter2 = Mock.Of<IFilterMetadata>(); | ||
var options = new MvcOptions(); | ||
options.Filters.Add(filter1); | ||
options.Filters.Add(filter2); | ||
var razorProject = new Mock<RazorProject>(); | ||
razorProject.Setup(p => p.EnumerateItems("/")) | ||
.Returns(new[] | ||
{ | ||
GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"), | ||
}); | ||
var provider = new PageActionDescriptorProvider( | ||
razorProject.Object, | ||
GetAccessor(options), | ||
GetAccessor<RazorPagesOptions>()); | ||
var context = new ActionDescriptorProviderContext(); | ||
|
||
// Act | ||
provider.OnProvidersExecuting(context); | ||
|
||
// Assert | ||
var result = Assert.Single(context.Results); | ||
var descriptor = Assert.IsType<PageActionDescriptor>(result); | ||
Assert.Collection(descriptor.FilterDescriptors, | ||
filterDescriptor => | ||
{ | ||
Assert.Equal(FilterScope.Global, filterDescriptor.Scope); | ||
Assert.Same(filter1, filterDescriptor.Filter); | ||
}, | ||
filterDescriptor => | ||
{ | ||
Assert.Equal(FilterScope.Global, filterDescriptor.Scope); | ||
Assert.Same(filter2, filterDescriptor.Filter); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void GetDescriptors_AddsFiltersAddedByConvention() | ||
{ | ||
// Arrange | ||
var globalFilter = Mock.Of<IFilterMetadata>(); | ||
var localFilter = Mock.Of<IFilterMetadata>(); | ||
var options = new MvcOptions(); | ||
options.Filters.Add(globalFilter); | ||
var convention = new Mock<IPageModelConvention>(); | ||
convention.Setup(c => c.Apply(It.IsAny<PageModel>())) | ||
.Callback((PageModel model) => | ||
{ | ||
model.Filters.Add(localFilter); | ||
}); | ||
var razorOptions = new RazorPagesOptions(); | ||
razorOptions.Conventions.Add(convention.Object); | ||
|
||
var razorProject = new Mock<RazorProject>(); | ||
razorProject.Setup(p => p.EnumerateItems("/")) | ||
.Returns(new[] | ||
{ | ||
GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"), | ||
}); | ||
var provider = new PageActionDescriptorProvider( | ||
razorProject.Object, | ||
GetAccessor(options), | ||
GetAccessor(razorOptions)); | ||
var context = new ActionDescriptorProviderContext(); | ||
|
||
// Act | ||
provider.OnProvidersExecuting(context); | ||
|
||
// Assert | ||
var result = Assert.Single(context.Results); | ||
var descriptor = Assert.IsType<PageActionDescriptor>(result); | ||
Assert.Collection(descriptor.FilterDescriptors, | ||
filterDescriptor => | ||
{ | ||
Assert.Equal(FilterScope.Global, filterDescriptor.Scope); | ||
Assert.Same(globalFilter, filterDescriptor.Filter); | ||
}, | ||
filterDescriptor => | ||
{ | ||
Assert.Equal(FilterScope.Action, filterDescriptor.Scope); | ||
Assert.Same(localFilter, filterDescriptor.Filter); | ||
}); | ||
} | ||
|
||
private static IOptions<TOptions> GetAccessor<TOptions>(TOptions options = null) | ||
where TOptions : class, new() | ||
{ | ||
var accessor = new Mock<IOptions<TOptions>>(); | ||
accessor.SetupGet(a => a.Value).Returns(options ?? new TOptions()); | ||
return accessor.Object; | ||
} | ||
|
||
private static RazorProjectItem GetProjectItem(string basePath, string path, string content) | ||
{ | ||
var testFileInfo = new TestFileInfo | ||
{ | ||
Content = content, | ||
}; | ||
|
||
return new DefaultRazorProjectItem(testFileInfo, basePath, path); | ||
} | ||
|
||
|
||
} | ||
} |