diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs index dc5e699e42..48db33bc17 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs @@ -1,27 +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.Diagnostics; using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { - public static class PageDirective + public class PageDirective { public static readonly DirectiveDescriptor DirectiveDescriptor = DirectiveDescriptorBuilder .Create("page") .BeginOptionals() - .AddString() + .AddString() // Route template + .AddString() // Page Name .Build(); + private PageDirective(string routeTemplate, string pageName) + { + RouteTemplate = routeTemplate; + PageName = pageName; + } + + public string RouteTemplate { get; } + + public string PageName { get; } + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder) { builder.AddDirective(DirectiveDescriptor); return builder; } - public static bool TryGetRouteTemplate(DocumentIRNode irDocument, out string routeTemplate) + public static bool TryGetPageDirective(DocumentIRNode irDocument, out PageDirective pageDirective) { var visitor = new Visitor(); for (var i = 0; i < irDocument.Children.Count; i++) @@ -29,22 +42,46 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions visitor.Visit(irDocument.Children[i]); } - routeTemplate = visitor.RouteTemplate; - return visitor.DirectiveNode != null; + if (visitor.DirectiveNode == null) + { + pageDirective = null; + return false; + } + + var tokens = visitor.DirectiveNode.Tokens.ToList(); + string routeTemplate = null; + string pageName = null; + if (tokens.Count > 0) + { + routeTemplate = TrimQuotes(tokens[0].Content); + } + + if (tokens.Count > 1) + { + pageName = TrimQuotes(tokens[1].Content); + } + + pageDirective = new PageDirective(routeTemplate, pageName); + return true; + } + + private static string TrimQuotes(string content) + { + Debug.Assert(content.StartsWith("\"", StringComparison.Ordinal)); + Debug.Assert(content.EndsWith("\"", StringComparison.Ordinal)); + + return content.Substring(1, content.Length - 2); } private class Visitor : RazorIRNodeWalker { public DirectiveIRNode DirectiveNode { get; private set; } - public string RouteTemplate { get; private set; } - public override void VisitDirective(DirectiveIRNode node) { if (node.Descriptor == DirectiveDescriptor) { DirectiveNode = node; - RouteTemplate = node.Tokens.FirstOrDefault()?.Content; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs index 5b1e07b910..3187b68b51 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs @@ -15,8 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument) { - string routePrefix; - return PageDirective.TryGetRouteTemplate(irDocument, out routePrefix); + return PageDirective.TryGetPageDirective(irDocument, out var directive); } protected override void OnDocumentStructureCreated( diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/PageDirectiveTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/PageDirectiveTest.cs new file mode 100644 index 0000000000..580651bdc2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/PageDirectiveTest.cs @@ -0,0 +1,129 @@ +// 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 Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class PageDirectiveTest + { + [Fact] + public void TryGetPageDirective_ReturnsFalse_IfPageDoesNotHaveDirective() + { + // Arrange + var content = "Hello world"; + var sourceDocument = RazorSourceDocument.Create(content, "file"); + var codeDocument = RazorCodeDocument.Create(sourceDocument); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective); + + // Assert + Assert.False(result); + Assert.Null(pageDirective); + } + + [Fact(Skip = "https://github.com/aspnet/Razor/issues/1201")] + public void TryGetPageDirective_ReturnsFalse_IfPageDoesStartWithDirective() + { + // Arrange + var content = "Hello @page"; + var sourceDocument = RazorSourceDocument.Create(content, "file"); + var codeDocument = RazorCodeDocument.Create(sourceDocument); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective); + + // Assert + Assert.False(result); + Assert.Null(pageDirective); + } + + [Fact] + public void TryGetPageDirective_ReturnsTrue_IfContentHasDirective() + { + // Arrange + var content = "@page"; + var sourceDocument = RazorSourceDocument.Create(content, "file"); + var codeDocument = RazorCodeDocument.Create(sourceDocument); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective); + + // Assert + Assert.True(result); + Assert.Null(pageDirective.RouteTemplate); + Assert.Null(pageDirective.PageName); + } + + [Fact] + public void TryGetPageDirective_ParsesRouteTemplate() + { + // Arrange + var content = "@page \"some-route-template\""; + var sourceDocument = RazorSourceDocument.Create(content, "file"); + var codeDocument = RazorCodeDocument.Create(sourceDocument); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective); + + // Assert + Assert.True(result); + Assert.Equal("some-route-template", pageDirective.RouteTemplate); + Assert.Null(pageDirective.PageName); + } + + [Fact] + public void TryGetPageDirective_ParsesPageName() + { + // Arrange + var content = "@page \"some-route\" \"some name\""; + var sourceDocument = RazorSourceDocument.Create(content, "file"); + var codeDocument = RazorCodeDocument.Create(sourceDocument); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective); + + // Assert + Assert.True(result); + Assert.Equal("some-route", pageDirective.RouteTemplate); + Assert.Equal("some name", pageDirective.PageName); + } + + private RazorEngine CreateEngine() + { + return RazorEngine.Create(b => + { + PageDirective.Register(b); + }); + } + + private DocumentIRNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument) + { + for (var i = 0; i < engine.Phases.Count; i++) + { + var phase = engine.Phases[i]; + phase.Execute(codeDocument); + + if (phase is IRazorDocumentClassifierPhase) + { + break; + } + } + + return codeDocument.GetIRDocument(); + } + } +}