From 773e3500ec3e193c5e754d183fbfb901104003c3 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 24 Oct 2016 17:43:16 -0700 Subject: [PATCH] Adding PageActionDescriptorProvider Fixes #5353 --- .../Infrastructure/DefaultRazorProject.cs | 59 ++++ .../Infrastructure/DefaultRazorProjectItem.cs | 32 ++ .../PageActionDescriptorProviderTest.cs | 303 ++++++++++++++++++ 3 files changed, 394 insertions(+) create mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProject.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProjectItem.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorProviderTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProject.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProject.cs new file mode 100644 index 0000000000..6dd877d063 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProject.cs @@ -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 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 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); + } + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProjectItem.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProjectItem.cs new file mode 100644 index 0000000000..4461709db7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultRazorProjectItem.cs @@ -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(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorProviderTest.cs new file mode 100644 index 0000000000..bc91ade8b3 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorProviderTest.cs @@ -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.Setup(p => p.EnumerateItems("/")) + .Returns(new[] + { + GetProjectItem("/", "/Index.cshtml", "

Hello world

"), + }); + var provider = new PageActionDescriptorProvider( + razorProject.Object, + GetAccessor(), + GetAccessor()); + var context = new ActionDescriptorProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + Assert.Empty(context.Results); + } + + [Fact] + public void GetDescriptors_AddsDescriptorsForFileWithPageDirective() + { + // Arrange + var razorProject = new Mock(); + razorProject.Setup(p => p.EnumerateItems("/")) + .Returns(new[] + { + GetProjectItem("/", "/Test.cshtml", $"@page{Environment.NewLine}

Hello world

"), + }); + var provider = new PageActionDescriptorProvider( + razorProject.Object, + GetAccessor(), + GetAccessor()); + var context = new ActionDescriptorProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var result = Assert.Single(context.Results); + var descriptor = Assert.IsType(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.Setup(p => p.EnumerateItems("/")) + .Returns(new[] + { + GetProjectItem("/", "/Test.cshtml", $"@page Home {Environment.NewLine}

Hello world

"), + }); + var provider = new PageActionDescriptorProvider( + razorProject.Object, + GetAccessor(), + GetAccessor()); + var context = new ActionDescriptorProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var result = Assert.Single(context.Results); + var descriptor = Assert.IsType(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.Setup(p => p.EnumerateItems("/")) + .Returns(new[] + { + GetProjectItem("/", "/Test.cshtml", $"@page {template} {Environment.NewLine}

Hello world

"), + }); + var provider = new PageActionDescriptorProvider( + razorProject.Object, + GetAccessor(), + GetAccessor()); + var context = new ActionDescriptorProviderContext(); + + // Act and Assert + var ex = Assert.Throws(() => 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.Setup(p => p.EnumerateItems("/")) + .Returns(new[] + { + GetProjectItem("/About", "/Index.cshtml", $"@page {Environment.NewLine}"), + }); + var provider = new PageActionDescriptorProvider( + razorProject.Object, + GetAccessor(), + GetAccessor()); + var context = new ActionDescriptorProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + Assert.Collection(context.Results, + result => + { + var descriptor = Assert.IsType(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(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.Setup(p => p.EnumerateItems("/")) + .Returns(new[] + { + GetProjectItem("/Catalog/Details", "/Index.cshtml", $"@page {{id:int?}} {Environment.NewLine}"), + }); + var provider = new PageActionDescriptorProvider( + razorProject.Object, + GetAccessor(), + GetAccessor()); + var context = new ActionDescriptorProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + Assert.Collection(context.Results, + result => + { + var descriptor = Assert.IsType(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(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(); + var filter2 = Mock.Of(); + var options = new MvcOptions(); + options.Filters.Add(filter1); + options.Filters.Add(filter2); + var razorProject = new Mock(); + razorProject.Setup(p => p.EnumerateItems("/")) + .Returns(new[] + { + GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"), + }); + var provider = new PageActionDescriptorProvider( + razorProject.Object, + GetAccessor(options), + GetAccessor()); + var context = new ActionDescriptorProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var result = Assert.Single(context.Results); + var descriptor = Assert.IsType(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(); + var localFilter = Mock.Of(); + var options = new MvcOptions(); + options.Filters.Add(globalFilter); + var convention = new Mock(); + convention.Setup(c => c.Apply(It.IsAny())) + .Callback((PageModel model) => + { + model.Filters.Add(localFilter); + }); + var razorOptions = new RazorPagesOptions(); + razorOptions.Conventions.Add(convention.Object); + + var razorProject = new Mock(); + 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(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 GetAccessor(TOptions options = null) + where TOptions : class, new() + { + var accessor = new Mock>(); + 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); + } + + + } +}