diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs index 8764d1a97a..8c779001f4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs @@ -13,16 +13,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions /// /// Value cannot be null or empty. /// - internal static string ArgumentCannotBeNullOrEmpy + internal static string ArgumentCannotBeNullOrEmpty { - get => GetString("ArgumentCannotBeNullOrEmpy"); + get => GetString("ArgumentCannotBeNullOrEmpty"); } /// /// Value cannot be null or empty. /// - internal static string FormatArgumentCannotBeNullOrEmpy() - => GetString("ArgumentCannotBeNullOrEmpy"); + internal static string FormatArgumentCannotBeNullOrEmpty() + => GetString("ArgumentCannotBeNullOrEmpty"); /// /// Inject a service from the application's service container into a property. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs index a4959dded2..1dfb460a47 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions @@ -10,6 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public class RazorPageDocumentClassifierPass : DocumentClassifierPassBase { public static readonly string RazorPageDocumentKind = "mvc.1.0.razor-page"; + public static readonly string RouteTemplateKey = "RouteTemplate"; protected override string DocumentKind => RazorPageDocumentKind; @@ -43,14 +45,38 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions method.Modifiers.Add("override"); method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}"; - EnsureValidPageDirective(codeDocument); - } - - private void EnsureValidPageDirective(RazorCodeDocument codeDocument) - { var document = codeDocument.GetDocumentIntermediateNode(); PageDirective.TryGetPageDirective(document, out var pageDirective); + EnsureValidPageDirective(pageDirective); + + AddRouteTemplateMetadataAttribute(@namespace, @class, pageDirective); + } + + private static void AddRouteTemplateMetadataAttribute(NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, PageDirective pageDirective) + { + if (string.IsNullOrEmpty(pageDirective.RouteTemplate)) + { + return; + } + + var classIndex = @namespace.Children.IndexOf(@class); + if (classIndex == -1) + { + return; + } + + var metadataAttributeNode = new RazorCompiledItemMetadataAttributeIntermediateNode + { + Key = RouteTemplateKey, + Value = pageDirective.RouteTemplate, + }; + // Metadata attributes need to be inserted right before the class declaration. + @namespace.Children.Insert(classIndex, metadataAttributeNode); + } + + private void EnsureValidPageDirective(PageDirective pageDirective) + { Debug.Assert(pageDirective != null); if (pageDirective.DirectiveNode.IsImported()) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx index 2bf9043ca6..41cf3a1b03 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + Value cannot be null or empty. diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/IMetadataAttributeTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/IMetadataAttributeTargetExtension.cs index 646dcdd46f..db2955fadd 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/IMetadataAttributeTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/IMetadataAttributeTargetExtension.cs @@ -10,5 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions void WriteRazorCompiledItemAttribute(CodeRenderingContext context, RazorCompiledItemAttributeIntermediateNode node); void WriteRazorSourceChecksumAttribute(CodeRenderingContext context, RazorSourceChecksumAttributeIntermediateNode node); + + void WriteRazorCompiledItemMetadataAttribute(CodeRenderingContext context, RazorCompiledItemMetadataAttributeIntermediateNode node); } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/MetadataAttributePass.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/MetadataAttributePass.cs index 843b1fa48b..4383b2e27a 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/MetadataAttributePass.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/MetadataAttributePass.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions protected override void OnInitialized() { - _identifierFeature = Engine.Features.OfType().FirstOrDefault(); + _identifierFeature = Engine.Features.OfType().FirstOrDefault(); } protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/MetadataAttributeTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/MetadataAttributeTargetExtension.cs index c9723efc5b..7c702bb1a0 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/MetadataAttributeTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/MetadataAttributeTargetExtension.cs @@ -12,6 +12,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions public string SourceChecksumAttributeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute"; + public string CompiledItemMetadataAttributeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute"; + + public void WriteRazorCompiledItemAttribute(CodeRenderingContext context, RazorCompiledItemAttributeIntermediateNode node) { if (context == null) @@ -36,6 +39,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions context.CodeWriter.WriteLine("\")]"); } + public void WriteRazorCompiledItemMetadataAttribute(CodeRenderingContext context, RazorCompiledItemMetadataAttributeIntermediateNode node) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + // [assembly: global::...RazorCompiledItemAttribute(@"{node.Key}", @"{node.Value}")] + context.CodeWriter.Write("["); + context.CodeWriter.Write(CompiledItemMetadataAttributeName); + context.CodeWriter.Write("("); + context.CodeWriter.WriteStringLiteral(node.Key); + context.CodeWriter.Write(", "); + context.CodeWriter.WriteStringLiteral(node.Value); + context.CodeWriter.WriteLine(")]"); + } + public void WriteRazorSourceChecksumAttribute(CodeRenderingContext context, RazorSourceChecksumAttributeIntermediateNode node) { if (context == null) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/RazorCompiledItemMetadataAttributeIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/RazorCompiledItemMetadataAttributeIntermediateNode.cs new file mode 100644 index 0000000000..0c05523028 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/RazorCompiledItemMetadataAttributeIntermediateNode.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 Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Language.Extensions +{ + /// + /// An that generates code for RazorCompiledItemMetadataAttribute. + /// + public class RazorCompiledItemMetadataAttributeIntermediateNode : ExtensionIntermediateNode + { + public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; + + /// + /// Gets or sets the attribute key. + /// + public string Key { get; set; } + + /// + /// Gets or sets the attribute value. + /// + public string Value { get; set; } + + public override void Accept(IntermediateNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(this, visitor); + } + + public override void WriteNode(CodeTarget target, CodeRenderingContext context) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var extension = target.GetExtension(); + if (extension == null) + { + ReportMissingCodeTargetExtension(context); + return; + } + + extension.WriteRazorCompiledItemMetadataAttribute(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Runtime/Hosting/RazorCompiledItemMetadataAttribute.cs b/src/Microsoft.AspNetCore.Razor.Runtime/Hosting/RazorCompiledItemMetadataAttribute.cs new file mode 100644 index 0000000000..0b52fd5f1b --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Runtime/Hosting/RazorCompiledItemMetadataAttribute.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Hosting +{ + /// + /// Defines a key/value metadata pair for the decorated Razor type. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + public sealed class RazorCompiledItemMetadataAttribute : Attribute + { + /// + /// Creates a new . + /// + /// The key. + /// The value. + public RazorCompiledItemMetadataAttribute(string key, string value) + { + Key = key; + Value = value; + } + + /// + /// Gets the key. + /// + public string Key { get; } + + /// + /// Gets the value. + /// + public string Value { get; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index bcddca531e..2155b6c1a9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -231,6 +231,14 @@ public class DivTagHelper : {typeof(TagHelper).FullName} RunRuntimeTest(compilation); } + [Fact] + public void RazorPagesWithRouteTemplate_Runtime() + { + var compilation = BaseCompilation; + + RunRuntimeTest(compilation); + } + [Fact] public void RazorPagesWithoutModel_Runtime() { @@ -510,6 +518,14 @@ public class DivTagHelper : {typeof(TagHelper).FullName} RunDesignTimeTest(compilation); } + [Fact] + public void RazorPagesWithRouteTemplate_DesignTime() + { + var compilation = BaseCompilation; + + RunDesignTimeTest(compilation); + } + [Fact] public void RazorPagesWithoutModel_DesignTime() { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs index 6a21674f2e..e3ba1aa9d6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs @@ -2,6 +2,7 @@ // 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.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; @@ -60,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { // Arrange var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml")); - + var engine = CreateEngine(); var irDocument = CreateIRDocument(engine, codeDocument); irDocument.DocumentKind = "some-value"; @@ -239,6 +240,31 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions Assert.Equal(new[] { "public", "async", "override" }, visitor.Method.Modifiers); } + [Fact] + public void RazorPageDocumentClassifierPass_AddsRouteTemplateMetadata() + { + // Arrange + var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: "Test.cshtml"); + var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page \"some-route\"", properties)); + + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + var attributeNode = Assert.IsType(visitor.ExtensionNode); + Assert.Equal("RouteTemplate", attributeNode.Key); + Assert.Equal("some-route", attributeNode.Value); + } + private static RazorEngine CreateEngine() { return RazorEngine.Create(b => @@ -271,6 +297,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions public MethodDeclarationIntermediateNode Method { get; private set; } + public ExtensionIntermediateNode ExtensionNode { get; private set; } + public override void VisitMethodDeclaration(MethodDeclarationIntermediateNode node) { Method = node; @@ -287,6 +315,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions Class = node; base.VisitClassDeclaration(node); } + + public override void VisitExtension(ExtensionIntermediateNode node) + { + ExtensionNode = node; + } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml new file mode 100644 index 0000000000..63d5955e72 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml @@ -0,0 +1,13 @@ +@page "/About" + +@model NewModel +@using Microsoft.AspNetCore.Mvc.RazorPages + +@functions { + public class NewModel : PageModel + { + public string Name { get; set; } + } +} + +

New Customer @Model.Name

diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.codegen.cs new file mode 100644 index 0000000000..69b52f95f7 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.codegen.cs @@ -0,0 +1,70 @@ +// +#pragma warning disable 1591 +namespace AspNetCore +{ + #line hidden + using TModel = global::System.Object; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Mvc.ViewFeatures; +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml" +using Microsoft.AspNetCore.Mvc.RazorPages; + +#line default +#line hidden + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("RouteTemplate", "/About")] + public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithRouteTemplate : global::Microsoft.AspNetCore.Mvc.RazorPages.Page + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::System.Object __typeHelper = "/About"; + } + ))(); + ((System.Action)(() => { +NewModel __typeHelper = default(NewModel); + } + ))(); + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static System.Object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { +#line 13 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml" + __o = Model.Name; + +#line default +#line hidden + } + #pragma warning restore 1998 +#line 6 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml" + + public class NewModel : PageModel + { + public string Name { get; set; } + } + +#line default +#line hidden + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + public NewModel Model => ViewData.Model; + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.ir.txt new file mode 100644 index 0000000000..c71b835bf9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.ir.txt @@ -0,0 +1,60 @@ +Document - + NamespaceDeclaration - - AspNetCore + UsingDirective - - TModel = global::System.Object + UsingDirective - (1:0,1 [12] ) - System + UsingDirective - (16:1,1 [32] ) - System.Collections.Generic + UsingDirective - (51:2,1 [17] ) - System.Linq + UsingDirective - (71:3,1 [28] ) - System.Threading.Tasks + UsingDirective - (102:4,1 [30] ) - Microsoft.AspNetCore.Mvc + UsingDirective - (135:5,1 [40] ) - Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures + UsingDirective - (36:3,1 [41] RazorPagesWithRouteTemplate.cshtml) - Microsoft.AspNetCore.Mvc.RazorPages + RazorCompiledItemMetadataAttribute - + ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithRouteTemplate - global::Microsoft.AspNetCore.Mvc.RazorPages.Page - + DesignTimeDirective - + DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper + DirectiveToken - (294:7,71 [4] ) - Html + DirectiveToken - (308:8,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper + DirectiveToken - (363:8,63 [4] ) - Json + DirectiveToken - (377:9,8 [53] ) - global::Microsoft.AspNetCore.Mvc.IViewComponentHelper + DirectiveToken - (431:9,62 [9] ) - Component + DirectiveToken - (450:10,8 [43] ) - global::Microsoft.AspNetCore.Mvc.IUrlHelper + DirectiveToken - (494:10,52 [3] ) - Url + DirectiveToken - (507:11,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider + DirectiveToken - (578:11,79 [23] ) - ModelExpressionProvider + DirectiveToken - (617:12,14 [96] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (729:13,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (832:14,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (6:0,6 [8] RazorPagesWithRouteTemplate.cshtml) - "/About" + DirectiveToken - (25:2,7 [8] RazorPagesWithRouteTemplate.cshtml) - NewModel + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static System.Object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + HtmlContent - (16:1,0 [2] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (16:1,0 [2] RazorPagesWithRouteTemplate.cshtml) - Html - \n + HtmlContent - (77:3,42 [4] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (77:3,42 [4] RazorPagesWithRouteTemplate.cshtml) - Html - \n\n + HtmlContent - (191:10,1 [21] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (191:10,1 [4] RazorPagesWithRouteTemplate.cshtml) - Html - \n\n + IntermediateToken - (195:12,0 [4] RazorPagesWithRouteTemplate.cshtml) - Html -

+ IntermediateToken - (199:12,4 [13] RazorPagesWithRouteTemplate.cshtml) - Html - New Customer + CSharpExpression - (213:12,18 [10] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (213:12,18 [10] RazorPagesWithRouteTemplate.cshtml) - CSharp - Model.Name + HtmlContent - (223:12,28 [7] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (223:12,28 [5] RazorPagesWithRouteTemplate.cshtml) - Html -

+ IntermediateToken - (228:12,33 [2] RazorPagesWithRouteTemplate.cshtml) - Html - \n + CSharpCode - (93:5,12 [97] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (93:5,12 [97] RazorPagesWithRouteTemplate.cshtml) - CSharp - \n public class NewModel : PageModel\n {\n public string Name { get; set; }\n }\n + Inject - + Inject - + Inject - + Inject - + Inject - + CSharpCode - + IntermediateToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + CSharpCode - + IntermediateToken - - CSharp - public NewModel Model => ViewData.Model; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.mappings.txt new file mode 100644 index 0000000000..e97ebe1943 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_DesignTime.mappings.txt @@ -0,0 +1,35 @@ +Source Location: (36:3,1 [41] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml) +|using Microsoft.AspNetCore.Mvc.RazorPages| +Generated Location: (492:14,0 [41] ) +|using Microsoft.AspNetCore.Mvc.RazorPages| + +Source Location: (6:0,6 [8] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml) +|"/About"| +Generated Location: (1005:24,37 [8] ) +|"/About"| + +Source Location: (25:2,7 [8] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml) +|NewModel| +Generated Location: (1076:28,0 [8] ) +|NewModel| + +Source Location: (213:12,18 [10] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml) +|Model.Name| +Generated Location: (1573:40,18 [10] ) +|Model.Name| + +Source Location: (93:5,12 [97] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml) +| + public class NewModel : PageModel + { + public string Name { get; set; } + } +| +Generated Location: (1781:47,12 [97] ) +| + public class NewModel : PageModel + { + public string Name { get; set; } + } +| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_Runtime.codegen.cs new file mode 100644 index 0000000000..9fcb78be96 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_Runtime.codegen.cs @@ -0,0 +1,72 @@ +#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "d2a113c82e86efc28ea0a17c1f42bbb23a52fecf" +// +#pragma warning disable 1591 +[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithRouteTemplate), @"mvc.1.0.razor-page", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml")] +[assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(@"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml", typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithRouteTemplate), @"/About")] +namespace AspNetCore +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Mvc.ViewFeatures; +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml" +using Microsoft.AspNetCore.Mvc.RazorPages; + +#line default +#line hidden + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("RouteTemplate", "/About")] + [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"d2a113c82e86efc28ea0a17c1f42bbb23a52fecf", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml")] + public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithRouteTemplate : global::Microsoft.AspNetCore.Mvc.RazorPages.Page + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + BeginContext(16, 2, true); + WriteLiteral("\r\n"); + EndContext(); + BeginContext(79, 2, true); + WriteLiteral("\r\n"); + EndContext(); + BeginContext(193, 19, true); + WriteLiteral("\r\n

New Customer "); + EndContext(); + BeginContext(213, 10, false); +#line 13 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml" + Write(Model.Name); + +#line default +#line hidden + EndContext(); + BeginContext(223, 7, true); + WriteLiteral("

\r\n"); + EndContext(); + } + #pragma warning restore 1998 +#line 6 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml" + + public class NewModel : PageModel + { + public string Name { get; set; } + } + +#line default +#line hidden + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + public NewModel Model => ViewData.Model; + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_Runtime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_Runtime.ir.txt new file mode 100644 index 0000000000..5d54680b90 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate_Runtime.ir.txt @@ -0,0 +1,61 @@ +Document - + RazorCompiledItemAttribute - + CSharpCode - + IntermediateToken - - CSharp - [assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(@"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithRouteTemplate.cshtml", typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithRouteTemplate), @"/About")] + NamespaceDeclaration - - AspNetCore + UsingDirective - (1:0,1 [14] ) - System + UsingDirective - (16:1,1 [34] ) - System.Collections.Generic + UsingDirective - (51:2,1 [19] ) - System.Linq + UsingDirective - (71:3,1 [30] ) - System.Threading.Tasks + UsingDirective - (102:4,1 [32] ) - Microsoft.AspNetCore.Mvc + UsingDirective - (135:5,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (178:6,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures + UsingDirective - (36:3,1 [43] RazorPagesWithRouteTemplate.cshtml) - Microsoft.AspNetCore.Mvc.RazorPages + RazorCompiledItemMetadataAttribute - + RazorSourceChecksumAttribute - + ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithRouteTemplate - global::Microsoft.AspNetCore.Mvc.RazorPages.Page - + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + CSharpCode - + IntermediateToken - - CSharp - BeginContext(16, 2, true); + HtmlContent - (16:1,0 [2] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (16:1,0 [2] RazorPagesWithRouteTemplate.cshtml) - Html - \n + CSharpCode - + IntermediateToken - - CSharp - EndContext(); + CSharpCode - + IntermediateToken - - CSharp - BeginContext(79, 2, true); + HtmlContent - (79:4,0 [2] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (79:4,0 [2] RazorPagesWithRouteTemplate.cshtml) - Html - \n + CSharpCode - + IntermediateToken - - CSharp - EndContext(); + CSharpCode - + IntermediateToken - - CSharp - BeginContext(193, 19, true); + HtmlContent - (193:11,0 [19] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (193:11,0 [2] RazorPagesWithRouteTemplate.cshtml) - Html - \n + IntermediateToken - (195:12,0 [4] RazorPagesWithRouteTemplate.cshtml) - Html -

+ IntermediateToken - (199:12,4 [13] RazorPagesWithRouteTemplate.cshtml) - Html - New Customer + CSharpCode - + IntermediateToken - - CSharp - EndContext(); + CSharpCode - + IntermediateToken - - CSharp - BeginContext(213, 10, false); + CSharpExpression - (213:12,18 [10] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (213:12,18 [10] RazorPagesWithRouteTemplate.cshtml) - CSharp - Model.Name + CSharpCode - + IntermediateToken - - CSharp - EndContext(); + CSharpCode - + IntermediateToken - - CSharp - BeginContext(223, 7, true); + HtmlContent - (223:12,28 [7] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (223:12,28 [5] RazorPagesWithRouteTemplate.cshtml) - Html -

+ IntermediateToken - (228:12,33 [2] RazorPagesWithRouteTemplate.cshtml) - Html - \n + CSharpCode - + IntermediateToken - - CSharp - EndContext(); + CSharpCode - (93:5,12 [97] RazorPagesWithRouteTemplate.cshtml) + IntermediateToken - (93:5,12 [97] RazorPagesWithRouteTemplate.cshtml) - CSharp - \n public class NewModel : PageModel\n {\n public string Name { get; set; }\n }\n + Inject - + Inject - + Inject - + Inject - + Inject - + CSharpCode - + IntermediateToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + CSharpCode - + IntermediateToken - - CSharp - public NewModel Model => ViewData.Model; diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/MetadataAttributeTargetExtensionTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/MetadataAttributeTargetExtensionTest.cs index 9f8a280f73..faeaf4d662 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/MetadataAttributeTargetExtensionTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Extensions/MetadataAttributeTargetExtensionTest.cs @@ -65,5 +65,59 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions csharp, ignoreLineEndingDifferences: true); } + + [Fact] + public void WriteRazorCompiledItemAttributeMetadata_RendersCorrectly() + { + // Arrange + var extension = new MetadataAttributeTargetExtension() + { + CompiledItemMetadataAttributeName = "global::TestItemMetadata", + }; + var context = TestCodeRenderingContext.CreateRuntime(); + + var node = new RazorCompiledItemMetadataAttributeIntermediateNode + { + Key = "key", + Value = "value", + }; + + // Act + extension.WriteRazorCompiledItemMetadataAttribute(context, node); + + // Assert + var csharp = context.CodeWriter.GenerateCode().Trim(); + Assert.Equal( +"[global::TestItemMetadata(\"key\", \"value\")]", + csharp, + ignoreLineEndingDifferences: true); + } + + [Fact] + public void WriteRazorCompiledItemAttributeMetadata_EscapesKeysAndValuesCorrectly() + { + // Arrange + var extension = new MetadataAttributeTargetExtension() + { + CompiledItemMetadataAttributeName = "global::TestItemMetadata", + }; + var context = TestCodeRenderingContext.CreateRuntime(); + + var node = new RazorCompiledItemMetadataAttributeIntermediateNode + { + Key = "\"test\" key", + Value = @"""test"" value", + }; + + // Act + extension.WriteRazorCompiledItemMetadataAttribute(context, node); + + // Assert + var csharp = context.CodeWriter.GenerateCode().Trim(); + Assert.Equal( +"[global::TestItemMetadata(\"\\\"test\\\" key\", \"\\\"test\\\" value\")]", + csharp, + ignoreLineEndingDifferences: true); + } } }