From 4e4945d0d567a533353697190a22a0aaacd46839 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Sat, 7 Feb 2026 22:05:05 +1000 Subject: [PATCH 01/14] WIP keyed injection --- .../src/Language/ComponentResources.resx | 60 ++++++++++--------- .../src/Mvc/InjectDirective.cs | 8 +++ .../src/Mvc/InjectIntermediateNode.cs | 5 ++ .../src/Mvc/InjectTargetExtension.cs | 17 +++++- .../src/Mvc/RazorExtensionsResources.resx | 60 ++++++++++--------- 5 files changed, 95 insertions(+), 55 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/ComponentResources.resx b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/ComponentResources.resx index 900c52162b9..4b35ed1370b 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/ComponentResources.resx +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/ComponentResources.resx @@ -1,17 +1,17 @@ - @@ -213,6 +213,12 @@ route template + + An optional key for when accessing keyed services. + + + Keyed Services Key + True if whitespace should be preserved, otherwise false. diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs index 6ca5202a3eb..73eccc5e6c0 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs @@ -19,6 +19,7 @@ public static class InjectDirective builder => { builder + .AddOptionalStringToken(RazorExtensionsResources.InjectDirective_KeyToken_Name, RazorExtensionsResources.InjectDirective_KeyToken_Description) .AddTypeToken(RazorExtensionsResources.InjectDirective_TypeToken_Name, RazorExtensionsResources.InjectDirective_TypeToken_Description) .AddMemberToken(RazorExtensionsResources.InjectDirective_MemberToken_Name, RazorExtensionsResources.InjectDirective_MemberToken_Description); @@ -83,6 +84,11 @@ protected override void ExecuteCore( continue; } + var hasKey = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); + Debug.Assert(hasKey || isMalformed); + var keyName = hasKeyName ? tokens[2].Content : null; + var keySpan = hasKeyName ? tokens[2].Source : null; + const string tModel = ""; if (typeName.EndsWith(tModel, StringComparison.Ordinal)) { @@ -99,6 +105,8 @@ protected override void ExecuteCore( MemberName = memberName, TypeSource = typeSpan, MemberSource = memberSpan, + KeyName = keyName, + KeySource = KeySource, IsMalformed = isMalformed }; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs index 8ef9bbbe3cc..ae225e2898b 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs @@ -22,6 +22,10 @@ public class InjectIntermediateNode : ExtensionIntermediateNode public bool IsMalformed { get; set; } + public string? KeyName { get; set; } + + public SourceSpan? KeySource { get; set; } + public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; public override void Accept(IntermediateNodeVisitor visitor) @@ -62,6 +66,7 @@ public override void FormatNode(IntermediateNodeFormatter formatter) formatter.WriteProperty(nameof(MemberName), MemberName); formatter.WriteProperty(nameof(TypeName), TypeName); + formatter.WriteProperty(nameof(KeyName), KeyName); formatter.WriteProperty(nameof(IsMalformed), IsMalformed.ToString()); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs index 0c1ab5152bd..4f634297f5b 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs @@ -12,6 +12,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; public class InjectTargetExtension(bool considerNullabilityEnforcement) : IInjectTargetExtension { private const string RazorInjectAttribute = "[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]"; + private const string RazorInjectAttributeWithAsterix = "[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute*]"; + + private string GetRazorInjectAttributeWithKey(string key) { + return RazorInjectAttributeWithAsterix.Replace("*", $"(Key = {key})"); + } public void WriteInjectProperty(CodeRenderingContext context, InjectIntermediateNode node) { @@ -36,7 +41,17 @@ public void WriteInjectProperty(CodeRenderingContext context, InjectIntermediate } else { - context.CodeWriter.WriteLine(RazorInjectAttribute); + // If there is a key write it into inject attribute + var keyName = node.KeyName; + if (keyName != null) + { + context.CodeWriter.WriteLine(GetRazorInjectAttributeWithKey(keyName)); + } + else + { + context.CodeWriter.WriteLine(RazorInjectAttribute); + } + var memberName = node.MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context); context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, memberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensionsResources.resx b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensionsResources.resx index e7e13dd292c..22e96b67d19 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensionsResources.resx +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensionsResources.resx @@ -1,17 +1,17 @@  - @@ -135,6 +135,12 @@ TypeName + + An optional key for when accessing keyed services. + + + Keyed Services Key + Specify the view or page model for the page. From 87a3df4261c44ae112b5d5ad7bb05281a26bb9d2 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Sat, 7 Feb 2026 23:23:04 +1000 Subject: [PATCH 02/14] WIP keyed injection2 --- .../test/InjectDirectiveTest.cs | 25 +++++++++++++++++ .../test/InjectTargetExtensionTest.cs | 28 +++++++++++++++++++ .../src/Mvc/InjectDirective.cs | 11 ++++---- .../src/Mvc/InjectTargetExtension.cs | 18 +++++------- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs index 98e4fac5b26..72f50275bea 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs @@ -49,6 +49,31 @@ @inject PropertyType PropertyName Assert.Equal("PropertyName", node.MemberName); } + + [Fact] + public void InjectDirectivePass_Execute_DefinesProperty_WithKey() + { + // Arrange + var codeDocument = ProjectEngine.CreateCodeDocument(@" +@inject PropertyType PropertyName ""PropertyKey"" +"); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var classNode = documentNode.GetClassNode(); + + Assert.Equal(2, classNode.Children.Count); + + var node = Assert.IsType(classNode.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + Assert.Equal("\"PropertyKey\"", node.KeyName); + } + // TODO: Check to see if deduping has been effected [Fact] public void InjectDirectivePass_Execute_DedupesPropertiesByName() { diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs index 11a910ccc20..330ac4ff051 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs @@ -40,6 +40,34 @@ public void InjectDirectiveTargetExtension_WritesProperty() context.CodeWriter.GetText().ToString()); } + + [Fact] + public void InjectDirectiveTargetExtension_WritesProperty_WithKey() + { + // Arrange + using var context = TestCodeRenderingContext.CreateRuntime(); + var target = new InjectTargetExtension(considerNullabilityEnforcement: true); + var node = new InjectIntermediateNode() + { + TypeName = "PropertyType", + MemberName = "PropertyName", + KeyName = "\"PropertyKey\"", + }; + + // Act + target.WriteInjectProperty(context, node); + + // Assert + Assert.Equal(""" + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "PropertyKey")] + public PropertyType PropertyName { get; private set; } = default!; + #nullable disable + + """, + context.CodeWriter.GetText().ToString()); + } + [Fact] public void InjectDirectiveTargetExtension_WritesPropertyWithLinePragma_WhenSourceIsSet() { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs index 73eccc5e6c0..c25042bd52d 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs @@ -19,9 +19,10 @@ public static class InjectDirective builder => { builder - .AddOptionalStringToken(RazorExtensionsResources.InjectDirective_KeyToken_Name, RazorExtensionsResources.InjectDirective_KeyToken_Description) .AddTypeToken(RazorExtensionsResources.InjectDirective_TypeToken_Name, RazorExtensionsResources.InjectDirective_TypeToken_Description) - .AddMemberToken(RazorExtensionsResources.InjectDirective_MemberToken_Name, RazorExtensionsResources.InjectDirective_MemberToken_Description); + .AddMemberToken(RazorExtensionsResources.InjectDirective_MemberToken_Name, RazorExtensionsResources.InjectDirective_MemberToken_Description) + // having this last to avoid validation issues + .AddOptionalStringToken(RazorExtensionsResources.InjectDirective_KeyToken_Name, RazorExtensionsResources.InjectDirective_KeyToken_Description); builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; builder.Description = RazorExtensionsResources.InjectDirective_Description; @@ -84,8 +85,8 @@ protected override void ExecuteCore( continue; } - var hasKey = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); - Debug.Assert(hasKey || isMalformed); + var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); + // No assert as is optional var keyName = hasKeyName ? tokens[2].Content : null; var keySpan = hasKeyName ? tokens[2].Source : null; @@ -106,7 +107,7 @@ protected override void ExecuteCore( TypeSource = typeSpan, MemberSource = memberSpan, KeyName = keyName, - KeySource = KeySource, + KeySource = keySpan, IsMalformed = isMalformed }; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs index 4f634297f5b..20a124ea324 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs @@ -6,6 +6,7 @@ using System; using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.AspNetCore.Razor.Language.Extensions; +using static Microsoft.AspNetCore.Razor.Language.Components.ComponentNodeWriter; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; @@ -15,7 +16,9 @@ public class InjectTargetExtension(bool considerNullabilityEnforcement) : IInjec private const string RazorInjectAttributeWithAsterix = "[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute*]"; private string GetRazorInjectAttributeWithKey(string key) { - return RazorInjectAttributeWithAsterix.Replace("*", $"(Key = {key})"); + + // If there is a key write it into inject attribute + return string.IsNullOrEmpty(key) ? RazorInjectAttribute : RazorInjectAttributeWithAsterix.Replace("*", $"(Key = {key})"); } public void WriteInjectProperty(CodeRenderingContext context, InjectIntermediateNode node) @@ -41,16 +44,9 @@ public void WriteInjectProperty(CodeRenderingContext context, InjectIntermediate } else { - // If there is a key write it into inject attribute var keyName = node.KeyName; - if (keyName != null) - { - context.CodeWriter.WriteLine(GetRazorInjectAttributeWithKey(keyName)); - } - else - { - context.CodeWriter.WriteLine(RazorInjectAttribute); - } + + context.CodeWriter.WriteLine(GetRazorInjectAttributeWithKey(keyName)); var memberName = node.MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context); context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, memberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true); @@ -84,7 +80,7 @@ void WriteProperty() } context.CodeWriter - .WriteLine(RazorInjectAttribute) + .WriteLine(GetRazorInjectAttributeWithKey(node.KeyName)) .WriteLine(property); if (considerNullabilityEnforcement && !context.Options.SuppressNullabilityEnforcement) From 9fcb5a0bf954f379d27f1540caff53e20ae7affe Mon Sep 17 00:00:00 2001 From: wlsquid Date: Sun, 8 Feb 2026 18:27:27 +1000 Subject: [PATCH 03/14] WIP updated components --- .../Components/ComponentInjectDirective.cs | 2 ++ .../ComponentInjectDirectivePass.cs | 7 +++++- .../ComponentInjectIntermediateNode.cs | 24 ++++++++++++------- .../src/Mvc/InjectIntermediateNode.cs | 2 +- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirective.cs index 06a6d114f7d..1f39dc975c4 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirective.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using Microsoft.AspNetCore.Mvc.Razor.Extensions; namespace Microsoft.AspNetCore.Razor.Language.Components; @@ -20,6 +21,7 @@ internal static class ComponentInjectDirective { builder.AddTypeToken("TypeName", "The type of the service to inject."); builder.AddMemberToken("PropertyName", "The name of the property."); + builder.AddOptionalStringToken("KeyName", "An optional key for when accessing keyed services."); builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; builder.Description = "Inject a service from the application's service container into a property."; }); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirectivePass.cs index 1d9ef59de24..aed076a55f7 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirectivePass.cs @@ -44,7 +44,12 @@ protected override void ExecuteCore( continue; } - classNode!.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan, isMalformed)); + var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); + // No assert as is optional + var keyName = hasKeyName ? tokens[2].Content : null; + var keySpan = hasKeyName ? tokens[2].Source : null; + + classNode!.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan, isMalformed, keyName, keySpan)); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectIntermediateNode.cs index b7076d544ba..80be3258bfc 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectIntermediateNode.cs @@ -8,25 +8,29 @@ using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Razor.Language.Components; internal class ComponentInjectIntermediateNode : ExtensionIntermediateNode { - private static readonly ImmutableArray s_injectedPropertyModifiers = - [ - $"[global::{ComponentsApi.InjectAttribute.FullTypeName}]", - "private" // Encapsulation is the default - ]; + private ImmutableArray injectedPropertyModifiers() { + return [ + $"[global::{ComponentsApi.InjectAttribute.FullTypeName}{(!string.IsNullOrEmpty(KeyName) ? "" : $"(Key = {KeyName})")}]", + "private" // Encapsulation is the default + ]; + } - public ComponentInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan, bool isMalformed) + public ComponentInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan, bool isMalformed, string keyName, SourceSpan? keySpan) { TypeName = typeName; MemberName = memberName; TypeSpan = typeSpan; MemberSpan = memberSpan; IsMalformed = isMalformed; - } + KeyName = keyName; + KeySource = keySpan; + } public string TypeName { get; } @@ -36,6 +40,10 @@ public ComponentInjectIntermediateNode(string typeName, string memberName, Sourc public SourceSpan? MemberSpan { get; } + public string KeyName { get; set; } + + public SourceSpan? KeySource { get; set; } + public bool IsMalformed { get; } public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; @@ -76,7 +84,7 @@ public override void WriteNode(CodeTarget target, CodeRenderingContext context) if (!context.Options.DesignTime || !IsMalformed) { context.CodeWriter.WriteAutoPropertyDeclaration( - s_injectedPropertyModifiers, + injectedPropertyModifiers(), TypeName, memberName, TypeSpan, diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs index ae225e2898b..8d2f85dfa69 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs @@ -22,7 +22,7 @@ public class InjectIntermediateNode : ExtensionIntermediateNode public bool IsMalformed { get; set; } - public string? KeyName { get; set; } + public string KeyName { get; set; } public SourceSpan? KeySource { get; set; } From acd6f9789a1c4090768578052736720e0e0754f0 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Mon, 9 Feb 2026 01:08:44 +1000 Subject: [PATCH 04/14] WIP 3 --- .../test/InjectDirectiveTest.cs | 2 +- .../CodeGenerationIntegrationTest.cs | 71 +++++++++++++++++++ .../InjectWithKey_DesignTime.ir.txt | 45 ++++++++++++ .../InjectWithKey_DesignTime.mappings.txt | 25 +++++++ .../InjectWithKey_Runtime.ir.txt | 21 ++++++ .../InjectWithKey_Runtime.mappings.txt | 25 +++++++ .../src/Mvc/InjectDirective.cs | 14 +++- 7 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs index 72f50275bea..9c766d9b334 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs @@ -73,7 +73,7 @@ @inject PropertyType PropertyName ""PropertyKey"" Assert.Equal("PropertyName", node.MemberName); Assert.Equal("\"PropertyKey\"", node.KeyName); } - // TODO: Check to see if deduping has been effected + [Fact] public void InjectDirectivePass_Execute_DedupesPropertiesByName() { diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs index 01c1f17d866..6bd6f7590bc 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -333,6 +333,41 @@ public class MyApp AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } + + [Fact] + public void InjectWithKey_Runtime() + { + // Arrange + AddCSharpSyntaxTree(""" + + public class MyModel + { + + } + + public class MyService + { + public string Html { get; set; } + } + + public class MyApp + { + public string MyProperty { get; set; } + } + """); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertLinePragmas(compiled.CodeDocument); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + } + [Fact] public void InjectWithSemicolon_Runtime() { @@ -1255,6 +1290,42 @@ public class MyApp AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } + + [Fact] + public void InjectWithKey_DesignTime() + { + // Arrange + AddCSharpSyntaxTree(""" + + public class MyModel + { + + } + + public class MyService + { + public string Html { get; set; } + } + + public class MyApp + { + public string MyProperty { get; set; } + } + """); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode()); + AssertHtmlDocumentMatchesBaseline(RazorHtmlWriter.GetHtmlDocument(compiled.CodeDocument)); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertLinePragmas(compiled.CodeDocument); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + } + [Fact] public void InjectWithSemicolon_DesignTime() { diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt new file mode 100644 index 00000000000..64e0602a5c2 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt @@ -0,0 +1,45 @@ +Document - + NamespaceDeclaration - - AspNetCoreGeneratedDocument + UsingDirective - - TModel = global::System.Object + UsingDirective - (1:0,1 [20] ) - global::System + UsingDirective - (24:1,1 [40] ) - global::System.Collections.Generic + UsingDirective - (67:2,1 [25] ) - global::System.Linq + UsingDirective - (95:3,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (134:4,1 [38] ) - global::Microsoft.AspNetCore.Mvc + UsingDirective - (175:5,1 [48] ) - global::Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (226:6,1 [51] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures + RazorCompiledItemMetadataAttribute - + CreateNewOnMetadataUpdateAttribute - + ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithModel - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + DesignTimeDirective - + DirectiveToken - (287:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper + DirectiveToken - (350:7,71 [4] ) - Html + DirectiveToken - (364:8,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper + DirectiveToken - (419:8,63 [4] ) - Json + DirectiveToken - (433:9,8 [53] ) - global::Microsoft.AspNetCore.Mvc.IViewComponentHelper + DirectiveToken - (487:9,62 [9] ) - Component + DirectiveToken - (506:10,8 [43] ) - global::Microsoft.AspNetCore.Mvc.IUrlHelper + DirectiveToken - (550:10,52 [3] ) - Url + DirectiveToken - (563:11,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider + DirectiveToken - (634:11,79 [23] ) - ModelExpressionProvider + DirectiveToken - (673:12,14 [104] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (793:13,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (904:14,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (7:0,7 [7] InjectWithModel.cshtml) - MyModel + DirectiveToken - (24:1,8 [5] InjectWithModel.cshtml) - MyApp + DirectiveToken - (30:1,14 [14] InjectWithModel.cshtml) - MyPropertyName + DirectiveToken - (54:2,8 [17] InjectWithModel.cshtml) - MyService + DirectiveToken - (72:2,26 [4] InjectWithModel.cshtml) - Html + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + Inject - + Inject - + Inject - + Inject - + Inject - + Inject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt new file mode 100644 index 00000000000..ead29c02167 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt @@ -0,0 +1,25 @@ +Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|MyModel| +Generated Location: (1224:26,0 [7] ) +|MyModel| + +Source Location: (24:1,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|MyApp| +Generated Location: (1486:36,0 [5] ) +|MyApp| + +Source Location: (30:1,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|MyPropertyName| +Generated Location: (1768:46,22 [14] ) +|MyPropertyName| + +Source Location: (54:2,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|MyService| +Generated Location: (2021:56,0 [17] ) +|MyService| + +Source Location: (72:2,26 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|Html| +Generated Location: (2315:66,22 [4] ) +|Html| + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt new file mode 100644 index 00000000000..79e29c00989 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt @@ -0,0 +1,21 @@ +Document - + RazorCompiledItemAttribute - + NamespaceDeclaration - - AspNetCoreGeneratedDocument + UsingDirective - (1:0,1 [20] ) - global::System + UsingDirective - (24:1,1 [40] ) - global::System.Collections.Generic + UsingDirective - (67:2,1 [25] ) - global::System.Linq + UsingDirective - (95:3,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (134:4,1 [38] ) - global::Microsoft.AspNetCore.Mvc + UsingDirective - (175:5,1 [48] ) - global::Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (226:6,1 [51] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures + RazorSourceChecksumAttribute - + RazorCompiledItemMetadataAttribute - + CreateNewOnMetadataUpdateAttribute - + ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithModel - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + Inject - + Inject - + Inject - + Inject - + Inject - + Inject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt new file mode 100644 index 00000000000..d04a02e4b5d --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt @@ -0,0 +1,25 @@ +Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|MyModel| +Generated Location: (1765:23,0 [7] ) +|MyModel| + +Source Location: (54:2,8 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|MyService| +Generated Location: (2259:40,0 [9] ) +|MyService| + +Source Location: (72:2,26 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|Html| +Generated Location: (2462:48,0 [4] ) +|Html| + +Source Location: (24:1,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|MyApp| +Generated Location: (2790:59,0 [5] ) +|MyApp| + +Source Location: (30:1,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +|MyPropertyName| +Generated Location: (2980:67,0 [14] ) +|MyPropertyName| + diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs index c25042bd52d..7447f142619 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs @@ -86,8 +86,8 @@ protected override void ExecuteCore( } var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); - // No assert as is optional - var keyName = hasKeyName ? tokens[2].Content : null; + // No assert as is optional and check to make sure it is a valid string (not a semi-colon) if it does exist + var keyName = hasKeyName ? ValidateStringToken(tokens[2].Content) : null; var keySpan = hasKeyName ? tokens[2].Source : null; const string tModel = ""; @@ -114,6 +114,16 @@ protected override void ExecuteCore( visitor.Class!.Children.Add(injectNode); } } + + private string ValidateStringToken(string token) + { + // Tokens aren't captured if they're malformed. Therefore, this method will + // always be called with a valid token content. + Debug.Assert(token.StartsWith("\"", StringComparison.Ordinal)); + Debug.Assert(token.EndsWith("\"", StringComparison.Ordinal)); + + return token; + } } private class Visitor : IntermediateNodeWalker From f9569960031bfb9ebb5cd459669035cff5c101c1 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Mon, 9 Feb 2026 02:49:32 +1000 Subject: [PATCH 05/14] WIP move to own directive to avoid semicolon issues --- .../test/InjectDirectiveTest.cs | 9 +- .../test/InjectTargetExtensionTest.cs | 8 +- .../Components/ComponentInjectDirective.cs | 2 - .../ComponentInjectDirectivePass.cs | 7 +- .../ComponentInjectIntermediateNode.cs | 22 +-- .../ComponentKeyedInjectDirective.cs | 39 +++++ .../ComponentKeyedInjectDirectivePass.cs | 76 +++++++++ .../ComponentKeyedInjectIntermediateNode.cs | 97 +++++++++++ .../src/Language/RazorProjectEngine.cs | 1 + .../src/Mvc/IKeyedInjectTargetExtension.cs | 13 ++ .../src/Mvc/InjectDirective.cs | 21 +-- .../src/Mvc/InjectIntermediateNode.cs | 5 - .../src/Mvc/InjectTargetExtension.cs | 15 +- .../src/Mvc/KeyedInjectDirective.cs | 157 ++++++++++++++++++ .../src/Mvc/KeyedInjectIntermediateNode.cs | 73 ++++++++ .../src/Mvc/KeyedInjectTargetExtension.cs | 93 +++++++++++ .../src/Mvc/RazorExtensions.cs | 1 + .../src/Mvc/RazorExtensionsResources.resx | 29 +++- 18 files changed, 595 insertions(+), 73 deletions(-) create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirective.cs create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/IKeyedInjectTargetExtension.cs create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectDirective.cs create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectIntermediateNode.cs create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectTargetExtension.cs diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs index 9c766d9b334..f5f4a74f5d9 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectDirectiveTest.cs @@ -15,6 +15,7 @@ protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder { // Notice we're not registering the InjectDirective.Pass here so we can run it on demand. builder.AddDirective(InjectDirective.Directive); + builder.AddDirective(KeyedInjectDirective.Directive); builder.AddDirective(ModelDirective.Directive); builder.Features.Add(new RazorPageDocumentClassifierPass()); @@ -51,16 +52,16 @@ @inject PropertyType PropertyName [Fact] - public void InjectDirectivePass_Execute_DefinesProperty_WithKey() + public void KeyedInjectDirectivePass_Execute_DefinesProperty() { // Arrange var codeDocument = ProjectEngine.CreateCodeDocument(@" -@inject PropertyType PropertyName ""PropertyKey"" +@keyedinject PropertyType PropertyName ""PropertyKey"" "); var processor = CreateCodeDocumentProcessor(codeDocument); // Act - processor.ExecutePass(); + processor.ExecutePass(); // Assert var documentNode = processor.GetDocumentNode(); @@ -68,7 +69,7 @@ @inject PropertyType PropertyName ""PropertyKey"" Assert.Equal(2, classNode.Children.Count); - var node = Assert.IsType(classNode.Children[1]); + var node = Assert.IsType(classNode.Children[1]); Assert.Equal("PropertyType", node.TypeName); Assert.Equal("PropertyName", node.MemberName); Assert.Equal("\"PropertyKey\"", node.KeyName); diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs index 330ac4ff051..82619059e9c 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs @@ -42,12 +42,12 @@ public void InjectDirectiveTargetExtension_WritesProperty() [Fact] - public void InjectDirectiveTargetExtension_WritesProperty_WithKey() + public void KeyedInjectDirectiveTargetExtension_WritesProperty() { // Arrange using var context = TestCodeRenderingContext.CreateRuntime(); - var target = new InjectTargetExtension(considerNullabilityEnforcement: true); - var node = new InjectIntermediateNode() + var target = new KeyedInjectTargetExtension(considerNullabilityEnforcement: true); + var node = new KeyedInjectIntermediateNode() { TypeName = "PropertyType", MemberName = "PropertyName", @@ -55,7 +55,7 @@ public void InjectDirectiveTargetExtension_WritesProperty_WithKey() }; // Act - target.WriteInjectProperty(context, node); + target.WriteKeyedInjectProperty(context, node); // Assert Assert.Equal(""" diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirective.cs index 1f39dc975c4..06a6d114f7d 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirective.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using Microsoft.AspNetCore.Mvc.Razor.Extensions; namespace Microsoft.AspNetCore.Razor.Language.Components; @@ -21,7 +20,6 @@ internal static class ComponentInjectDirective { builder.AddTypeToken("TypeName", "The type of the service to inject."); builder.AddMemberToken("PropertyName", "The name of the property."); - builder.AddOptionalStringToken("KeyName", "An optional key for when accessing keyed services."); builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; builder.Description = "Inject a service from the application's service container into a property."; }); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirectivePass.cs index aed076a55f7..1d9ef59de24 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectDirectivePass.cs @@ -44,12 +44,7 @@ protected override void ExecuteCore( continue; } - var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); - // No assert as is optional - var keyName = hasKeyName ? tokens[2].Content : null; - var keySpan = hasKeyName ? tokens[2].Source : null; - - classNode!.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan, isMalformed, keyName, keySpan)); + classNode!.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan, isMalformed)); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectIntermediateNode.cs index 80be3258bfc..3527e2234db 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentInjectIntermediateNode.cs @@ -8,28 +8,24 @@ using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Razor.Language.Components; internal class ComponentInjectIntermediateNode : ExtensionIntermediateNode { - private ImmutableArray injectedPropertyModifiers() { - return [ - $"[global::{ComponentsApi.InjectAttribute.FullTypeName}{(!string.IsNullOrEmpty(KeyName) ? "" : $"(Key = {KeyName})")}]", - "private" // Encapsulation is the default - ]; - } + private static readonly ImmutableArray s_injectedPropertyModifiers = + [ + $"[global::{ComponentsApi.InjectAttribute.FullTypeName}]", + "private" // Encapsulation is the default + ]; - public ComponentInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan, bool isMalformed, string keyName, SourceSpan? keySpan) + public ComponentInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan, bool isMalformed) { TypeName = typeName; MemberName = memberName; TypeSpan = typeSpan; MemberSpan = memberSpan; IsMalformed = isMalformed; - KeyName = keyName; - KeySource = keySpan; } public string TypeName { get; } @@ -40,10 +36,6 @@ public ComponentInjectIntermediateNode(string typeName, string memberName, Sourc public SourceSpan? MemberSpan { get; } - public string KeyName { get; set; } - - public SourceSpan? KeySource { get; set; } - public bool IsMalformed { get; } public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; @@ -84,7 +76,7 @@ public override void WriteNode(CodeTarget target, CodeRenderingContext context) if (!context.Options.DesignTime || !IsMalformed) { context.CodeWriter.WriteAutoPropertyDeclaration( - injectedPropertyModifiers(), + s_injectedPropertyModifiers, TypeName, memberName, TypeSpan, diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirective.cs new file mode 100644 index 00000000000..eb5f2765b69 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirective.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System; +using Microsoft.AspNetCore.Mvc.Razor.Extensions; + +namespace Microsoft.AspNetCore.Razor.Language.Components; + +// Much of the following is equivalent to Microsoft.AspNetCore.Mvc.Razor.Extensions's InjectDirective, +// but this one outputs properties annotated for Components's property injector, plus it doesn't need to +// support multiple CodeTargets. + +internal static class ComponentKeyedInjectDirective +{ + public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( + "keyedinject", + DirectiveKind.SingleLine, + builder => + { + builder.AddTypeToken("TypeName", "The type of the service to inject."); + builder.AddMemberToken("PropertyName", "The name of the property."); + builder.AddOptionalStringToken("KeyName", "An optional key for when accessing keyed services."); + builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; + builder.Description = "Inject a service from the application's service container into a property."; + }); + + public static void Register(RazorProjectEngineBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddDirective(Directive, RazorFileKind.Component, RazorFileKind.ComponentImport); + builder.Features.Add(new ComponentInjectDirectivePass()); + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs new file mode 100644 index 00000000000..f1d8dd0fc0c --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.AspNetCore.Razor.Language.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Language.Components; + +internal sealed class ComponentKeyedInjectDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass +{ + protected override void ExecuteCore( + RazorCodeDocument codeDocument, + DocumentIntermediateNode documentNode, + CancellationToken cancellationToken) + { + var visitor = new Visitor(); + visitor.Visit(documentNode); + + var properties = new HashSet(StringComparer.Ordinal); + var classNode = documentNode.FindPrimaryClass(); + + for (var i = visitor.Directives.Count - 1; i >= 0; i--) + { + var directive = visitor.Directives[i]; + var tokens = directive.Children.OfType().ToArray(); + var isMalformed = directive is MalformedDirectiveIntermediateNode; + + var hasType = tokens.Length > 0 && !string.IsNullOrWhiteSpace(tokens[0].Content); + Debug.Assert(hasType || isMalformed); + var typeName = hasType ? tokens[0].Content : string.Empty; + var typeSpan = hasType ? tokens[0].Source : directive.Source?.GetZeroWidthEndSpan(); + + var hasMemberName = tokens.Length > 1 && !string.IsNullOrWhiteSpace(tokens[1].Content); + Debug.Assert(hasMemberName || isMalformed); + var memberName = hasMemberName ? tokens[1].Content : null; + var memberSpan = hasMemberName ? tokens[1].Source : null; + + if (hasMemberName && !properties.Add(memberName!)) + { + continue; + } + + var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); + // No assert as is optional + var keyName = hasKeyName ? tokens[2].Content : null; + var keySpan = hasKeyName ? tokens[2].Source : null; + + classNode!.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan, isMalformed, keyName, keySpan)); + } + } + + private class Visitor : IntermediateNodeWalker + { + public IList Directives { get; } = []; + + public override void VisitDirective(DirectiveIntermediateNode node) + { + if (node.Directive == ComponentInjectDirective.Directive) + { + Directives.Add(node); + } + } + + public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node) + { + if (node.Directive == ComponentInjectDirective.Directive) + { + Directives.Add(node); + } + } + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs new file mode 100644 index 00000000000..e9b6c897879 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System; +using System.Collections.Immutable; +using Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Extensions; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.AspNetCore.Razor.Language.Components; + +internal class ComponentKeyedInjectIntermediateNode : ExtensionIntermediateNode +{ + private ImmutableArray injectedPropertyModifiers() { + return [ + $"[global::{ComponentsApi.InjectAttribute.FullTypeName}{(!string.IsNullOrEmpty(KeyName) ? "" : $"(Key = {KeyName})")}]", + "private" // Encapsulation is the default + ]; + } + + public ComponentInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan, bool isMalformed, string keyName, SourceSpan? keySpan) + { + TypeName = typeName; + MemberName = memberName; + TypeSpan = typeSpan; + MemberSpan = memberSpan; + IsMalformed = isMalformed; + KeyName = keyName; + KeySource = keySpan; + } + + public string TypeName { get; } + + public string MemberName { get; } + + public SourceSpan? TypeSpan { get; } + + public SourceSpan? MemberSpan { get; } + + public string KeyName { get; set; } + + public SourceSpan? KeySource { get; set; } + + public bool IsMalformed { get; } + + public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; + + 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)); + } + + if (TypeName == string.Empty && TypeSpan.HasValue && !context.Options.DesignTime) + { + // if we don't even have a type name, just emit an empty mapped region so that intellisense still works + using (context.BuildEnhancedLinePragma(TypeSpan.Value)) + { + } + } + else + { + var memberName = MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context); + + if (!context.Options.DesignTime || !IsMalformed) + { + context.CodeWriter.WriteAutoPropertyDeclaration( + injectedPropertyModifiers(), + TypeName, + memberName, + TypeSpan, + MemberSpan, + context, + defaultValue: true); + } + } + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs index 3427898e1a1..c50d59ef7a4 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs @@ -403,6 +403,7 @@ private static void AddComponentFeatures(RazorProjectEngineBuilder builder, Razo // Directives (conditional on file kind) ComponentCodeDirective.Register(builder); ComponentInjectDirective.Register(builder); + ComponentKeyedInjectDirective.Register(builder); ComponentLayoutDirective.Register(builder); ComponentPageDirective.Register(builder); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/IKeyedInjectTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/IKeyedInjectTargetExtension.cs new file mode 100644 index 00000000000..fd2a6e4f7f1 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/IKeyedInjectTargetExtension.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using Microsoft.AspNetCore.Razor.Language.CodeGeneration; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; + +public interface IKeyedInjectTargetExtension : ICodeTargetExtension +{ + void WriteKeyedInjectProperty(CodeRenderingContext context, KeyedInjectIntermediateNode node); +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs index 7447f142619..6ca5202a3eb 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectDirective.cs @@ -20,9 +20,7 @@ public static class InjectDirective { builder .AddTypeToken(RazorExtensionsResources.InjectDirective_TypeToken_Name, RazorExtensionsResources.InjectDirective_TypeToken_Description) - .AddMemberToken(RazorExtensionsResources.InjectDirective_MemberToken_Name, RazorExtensionsResources.InjectDirective_MemberToken_Description) - // having this last to avoid validation issues - .AddOptionalStringToken(RazorExtensionsResources.InjectDirective_KeyToken_Name, RazorExtensionsResources.InjectDirective_KeyToken_Description); + .AddMemberToken(RazorExtensionsResources.InjectDirective_MemberToken_Name, RazorExtensionsResources.InjectDirective_MemberToken_Description); builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; builder.Description = RazorExtensionsResources.InjectDirective_Description; @@ -85,11 +83,6 @@ protected override void ExecuteCore( continue; } - var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); - // No assert as is optional and check to make sure it is a valid string (not a semi-colon) if it does exist - var keyName = hasKeyName ? ValidateStringToken(tokens[2].Content) : null; - var keySpan = hasKeyName ? tokens[2].Source : null; - const string tModel = ""; if (typeName.EndsWith(tModel, StringComparison.Ordinal)) { @@ -106,24 +99,12 @@ protected override void ExecuteCore( MemberName = memberName, TypeSource = typeSpan, MemberSource = memberSpan, - KeyName = keyName, - KeySource = keySpan, IsMalformed = isMalformed }; visitor.Class!.Children.Add(injectNode); } } - - private string ValidateStringToken(string token) - { - // Tokens aren't captured if they're malformed. Therefore, this method will - // always be called with a valid token content. - Debug.Assert(token.StartsWith("\"", StringComparison.Ordinal)); - Debug.Assert(token.EndsWith("\"", StringComparison.Ordinal)); - - return token; - } } private class Visitor : IntermediateNodeWalker diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs index 8d2f85dfa69..8ef9bbbe3cc 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectIntermediateNode.cs @@ -22,10 +22,6 @@ public class InjectIntermediateNode : ExtensionIntermediateNode public bool IsMalformed { get; set; } - public string KeyName { get; set; } - - public SourceSpan? KeySource { get; set; } - public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; public override void Accept(IntermediateNodeVisitor visitor) @@ -66,7 +62,6 @@ public override void FormatNode(IntermediateNodeFormatter formatter) formatter.WriteProperty(nameof(MemberName), MemberName); formatter.WriteProperty(nameof(TypeName), TypeName); - formatter.WriteProperty(nameof(KeyName), KeyName); formatter.WriteProperty(nameof(IsMalformed), IsMalformed.ToString()); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs index 20a124ea324..0c1ab5152bd 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/InjectTargetExtension.cs @@ -6,20 +6,12 @@ using System; using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.AspNetCore.Razor.Language.Extensions; -using static Microsoft.AspNetCore.Razor.Language.Components.ComponentNodeWriter; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; public class InjectTargetExtension(bool considerNullabilityEnforcement) : IInjectTargetExtension { private const string RazorInjectAttribute = "[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]"; - private const string RazorInjectAttributeWithAsterix = "[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute*]"; - - private string GetRazorInjectAttributeWithKey(string key) { - - // If there is a key write it into inject attribute - return string.IsNullOrEmpty(key) ? RazorInjectAttribute : RazorInjectAttributeWithAsterix.Replace("*", $"(Key = {key})"); - } public void WriteInjectProperty(CodeRenderingContext context, InjectIntermediateNode node) { @@ -44,10 +36,7 @@ public void WriteInjectProperty(CodeRenderingContext context, InjectIntermediate } else { - var keyName = node.KeyName; - - context.CodeWriter.WriteLine(GetRazorInjectAttributeWithKey(keyName)); - + context.CodeWriter.WriteLine(RazorInjectAttribute); var memberName = node.MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context); context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, memberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true); } @@ -80,7 +69,7 @@ void WriteProperty() } context.CodeWriter - .WriteLine(GetRazorInjectAttributeWithKey(node.KeyName)) + .WriteLine(RazorInjectAttribute) .WriteLine(property); if (considerNullabilityEnforcement && !context.Options.SuppressNullabilityEnforcement) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectDirective.cs new file mode 100644 index 00000000000..21dce134c6c --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectDirective.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; + +public static class KeyedInjectDirective +{ + public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( + "keyedinject", + DirectiveKind.SingleLine, + builder => + { + builder + .AddTypeToken(RazorExtensionsResources.KeyedInjectDirective_TypeToken_Name, RazorExtensionsResources.KeyedInjectDirective_TypeToken_Description) + .AddMemberToken(RazorExtensionsResources.KeyedInjectDirective_MemberToken_Name, RazorExtensionsResources.KeyedInjectDirective_MemberToken_Description) + .AddStringToken(RazorExtensionsResources.KeyedInjectDirective_KeyToken_Name, RazorExtensionsResources.KeyedInjectDirective_KeyToken_Description); + + builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; + builder.Description = RazorExtensionsResources.InjectDirective_Description; + }); + + public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder, bool considerNullabilityEnforcement) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddDirective(Directive); + builder.Features.Add(new Pass()); + builder.AddTargetExtension(new KeyedInjectTargetExtension(considerNullabilityEnforcement)); + return builder; + } + + internal sealed class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass + { + // Runs after the @model and @namespace directives + public override int Order => 10; + + protected override void ExecuteCore( + RazorCodeDocument codeDocument, + DocumentIntermediateNode documentNode, + CancellationToken cancellationToken) + { + if (documentNode.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind && + documentNode.DocumentKind != MvcViewDocumentClassifierPass.MvcViewDocumentKind) + { + // Not a MVC file. Skip. + return; + } + + var visitor = new Visitor(); + visitor.Visit(documentNode); + var modelType = ModelDirective.GetModelType(documentNode).Content; + + var properties = new HashSet(StringComparer.Ordinal); + + for (var i = visitor.Directives.Count - 1; i >= 0; i--) + { + var directive = visitor.Directives[i]; + var tokens = directive.Children.OfType().ToArray(); + var isMalformed = directive is MalformedDirectiveIntermediateNode; + + var hasType = tokens.Length > 0 && !string.IsNullOrWhiteSpace(tokens[0].Content); + Debug.Assert(hasType || isMalformed); + var typeName = hasType ? tokens[0].Content : string.Empty; + var typeSpan = hasType ? tokens[0].Source : directive.Source?.GetZeroWidthEndSpan(); + + var hasMemberName = tokens.Length > 1 && !string.IsNullOrWhiteSpace(tokens[1].Content); + Debug.Assert(hasMemberName || isMalformed); + var memberName = hasMemberName ? tokens[1].Content : null; + var memberSpan = hasMemberName ? tokens[1].Source : null; + + if (hasMemberName && !properties.Add(memberName!)) + { + continue; + } + + var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); + // No assert as is optional but assert make sure it is a valid string (not a semi-colon) if it does exist + var keyName = hasKeyName ? ValidateStringToken(tokens[2].Content) : null; + var keySpan = hasKeyName ? tokens[2].Source : null; + + const string tModel = ""; + if (typeName.EndsWith(tModel, StringComparison.Ordinal)) + { + typeName = typeName[..^tModel.Length] + "<" + modelType + ">"; + if (typeSpan.HasValue) + { + typeSpan = new SourceSpan(typeSpan.Value.FilePath, typeSpan.Value.AbsoluteIndex, typeSpan.Value.LineIndex, typeSpan.Value.CharacterIndex, typeSpan.Value.Length - tModel.Length, typeSpan.Value.LineCount, typeSpan.Value.EndCharacterIndex - tModel.Length); + } + } + + var injectNode = new KeyedInjectIntermediateNode() + { + TypeName = typeName, + MemberName = memberName, + TypeSource = typeSpan, + MemberSource = memberSpan, + KeyName = keyName, + KeySource = keySpan, + IsMalformed = isMalformed + }; + + visitor.Class!.Children.Add(injectNode); + } + } + + private string ValidateStringToken(string token) + { + // Tokens aren't captured if they're malformed. Therefore, this method will + // always be called with a valid token content. + Debug.Assert(token.StartsWith("\"", StringComparison.Ordinal)); + Debug.Assert(token.EndsWith("\"", StringComparison.Ordinal)); + + return token; + } + } + + private class Visitor : IntermediateNodeWalker + { + public ClassDeclarationIntermediateNode? Class { get; private set; } + + public IList Directives { get; } = []; + + public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node) + { + Class ??= node; + + base.VisitClassDeclaration(node); + } + + public override void VisitDirective(DirectiveIntermediateNode node) + { + if (node.Directive == Directive) + { + Directives.Add(node); + } + } + + public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node) + { + if (node.Directive == Directive) + { + Directives.Add(node); + } + } + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectIntermediateNode.cs new file mode 100644 index 00000000000..e598ed5ac41 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectIntermediateNode.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; + +public class KeyedInjectIntermediateNode : ExtensionIntermediateNode +{ + public string TypeName { get; set; } + + public SourceSpan? TypeSource { get; set; } + + public string MemberName { get; set; } + + public SourceSpan? MemberSource { get; set; } + + public bool IsMalformed { get; set; } + + public string KeyName { get; set; } + + public SourceSpan? KeySource { get; set; } + + public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; + + 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.WriteKeyedInjectProperty(context, this); + } + + public override void FormatNode(IntermediateNodeFormatter formatter) + { + formatter.WriteContent(MemberName); + + formatter.WriteProperty(nameof(MemberName), MemberName); + formatter.WriteProperty(nameof(TypeName), TypeName); + formatter.WriteProperty(nameof(KeyName), KeyName); + formatter.WriteProperty(nameof(IsMalformed), IsMalformed.ToString()); + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectTargetExtension.cs new file mode 100644 index 00000000000..09bdd7eaae6 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectTargetExtension.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System; +using Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Extensions; +using static Microsoft.AspNetCore.Razor.Language.Components.ComponentNodeWriter; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; + +public class KeyedInjectTargetExtension(bool considerNullabilityEnforcement) : IKeyedInjectTargetExtension +{ + private const string RazorInjectAttribute = "[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]"; + private const string RazorInjectAttributeWithAsterix = "[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute*]"; + + private string GetRazorInjectAttributeWithKey(string key) { + + // If there is a key write it into inject attribute + return string.IsNullOrEmpty(key) ? RazorInjectAttribute : RazorInjectAttributeWithAsterix.Replace("*", $"(Key = {key})"); + } + + public void WriteKeyedInjectProperty(CodeRenderingContext context, KeyedInjectIntermediateNode node) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (!context.Options.DesignTime && !string.IsNullOrWhiteSpace(node.TypeSource?.FilePath)) + { + if (node.TypeName == "") + { + // if we don't even have a type name, just emit an empty mapped region so that intellisense still works + using (context.BuildEnhancedLinePragma(node.TypeSource.Value)) + { + } + } + else + { + var keyName = node.KeyName; + + context.CodeWriter.WriteLine(GetRazorInjectAttributeWithKey(keyName)); + + var memberName = node.MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context); + context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, memberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true); + } + } + else if (!node.IsMalformed) + { + var property = $"public {node.TypeName} {node.MemberName} {{ get; private set; }}"; + if (considerNullabilityEnforcement && !context.Options.SuppressNullabilityEnforcement) + { + property += " = default!;"; + } + + if (node.Source.HasValue) + { + using (context.BuildLinePragma(node.Source.Value)) + { + WriteProperty(); + } + } + else + { + WriteProperty(); + } + + void WriteProperty() + { + if (considerNullabilityEnforcement && !context.Options.SuppressNullabilityEnforcement) + { + context.CodeWriter.WriteLine("#nullable restore"); + } + + context.CodeWriter + .WriteLine(GetRazorInjectAttributeWithKey(node.KeyName)) + .WriteLine(property); + + if (considerNullabilityEnforcement && !context.Options.SuppressNullabilityEnforcement) + { + context.CodeWriter.WriteLine("#nullable disable"); + } + } + } + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensions.cs index 027d6998f99..a55f82fd622 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensions.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensions.cs @@ -16,6 +16,7 @@ public static void Register(RazorProjectEngineBuilder builder) ArgHelper.ThrowIfNull(builder); InjectDirective.Register(builder, considerNullabilityEnforcement: true); + KeyedInjectDirective.Register(builder, considerNullabilityEnforcement: true); ModelDirective.Register(builder); PageDirective.Register(builder); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensionsResources.resx b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensionsResources.resx index 22e96b67d19..475427ae511 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensionsResources.resx +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorExtensionsResources.resx @@ -1,4 +1,4 @@ - + Specify the view or page model for the page. @@ -195,4 +216,4 @@ Method '{0}' of view component '{1}' should be declared to return a value. - \ No newline at end of file + From 54cc5271233595e37fbc4ba0f797d1570269a8d3 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Mon, 9 Feb 2026 22:15:00 +1000 Subject: [PATCH 06/14] WIP fixed collision issue --- .../ComponentKeyedInjectDirective.cs | 6 ++--- .../ComponentKeyedInjectDirectivePass.cs | 6 ++--- .../ComponentKeyedInjectIntermediateNode.cs | 2 +- .../src/Mvc/KeyedInjectDirective.cs | 24 +++++++++++++++---- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirective.cs index eb5f2765b69..71a93bff63b 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirective.cs @@ -21,9 +21,9 @@ internal static class ComponentKeyedInjectDirective { builder.AddTypeToken("TypeName", "The type of the service to inject."); builder.AddMemberToken("PropertyName", "The name of the property."); - builder.AddOptionalStringToken("KeyName", "An optional key for when accessing keyed services."); + builder.AddStringToken("KeyName", "A key for when accessing keyed services."); builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; - builder.Description = "Inject a service from the application's service container into a property."; + builder.Description = "Inject a keyed service from the application's service container into a property."; }); public static void Register(RazorProjectEngineBuilder builder) @@ -34,6 +34,6 @@ public static void Register(RazorProjectEngineBuilder builder) } builder.AddDirective(Directive, RazorFileKind.Component, RazorFileKind.ComponentImport); - builder.Features.Add(new ComponentInjectDirectivePass()); + builder.Features.Add(new ComponentKeyedInjectDirectivePass()); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs index f1d8dd0fc0c..17dca636056 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs @@ -49,7 +49,7 @@ protected override void ExecuteCore( var keyName = hasKeyName ? tokens[2].Content : null; var keySpan = hasKeyName ? tokens[2].Source : null; - classNode!.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan, isMalformed, keyName, keySpan)); + classNode!.Children.Add(new ComponentKeyedInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan, isMalformed, keyName, keySpan)); } } @@ -59,7 +59,7 @@ private class Visitor : IntermediateNodeWalker public override void VisitDirective(DirectiveIntermediateNode node) { - if (node.Directive == ComponentInjectDirective.Directive) + if (node.Directive == ComponentKeyedInjectDirective.Directive) { Directives.Add(node); } @@ -67,7 +67,7 @@ public override void VisitDirective(DirectiveIntermediateNode node) public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node) { - if (node.Directive == ComponentInjectDirective.Directive) + if (node.Directive == ComponentKeyedInjectDirective.Directive) { Directives.Add(node); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs index e9b6c897879..6063c17b566 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs @@ -21,7 +21,7 @@ private ImmutableArray injectedPropertyModifiers() { ]; } - public ComponentInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan, bool isMalformed, string keyName, SourceSpan? keySpan) + public ComponentKeyedInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan, bool isMalformed, string keyName, SourceSpan? keySpan) { TypeName = typeName; MemberName = memberName; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectDirective.cs index 21dce134c6c..f3d49a5797a 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/KeyedInjectDirective.cs @@ -24,7 +24,7 @@ public static class KeyedInjectDirective .AddStringToken(RazorExtensionsResources.KeyedInjectDirective_KeyToken_Name, RazorExtensionsResources.KeyedInjectDirective_KeyToken_Description); builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; - builder.Description = RazorExtensionsResources.InjectDirective_Description; + builder.Description = RazorExtensionsResources.KeyedInjectDirective_Description; }); public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder, bool considerNullabilityEnforcement) @@ -61,6 +61,20 @@ protected override void ExecuteCore( visitor.Visit(documentNode); var modelType = ModelDirective.GetModelType(documentNode).Content; + // Stop collisions with existing inject directives + var existingMembers = new HashSet(StringComparer.Ordinal); + if (visitor.Class != null) + { + foreach (var property in visitor.Class.Children + .OfType()) + { + if (!string.IsNullOrEmpty(property.MemberName)) + { + existingMembers.Add(property.MemberName); + } + } + } + var properties = new HashSet(StringComparer.Ordinal); for (var i = visitor.Directives.Count - 1; i >= 0; i--) @@ -78,14 +92,14 @@ protected override void ExecuteCore( Debug.Assert(hasMemberName || isMalformed); var memberName = hasMemberName ? tokens[1].Content : null; var memberSpan = hasMemberName ? tokens[1].Source : null; - - if (hasMemberName && !properties.Add(memberName!)) + // continue if the membername is in any existing inject statement or in a previous keyedinject statement + if (hasMemberName && (!properties.Add(memberName!) || existingMembers.Contains(memberName!))) { continue; } var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); - // No assert as is optional but assert make sure it is a valid string (not a semi-colon) if it does exist + Debug.Assert(hasKeyName || isMalformed); var keyName = hasKeyName ? ValidateStringToken(tokens[2].Content) : null; var keySpan = hasKeyName ? tokens[2].Source : null; @@ -114,7 +128,7 @@ protected override void ExecuteCore( } } - private string ValidateStringToken(string token) + private static string ValidateStringToken(string token) { // Tokens aren't captured if they're malformed. Therefore, this method will // always be called with a valid token content. From 908104b10da5a784ea4e83dd0bf206e392b799d1 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Mon, 9 Feb 2026 22:54:16 +1000 Subject: [PATCH 07/14] WIP integration tests --- .../InjectWithKey.cshtml | 5 +++ .../InjectWithKeyAfter.cshtml | 5 +++ .../InjectWithKey_DesignTime.ir.txt | 20 +++++---- .../InjectWithKey_DesignTime.mappings.txt | 41 ++++++++++++++----- .../InjectWithKey_Runtime.ir.txt | 4 +- .../InjectWithKey_Runtime.mappings.txt | 10 ++--- 6 files changed, 61 insertions(+), 24 deletions(-) create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKeyAfter.cshtml diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml new file mode 100644 index 00000000000..f4a64bd21f6 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml @@ -0,0 +1,5 @@ +@model MyModel +@keyedinject MyApp MyPropertyName "KeyOne" +@keyedinject MyService Html "KeyOne" +@keyedinject MyApp MyPropertyName2 "SomeKey" ; +@keyedinject MyService Html2 "SomeKey" ; diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKeyAfter.cshtml b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKeyAfter.cshtml new file mode 100644 index 00000000000..3b9d9f02d3f --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKeyAfter.cshtml @@ -0,0 +1,5 @@ +@model MyModel +@keyedinject MyApp MyPropertyName "KeyOne" +@keyedinject MyService ServiceOne "KeyOne" +@keyedinject MyApp MyPropertyName2 "SomeKey" ; +@inject MyService ServiceOne diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt index 64e0602a5c2..d61b2df7b06 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt @@ -1,4 +1,4 @@ -Document - +Document - NamespaceDeclaration - - AspNetCoreGeneratedDocument UsingDirective - - TModel = global::System.Object UsingDirective - (1:0,1 [20] ) - global::System @@ -10,7 +10,7 @@ UsingDirective - (226:6,1 [51] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures RazorCompiledItemMetadataAttribute - CreateNewOnMetadataUpdateAttribute - - ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithModel - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithKey - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - DesignTimeDirective - DirectiveToken - (287:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper DirectiveToken - (350:7,71 [4] ) - Html @@ -25,11 +25,15 @@ DirectiveToken - (673:12,14 [104] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (793:13,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (904:14,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor - DirectiveToken - (7:0,7 [7] InjectWithModel.cshtml) - MyModel - DirectiveToken - (24:1,8 [5] InjectWithModel.cshtml) - MyApp - DirectiveToken - (30:1,14 [14] InjectWithModel.cshtml) - MyPropertyName - DirectiveToken - (54:2,8 [17] InjectWithModel.cshtml) - MyService - DirectiveToken - (72:2,26 [4] InjectWithModel.cshtml) - Html + DirectiveToken - (7:0,7 [7] InjectWithKey.cshtml) - MyModel + DirectiveToken - (24:1,8 [5] InjectWithKey.cshtml) - MyApp + DirectiveToken - (30:1,14 [14] InjectWithKey.cshtml) - MyPropertyName + DirectiveToken - (58:2,8 [17] InjectWithKey.cshtml) - MyService + DirectiveToken - (76:2,26 [4] InjectWithKey.cshtml) - Html + DirectiveToken - (93:3,8 [5] InjectWithKey.cshtml) - MyApp + DirectiveToken - (99:3,14 [15] InjectWithKey.cshtml) - MyPropertyName2 + DirectiveToken - (129:4,8 [17] InjectWithKey.cshtml) - MyService + DirectiveToken - (147:4,26 [5] InjectWithKey.cshtml) - Html2 CSharpCode - IntermediateToken - - CSharp - #pragma warning disable 0414 CSharpCode - @@ -43,3 +47,5 @@ Inject - Inject - Inject - + Inject - + Inject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt index ead29c02167..77ccc2c161e 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt @@ -1,25 +1,46 @@ -Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +#Gotta add the extra characters to these +Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyModel| -Generated Location: (1224:26,0 [7] ) +Generated Location: (1236:26,0 [7] ) |MyModel| -Source Location: (24:1,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (24:1,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyApp| -Generated Location: (1486:36,0 [5] ) +Generated Location: (1502:36,0 [5] ) |MyApp| -Source Location: (30:1,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (30:1,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyPropertyName| -Generated Location: (1768:46,22 [14] ) +Generated Location: (1788:46,22 [14] ) |MyPropertyName| -Source Location: (54:2,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (58:2,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyService| -Generated Location: (2021:56,0 [17] ) +Generated Location: (2045:56,0 [17] ) |MyService| -Source Location: (72:2,26 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (76:2,26 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |Html| -Generated Location: (2315:66,22 [4] ) +Generated Location: (2343:66,22 [4] ) |Html| +Source Location: (93:3,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|MyApp| +Generated Location: (2590:76,0 [5] ) +|MyApp| + +Source Location: (99:3,14 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|MyPropertyName2| +Generated Location: (2876:86,22 [15] ) +|MyPropertyName2| + +Source Location: (129:4,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|MyService| +Generated Location: (3134:96,0 [17] ) +|MyService| + +Source Location: (147:4,26 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|Html2| +Generated Location: (3432:106,22 [5] ) +|Html2| + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt index 79e29c00989..c17b2d1004f 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt @@ -1,4 +1,4 @@ -Document - +Document - RazorCompiledItemAttribute - NamespaceDeclaration - - AspNetCoreGeneratedDocument UsingDirective - (1:0,1 [20] ) - global::System @@ -11,7 +11,7 @@ RazorSourceChecksumAttribute - RazorCompiledItemMetadataAttribute - CreateNewOnMetadataUpdateAttribute - - ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithModel - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithKey - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync Inject - Inject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt index d04a02e4b5d..7efd3265d6f 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt @@ -1,24 +1,24 @@ -Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyModel| Generated Location: (1765:23,0 [7] ) |MyModel| -Source Location: (54:2,8 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (54:2,8 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyService| Generated Location: (2259:40,0 [9] ) |MyService| -Source Location: (72:2,26 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (72:2,26 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |Html| Generated Location: (2462:48,0 [4] ) |Html| -Source Location: (24:1,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (24:1,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyApp| Generated Location: (2790:59,0 [5] ) |MyApp| -Source Location: (30:1,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithModel.cshtml) +Source Location: (30:1,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyPropertyName| Generated Location: (2980:67,0 [14] ) |MyPropertyName| From 897e1ee060793a0ff7f8d63f977d8adc6901e991 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Tue, 10 Feb 2026 20:02:55 +1000 Subject: [PATCH 08/14] WIP integration tests --- .../CodeGenerationIntegrationTest.cs | 34 +++ .../InjectWithKey_DesignTime.codegen.cs | 200 ++++++++++++++++++ .../InjectWithKey_DesignTime.ir.txt | 26 ++- .../InjectWithKey_DesignTime.mappings.txt | 34 +-- ...thSemicolon_DesignTime - Copy.codegen.html | 5 + ...njectWithSemicolon_DesignTime.mappings.txt | 2 +- 6 files changed, 272 insertions(+), 29 deletions(-) create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.cs create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime - Copy.codegen.html diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs index 6bd6f7590bc..7dd01a330c9 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -1326,6 +1326,40 @@ public class MyApp AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } + [Fact] + public void InjectWithKeyAfter_DesignTime() + { + // Arrange + AddCSharpSyntaxTree(""" + + public class MyModel + { + + } + + public class MyService + { + public string Html { get; set; } + } + + public class MyApp + { + public string MyProperty { get; set; } + } + """); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode()); + AssertHtmlDocumentMatchesBaseline(RazorHtmlWriter.GetHtmlDocument(compiled.CodeDocument)); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertLinePragmas(compiled.CodeDocument); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + } [Fact] public void InjectWithSemicolon_DesignTime() { diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.cs new file mode 100644 index 00000000000..4af73c7ea04 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.cs @@ -0,0 +1,200 @@ +{// +#pragma warning disable 1591 +namespace AspNetCoreGeneratedDocument +{ +#line default + using TModel = global::System.Object; + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Mvc; + using global::Microsoft.AspNetCore.Mvc.Rendering; + using global::Microsoft.AspNetCore.Mvc.ViewFeatures; +#line default +#line hidden + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml")] + [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] +#nullable restore + internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithKey : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage +#nullable disable + { +#pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() + { + ((global::System.Action)(() => { +#nullable restore +#line 1 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + MyModel __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + MyApp __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + global::System.Object MyPropertyName = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + global::System.Object __typeHelper = "KeyOne"; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + MyService __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + global::System.Object Html = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + global::System.Object __typeHelper = "KeyOne"; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + MyApp __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + global::System.Object MyPropertyName2 = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + global::System.Object __typeHelper = "SomeKey"; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + MyService __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + global::System.Object Html2 = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" + global::System.Object __typeHelper = "SomeKey"; + +#line default +#line hidden +#nullable disable + } + ))(); + } +#pragma warning restore 219 +#pragma warning disable 0414 + private static object __o = null; +#pragma warning restore 0414 +#pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } +#pragma warning restore 1998 +#nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; +#nullable disable +#nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; +#nullable disable +#nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; +#nullable disable +#nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; +#nullable disable +#nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; +#nullable disable +#nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "SomeKey")] + public MyService Html2 { get; private set; } = default!; +#nullable disable +#nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "SomeKey")] + public MyApp MyPropertyName2 { get; private set; } = default!; +#nullable disable +#nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "KeyOne")] + public MyApp MyPropertyName { get; private set; } = default!; +#nullable disable + } +} +#pragma warning restore 1591 +} diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt index d61b2df7b06..7647f1beb9f 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.ir.txt @@ -26,14 +26,18 @@ Document - DirectiveToken - (793:13,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (904:14,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (7:0,7 [7] InjectWithKey.cshtml) - MyModel - DirectiveToken - (24:1,8 [5] InjectWithKey.cshtml) - MyApp - DirectiveToken - (30:1,14 [14] InjectWithKey.cshtml) - MyPropertyName - DirectiveToken - (58:2,8 [17] InjectWithKey.cshtml) - MyService - DirectiveToken - (76:2,26 [4] InjectWithKey.cshtml) - Html - DirectiveToken - (93:3,8 [5] InjectWithKey.cshtml) - MyApp - DirectiveToken - (99:3,14 [15] InjectWithKey.cshtml) - MyPropertyName2 - DirectiveToken - (129:4,8 [17] InjectWithKey.cshtml) - MyService - DirectiveToken - (147:4,26 [5] InjectWithKey.cshtml) - Html2 + DirectiveToken - (29:1,13 [5] InjectWithKey.cshtml) - MyApp + DirectiveToken - (35:1,19 [14] InjectWithKey.cshtml) - MyPropertyName + DirectiveToken - (51:1,35 [8] InjectWithKey.cshtml) - "KeyOne" + DirectiveToken - (74:2,13 [17] InjectWithKey.cshtml) - MyService + DirectiveToken - (92:2,31 [4] InjectWithKey.cshtml) - Html + DirectiveToken - (97:2,36 [8] InjectWithKey.cshtml) - "KeyOne" + DirectiveToken - (120:3,13 [5] InjectWithKey.cshtml) - MyApp + DirectiveToken - (126:3,19 [15] InjectWithKey.cshtml) - MyPropertyName2 + DirectiveToken - (142:3,35 [9] InjectWithKey.cshtml) - "SomeKey" + DirectiveToken - (168:4,13 [17] InjectWithKey.cshtml) - MyService + DirectiveToken - (186:4,31 [5] InjectWithKey.cshtml) - Html2 + DirectiveToken - (192:4,37 [9] InjectWithKey.cshtml) - "SomeKey" CSharpCode - IntermediateToken - - CSharp - #pragma warning disable 0414 CSharpCode - @@ -46,6 +50,6 @@ Document - Inject - Inject - Inject - - Inject - - Inject - - Inject - + KeyedInject - + KeyedInject - + KeyedInject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt index 77ccc2c161e..ca0597cbce7 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt @@ -1,46 +1,46 @@ #Gotta add the extra characters to these Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyModel| -Generated Location: (1236:26,0 [7] ) +Generated Location: (1220:28,0 [7] ) |MyModel| -Source Location: (24:1,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (29:1,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyApp| -Generated Location: (1502:36,0 [5] ) +Generated Location: (1508:38,0 [5] ) |MyApp| -Source Location: (30:1,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (35:1,19 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyPropertyName| -Generated Location: (1788:46,22 [14] ) +Generated Location: (1816:48,22 [14] ) |MyPropertyName| -Source Location: (58:2,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (74:2,13 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyService| -Generated Location: (2045:56,0 [17] ) +Generated Location: (2397:68,0 [17] ) |MyService| -Source Location: (76:2,26 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (92:2,31 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |Html| -Generated Location: (2343:66,22 [4] ) +Generated Location: (2717:78,22 [4] ) |Html| -Source Location: (93:3,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (120:3,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyApp| -Generated Location: (2590:76,0 [5] ) +Generated Location: (3288:98,0 [5] ) |MyApp| -Source Location: (99:3,14 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (126:3,19 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyPropertyName2| -Generated Location: (2876:86,22 [15] ) +Generated Location: (3596:108,22 [15] ) |MyPropertyName2| -Source Location: (129:4,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (168:4,13 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyService| -Generated Location: (3134:96,0 [17] ) +Generated Location: (4179:128,0 [17] ) |MyService| -Source Location: (147:4,26 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (186:4,31 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |Html2| -Generated Location: (3432:106,22 [5] ) +Generated Location: (4499:138,22 [5] ) |Html2| diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime - Copy.codegen.html b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime - Copy.codegen.html new file mode 100644 index 00000000000..d9302bcff03 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime - Copy.codegen.html @@ -0,0 +1,5 @@ +/*~~*/ /*~~~*/ +/*~~~*/ /*~*/ /*~~~~~~~~~~~*/ +/*~~~*/ /*~~~~~~~~~~~~~*/ /*~*/ +/*~~~*/ /*~*/ /*~~~~~~~~~~~*/ ~ +/*~~~*/ /*~~~~~~~~~~~~~*/ /*~*/ ~ diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime.mappings.txt index cb492a0fcca..bbbf2007e9c 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime.mappings.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime.mappings.txt @@ -1,4 +1,4 @@ -Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon.cshtml) +Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon.cshtml) |MyModel| Generated Location: (1236:26,0 [7] ) |MyModel| From f691557f713a3342ee32047346d47410e9161287 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Tue, 10 Feb 2026 20:38:16 +1000 Subject: [PATCH 09/14] Semicolon Integration Test --- .../InjectWithKey_DesignTime.codegen.cs | 164 +++++++++--------- .../InjectWithKey_DesignTime.codegen.html | 5 + .../InjectWithKey_DesignTime.mappings.txt | 39 +++-- ...thSemicolon_DesignTime - Copy.codegen.html | 5 - 4 files changed, 115 insertions(+), 98 deletions(-) create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.html delete mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime - Copy.codegen.html diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.cs index 4af73c7ea04..6887194898e 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.cs @@ -1,8 +1,8 @@ -{// +// #pragma warning disable 1591 namespace AspNetCoreGeneratedDocument { -#line default + #line default using TModel = global::System.Object; using global::System; using global::System.Collections.Generic; @@ -11,190 +11,188 @@ namespace AspNetCoreGeneratedDocument using global::Microsoft.AspNetCore.Mvc; using global::Microsoft.AspNetCore.Mvc.Rendering; using global::Microsoft.AspNetCore.Mvc.ViewFeatures; -#line default -#line hidden + #line default + #line hidden [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml")] [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] -#nullable restore + #nullable restore internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithKey : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage -#nullable disable + #nullable disable { -#pragma warning disable 219 - private void __RazorDirectiveTokenHelpers__() - { - ((global::System.Action)(() => { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((global::System.Action)(() => { #nullable restore #line 1 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - MyModel __typeHelper = default!; +MyModel __typeHelper = default!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - MyApp __typeHelper = default!; +MyApp __typeHelper = default!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - global::System.Object MyPropertyName = null!; +global::System.Object MyPropertyName = null!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - global::System.Object __typeHelper = "KeyOne"; +global::System.Object __typeHelper = "KeyOne"; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - MyService __typeHelper = default!; +MyService __typeHelper = default!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - global::System.Object Html = null!; +global::System.Object Html = null!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - global::System.Object __typeHelper = "KeyOne"; +global::System.Object __typeHelper = "KeyOne"; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - MyApp __typeHelper = default!; +MyApp __typeHelper = default!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - global::System.Object MyPropertyName2 = null!; +global::System.Object MyPropertyName2 = null!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - global::System.Object __typeHelper = "SomeKey"; +global::System.Object __typeHelper = "SomeKey"; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - MyService __typeHelper = default!; +MyService __typeHelper = default!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - global::System.Object Html2 = null!; +global::System.Object Html2 = null!; #line default #line hidden #nullable disable - } - ))(); - ((global::System.Action)(() => { + } + ))(); + ((global::System.Action)(() => { #nullable restore #line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" - global::System.Object __typeHelper = "SomeKey"; +global::System.Object __typeHelper = "SomeKey"; #line default #line hidden #nullable disable - } - ))(); } -#pragma warning restore 219 -#pragma warning disable 0414 + ))(); + } + #pragma warning restore 219 + #pragma warning disable 0414 private static object __o = null; -#pragma warning restore 0414 -#pragma warning disable 1998 + #pragma warning restore 0414 + #pragma warning disable 1998 public async override global::System.Threading.Tasks.Task ExecuteAsync() { } -#pragma warning restore 1998 -#nullable restore + #pragma warning restore 1998 + #nullable restore [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; -#nullable disable -#nullable restore + #nullable disable + #nullable restore [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; -#nullable disable -#nullable restore + #nullable disable + #nullable restore [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; -#nullable disable -#nullable restore + #nullable disable + #nullable restore [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; -#nullable disable -#nullable restore + #nullable disable + #nullable restore [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; -#nullable disable -#nullable restore + #nullable disable + #nullable restore [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "SomeKey")] public MyService Html2 { get; private set; } = default!; -#nullable disable -#nullable restore + #nullable disable + #nullable restore [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "SomeKey")] public MyApp MyPropertyName2 { get; private set; } = default!; -#nullable disable -#nullable restore + #nullable disable + #nullable restore [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "KeyOne")] public MyApp MyPropertyName { get; private set; } = default!; -#nullable disable + #nullable disable } } #pragma warning restore 1591 -} diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.html b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.html new file mode 100644 index 00000000000..0c00766321c --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.codegen.html @@ -0,0 +1,5 @@ +/*~~*/ /*~~~*/ +/*~~~~~~~~*/ /*~*/ /*~~~~~~~~~~*/ /*~~~~*/ +/*~~~~~~~~*/ /*~~~~~~~~~~~~~*/ /**/ /*~~~~*/ +/*~~~~~~~~*/ /*~*/ /*~~~~~~~~~~~*/ /*~~~~~*/ ~ +/*~~~~~~~~*/ /*~~~~~~~~~~~~~*/ /*~*/ /*~~~~~*/ ~ diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt index ca0597cbce7..97114ac3f15 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_DesignTime.mappings.txt @@ -1,46 +1,65 @@ -#Gotta add the extra characters to these Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyModel| -Generated Location: (1220:28,0 [7] ) +Generated Location: (1218:26,0 [7] ) |MyModel| Source Location: (29:1,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyApp| -Generated Location: (1508:38,0 [5] ) +Generated Location: (1478:36,0 [5] ) |MyApp| Source Location: (35:1,19 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyPropertyName| -Generated Location: (1816:48,22 [14] ) +Generated Location: (1758:46,22 [14] ) |MyPropertyName| +Source Location: (51:1,35 [8] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|"KeyOne"| +Generated Location: (2046:56,37 [8] ) +|"KeyOne"| + Source Location: (74:2,13 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyService| -Generated Location: (2397:68,0 [17] ) +Generated Location: (2283:66,0 [17] ) |MyService| Source Location: (92:2,31 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |Html| -Generated Location: (2717:78,22 [4] ) +Generated Location: (2575:76,22 [4] ) |Html| +Source Location: (97:2,36 [8] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|"KeyOne"| +Generated Location: (2853:86,37 [8] ) +|"KeyOne"| + Source Location: (120:3,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyApp| -Generated Location: (3288:98,0 [5] ) +Generated Location: (3090:96,0 [5] ) |MyApp| Source Location: (126:3,19 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyPropertyName2| -Generated Location: (3596:108,22 [15] ) +Generated Location: (3370:106,22 [15] ) |MyPropertyName2| +Source Location: (142:3,35 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|"SomeKey"| +Generated Location: (3659:116,37 [9] ) +|"SomeKey"| + Source Location: (168:4,13 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyService| -Generated Location: (4179:128,0 [17] ) +Generated Location: (3897:126,0 [17] ) |MyService| Source Location: (186:4,31 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |Html2| -Generated Location: (4499:138,22 [5] ) +Generated Location: (4189:136,22 [5] ) |Html2| +Source Location: (192:4,37 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|"SomeKey"| +Generated Location: (4468:146,37 [9] ) +|"SomeKey"| + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime - Copy.codegen.html b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime - Copy.codegen.html deleted file mode 100644 index d9302bcff03..00000000000 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithSemicolon_DesignTime - Copy.codegen.html +++ /dev/null @@ -1,5 +0,0 @@ -/*~~*/ /*~~~*/ -/*~~~*/ /*~*/ /*~~~~~~~~~~~*/ -/*~~~*/ /*~~~~~~~~~~~~~*/ /*~*/ -/*~~~*/ /*~*/ /*~~~~~~~~~~~*/ ~ -/*~~~*/ /*~~~~~~~~~~~~~*/ /*~*/ ~ From 0b7b9651fd8ade29a623b61baa208d8957bcfb0b Mon Sep 17 00:00:00 2001 From: wlsquid Date: Tue, 10 Feb 2026 21:46:20 +1000 Subject: [PATCH 10/14] WIP runtime test --- .../InjectWithKey_Runtime.codegen.cs | 116 ++++++++++++++++++ .../InjectWithKey_Runtime.ir.txt | 4 +- .../InjectWithKey_Runtime.mappings.txt | 32 +++-- .../ComponentKeyedInjectDirectivePass.cs | 21 +++- 4 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.codegen.cs diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.codegen.cs new file mode 100644 index 00000000000..69d9d83df78 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.codegen.cs @@ -0,0 +1,116 @@ +#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "d201ecac63a18768701926f12ca75703f61ae21a3fac9eee4ae94b14be150e27" +// +#pragma warning disable 1591 +[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithKey), @"mvc.1.0.view", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml")] +namespace AspNetCoreGeneratedDocument +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Mvc; + using global::Microsoft.AspNetCore.Mvc.Rendering; + using global::Microsoft.AspNetCore.Mvc.ViewFeatures; + #line default + #line hidden + [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"Sha256", @"d201ecac63a18768701926f12ca75703f61ae21a3fac9eee4ae94b14be150e27", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml")] + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml")] + [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] + #nullable restore + internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InjectWithKey : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage< +#nullable restore +#line (1,8)-(1,15) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" +MyModel + +#line default +#line hidden +#nullable disable + > + #nullable disable + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; + #nullable disable + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "SomeKey")] + public +#nullable restore +#line (5,14)-(5,23) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" +MyService + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (5,32)-(5,37) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" +Html2 + +#line default +#line hidden +#nullable disable + { get; private set; } + = default!; + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "SomeKey")] + public +#nullable restore +#line (4,14)-(4,19) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" +MyApp + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (4,20)-(4,35) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" +MyPropertyName2 + +#line default +#line hidden +#nullable disable + { get; private set; } + = default!; + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "KeyOne")] + public +#nullable restore +#line (2,14)-(2,19) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" +MyApp + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (2,20)-(2,34) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml" +MyPropertyName + +#line default +#line hidden +#nullable disable + { get; private set; } + = default!; + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt index c17b2d1004f..63cd065ed9d 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.ir.txt @@ -18,4 +18,6 @@ Document - Inject - Inject - Inject - - Inject - + KeyedInject - + KeyedInject - + KeyedInject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt index 7efd3265d6f..7a2f78f2aee 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey_Runtime.mappings.txt @@ -1,25 +1,35 @@ Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyModel| -Generated Location: (1765:23,0 [7] ) +Generated Location: (1751:23,0 [7] ) |MyModel| -Source Location: (54:2,8 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (168:4,13 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyService| -Generated Location: (2259:40,0 [9] ) +Generated Location: (3517:60,0 [9] ) |MyService| -Source Location: (72:2,26 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) -|Html| -Generated Location: (2462:48,0 [4] ) -|Html| +Source Location: (186:4,31 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|Html2| +Generated Location: (3718:68,0 [5] ) +|Html2| -Source Location: (24:1,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (120:3,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyApp| -Generated Location: (2790:59,0 [5] ) +Generated Location: (4063:79,0 [5] ) |MyApp| -Source Location: (30:1,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +Source Location: (126:3,19 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|MyPropertyName2| +Generated Location: (4251:87,0 [15] ) +|MyPropertyName2| + +Source Location: (29:1,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) +|MyApp| +Generated Location: (4605:98,0 [5] ) +|MyApp| + +Source Location: (35:1,19 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKey.cshtml) |MyPropertyName| -Generated Location: (2980:67,0 [14] ) +Generated Location: (4793:106,0 [14] ) |MyPropertyName| diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs index 17dca636056..13b8e8c785a 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectDirectivePass.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using Microsoft.AspNetCore.Mvc.Razor.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Razor.Language.Components; @@ -20,6 +21,20 @@ protected override void ExecuteCore( var visitor = new Visitor(); visitor.Visit(documentNode); + // Stop collisions with existing inject directives + var existingMembers = new HashSet(StringComparer.Ordinal); + if (documentNode != null) + { + foreach (var property in documentNode.Children + .OfType()) + { + if (!string.IsNullOrEmpty(property.MemberName)) + { + existingMembers.Add(property.MemberName); + } + } + } + var properties = new HashSet(StringComparer.Ordinal); var classNode = documentNode.FindPrimaryClass(); @@ -39,13 +54,13 @@ protected override void ExecuteCore( var memberName = hasMemberName ? tokens[1].Content : null; var memberSpan = hasMemberName ? tokens[1].Source : null; - if (hasMemberName && !properties.Add(memberName!)) + // continue if the membername is in any existing inject statement or in a previous keyedinject statement + if (hasMemberName && (!properties.Add(memberName!) || existingMembers.Contains(memberName!))) { continue; } - var hasKeyName = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2].Content); - // No assert as is optional + Debug.Assert(hasKeyName || isMalformed); var keyName = hasKeyName ? tokens[2].Content : null; var keySpan = hasKeyName ? tokens[2].Source : null; From 0330bd2cd328174e7bb840013bd2c96dce7c9da2 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Tue, 10 Feb 2026 22:53:02 +1000 Subject: [PATCH 11/14] Runtime integration test --- .../CodeGenerationIntegrationTest.cs | 1 - .../ComponentKeyedInjectIntermediateNode.cs | 63 ++++++++++++++++--- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs index 7dd01a330c9..3c8af097149 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -365,7 +365,6 @@ public class MyApp AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode()); AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); AssertLinePragmas(compiled.CodeDocument); - AssertSourceMappingsMatchBaseline(compiled.CodeDocument); } [Fact] diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs index 6063c17b566..b32bd8046bf 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs @@ -16,7 +16,7 @@ internal class ComponentKeyedInjectIntermediateNode : ExtensionIntermediateNode { private ImmutableArray injectedPropertyModifiers() { return [ - $"[global::{ComponentsApi.InjectAttribute.FullTypeName}{(!string.IsNullOrEmpty(KeyName) ? "" : $"(Key = {KeyName})")}]", + $"[global::{ComponentsApi.InjectAttribute.FullTypeName}(Key = {KeyName})]", "private" // Encapsulation is the default ]; } @@ -79,18 +79,63 @@ public override void WriteNode(CodeTarget target, CodeRenderingContext context) } else { + var memberName = MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context); if (!context.Options.DesignTime || !IsMalformed) { - context.CodeWriter.WriteAutoPropertyDeclaration( - injectedPropertyModifiers(), - TypeName, - memberName, - TypeSpan, - MemberSpan, - context, - defaultValue: true); + // I was just writing out string interpolation here with no source mappings but that was messing with the + // integration tests. Not sure what is preferred. + context.CodeWriter.Write($"[global::{ComponentsApi.InjectAttribute.FullTypeName}("); + + context.CodeWriter.Write("Key = "); + + using (context.BuildEnhancedLinePragma(KeySource)) + { + context.CodeWriter.Write(KeyName); + } + + context.CodeWriter.Write(")]"); + + // + WriteToken(context.CodeWriter, TypeName, TypeSpan, context); + context.CodeWriter.Write(" "); + WriteToken(context.CodeWriter, memberName, MemberSpan, context); + + static void WriteToken(CodeWriter writer, string content, SourceSpan? span, CodeRenderingContext context) + { + if (span is not null && context?.Options.DesignTime == false) + { + using (context.BuildEnhancedLinePragma(span)) + { + writer.Write(content); + } + } + else + { + writer.Write(content); + } + } + + context.CodeWriter.Write(" { get;"); + + context.CodeWriter.WriteLine(" set; }"); + + if (context?.Options is { SuppressNullabilityEnforcement: false, DesignTime: false }) + { + context.CodeWriter.WriteLine(" = default!;"); + } + + + + //context.CodeWriter.WriteAutoPropertyDeclaration( + // injectedPropertyModifiers(), + // TypeName, + // memberName, + // TypeSpan, + // MemberSpan, + // context, + // defaultValue: true); } } } From ce40c4943fb9126e84ade3efbd863d4433c9a572 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Tue, 10 Feb 2026 23:52:52 +1000 Subject: [PATCH 12/14] Integration tests for malformation and deduplication --- TestApp/Controllers/HomeController.cs | 27 ++ TestApp/Models/ErrorViewModel.cs | 11 + TestApp/Program.cs | 37 +++ TestApp/Properties/launchSettings.json | 23 ++ TestApp/TestApp.csproj | 13 + TestApp/TestClass.cs | 35 +++ TestApp/Views/Home/Index.cshtml | 9 + TestApp/Views/Home/Privacy.cshtml | 6 + TestApp/Views/Shared/Error.cshtml | 25 ++ TestApp/Views/Shared/_Layout.cshtml | 50 +++ TestApp/Views/Shared/_Layout.cshtml.css | 48 +++ .../Shared/_ValidationScriptsPartial.cshtml | 2 + TestApp/Views/_ViewImports.cshtml | 3 + TestApp/Views/_ViewStart.cshtml | 3 + TestApp/appsettings.Development.json | 8 + TestApp/appsettings.json | 9 + TestApp/wwwroot/css/site.css | 31 ++ TestApp/wwwroot/favicon.ico | Bin 0 -> 5430 bytes TestApp/wwwroot/js/site.js | 4 + TestApp/wwwroot/lib/bootstrap/LICENSE | 22 ++ .../jquery-validation-unobtrusive/LICENSE.txt | 23 ++ .../wwwroot/lib/jquery-validation/LICENSE.md | 22 ++ TestApp/wwwroot/lib/jquery/LICENSE.txt | 21 ++ .../CodeGenerationIntegrationTest.cs | 76 ++++- .../InjectWithKeyAfter.cshtml | 5 - .../MalformedKeyedInject.cshtml | 2 + ...MalformedKeyedInject_DesignTime.codegen.cs | 86 ++++++ ...lformedKeyedInject_DesignTime.codegen.html | 2 + ...rmedKeyedInject_DesignTime.diagnostics.txt | 1 + .../MalformedKeyedInject_DesignTime.ir.txt | 48 +++ ...lformedKeyedInject_DesignTime.mappings.txt | 15 + .../MalformedKeyedInject_Runtime.codegen.cs | 79 +++++ ...lformedKeyedInject_Runtime.diagnostics.txt | 1 + .../MalformedKeyedInject_Runtime.ir.txt | 26 ++ .../MixKeyedInject.cshtml | 9 + .../MixKeyedInject_DesignTime.codegen.cs | 292 ++++++++++++++++++ .../MixKeyedInject_DesignTime.codegen.html | 9 + .../MixKeyedInject_DesignTime.ir.txt | 65 ++++ .../MixKeyedInject_DesignTime.mappings.txt | 110 +++++++ .../MixKeyedInject_Runtime.codegen.cs | 135 ++++++++ .../MixKeyedInject_Runtime.ir.txt | 24 ++ ...ViewComponentTagHelperOptionalParam.cshtml | 2 +- .../src/Language/Legacy/CSharpCodeParser.cs | 2 + .../files/LanguageSupport/IsExternalInit.cs | 1 - 44 files changed, 1414 insertions(+), 8 deletions(-) create mode 100644 TestApp/Controllers/HomeController.cs create mode 100644 TestApp/Models/ErrorViewModel.cs create mode 100644 TestApp/Program.cs create mode 100644 TestApp/Properties/launchSettings.json create mode 100644 TestApp/TestApp.csproj create mode 100644 TestApp/TestClass.cs create mode 100644 TestApp/Views/Home/Index.cshtml create mode 100644 TestApp/Views/Home/Privacy.cshtml create mode 100644 TestApp/Views/Shared/Error.cshtml create mode 100644 TestApp/Views/Shared/_Layout.cshtml create mode 100644 TestApp/Views/Shared/_Layout.cshtml.css create mode 100644 TestApp/Views/Shared/_ValidationScriptsPartial.cshtml create mode 100644 TestApp/Views/_ViewImports.cshtml create mode 100644 TestApp/Views/_ViewStart.cshtml create mode 100644 TestApp/appsettings.Development.json create mode 100644 TestApp/appsettings.json create mode 100644 TestApp/wwwroot/css/site.css create mode 100644 TestApp/wwwroot/favicon.ico create mode 100644 TestApp/wwwroot/js/site.js create mode 100644 TestApp/wwwroot/lib/bootstrap/LICENSE create mode 100644 TestApp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt create mode 100644 TestApp/wwwroot/lib/jquery-validation/LICENSE.md create mode 100644 TestApp/wwwroot/lib/jquery/LICENSE.txt delete mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKeyAfter.cshtml create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.codegen.cs create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.codegen.html create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.diagnostics.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.ir.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.mappings.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.codegen.cs create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.diagnostics.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.ir.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.codegen.cs create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.codegen.html create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.ir.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.mappings.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_Runtime.codegen.cs create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_Runtime.ir.txt diff --git a/TestApp/Controllers/HomeController.cs b/TestApp/Controllers/HomeController.cs new file mode 100644 index 00000000000..cf5f1e1fa01 --- /dev/null +++ b/TestApp/Controllers/HomeController.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using TestApp.Models; + +namespace TestApp.Controllers; + +public class HomeController : Controller +{ + public IActionResult Index() + { + return View(); + } + + public IActionResult Privacy() + { + return View(); + } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } +} diff --git a/TestApp/Models/ErrorViewModel.cs b/TestApp/Models/ErrorViewModel.cs new file mode 100644 index 00000000000..2cc414989d7 --- /dev/null +++ b/TestApp/Models/ErrorViewModel.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace TestApp.Models; + +public class ErrorViewModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +} diff --git a/TestApp/Program.cs b/TestApp/Program.cs new file mode 100644 index 00000000000..3dde32d4e8f --- /dev/null +++ b/TestApp/Program.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using TestApp; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddSingleton(); +builder.Services.AddKeyedSingleton("NumberOne"); +builder.Services.AddKeyedSingleton("NumberTwo"); +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Home/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseRouting(); + +app.UseAuthorization(); + +app.MapStaticAssets(); + +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}") + .WithStaticAssets(); + + +app.Run(); diff --git a/TestApp/Properties/launchSettings.json b/TestApp/Properties/launchSettings.json new file mode 100644 index 00000000000..7d0531d7ebd --- /dev/null +++ b/TestApp/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5222", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7129;http://localhost:5222", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/TestApp/TestApp.csproj b/TestApp/TestApp.csproj new file mode 100644 index 00000000000..3f5522f6d29 --- /dev/null +++ b/TestApp/TestApp.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/TestApp/TestClass.cs b/TestApp/TestClass.cs new file mode 100644 index 00000000000..8e5b8971aae --- /dev/null +++ b/TestApp/TestClass.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace TestApp; + + + public interface ITest + { + string DoSomething(); + } + public class TestClass : ITest + { + public string DoSomething() + { + return "TestClass"; + } + } + + public class TestNo2Class : ITest + { + public string DoSomething() + { + return "TestNo2Class"; + } + } + + public class NormalClass + { + public string DoABarrelRoll() + { + return "Did a barrell roll"; + } + } + + diff --git a/TestApp/Views/Home/Index.cshtml b/TestApp/Views/Home/Index.cshtml new file mode 100644 index 00000000000..92a01bb6546 --- /dev/null +++ b/TestApp/Views/Home/Index.cshtml @@ -0,0 +1,9 @@ +@{ + ViewData["Title"] = "Home Page"; +} +@inject NormalClass normalClass +@inject ITest Class1 "NumberOne" +
+

Welcome

+

Learn about building Web apps with ASP.NET Core.

+
diff --git a/TestApp/Views/Home/Privacy.cshtml b/TestApp/Views/Home/Privacy.cshtml new file mode 100644 index 00000000000..af4fb195a3c --- /dev/null +++ b/TestApp/Views/Home/Privacy.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Privacy Policy"; +} +

@ViewData["Title"]

+ +

Use this page to detail your site's privacy policy.

diff --git a/TestApp/Views/Shared/Error.cshtml b/TestApp/Views/Shared/Error.cshtml new file mode 100644 index 00000000000..a1e04783c67 --- /dev/null +++ b/TestApp/Views/Shared/Error.cshtml @@ -0,0 +1,25 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

diff --git a/TestApp/Views/Shared/_Layout.cshtml b/TestApp/Views/Shared/_Layout.cshtml new file mode 100644 index 00000000000..081b7f67170 --- /dev/null +++ b/TestApp/Views/Shared/_Layout.cshtml @@ -0,0 +1,50 @@ + + + + + + @ViewData["Title"] - TestApp + + + + + + +
+ +
+
+
+ @RenderBody() +
+
+ +
+
+ © 2026 - TestApp - Privacy +
+
+ + + + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/TestApp/Views/Shared/_Layout.cshtml.css b/TestApp/Views/Shared/_Layout.cshtml.css new file mode 100644 index 00000000000..c187c02e050 --- /dev/null +++ b/TestApp/Views/Shared/_Layout.cshtml.css @@ -0,0 +1,48 @@ +/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification +for details on configuring this project to bundle and minify static web assets. */ + +a.navbar-brand { + white-space: normal; + text-align: center; + word-break: break-all; +} + +a { + color: #0077cc; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.nav-pills .nav-link.active, .nav-pills .show > .nav-link { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.border-top { + border-top: 1px solid #e5e5e5; +} +.border-bottom { + border-bottom: 1px solid #e5e5e5; +} + +.box-shadow { + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); +} + +button.accept-policy { + font-size: 1rem; + line-height: inherit; +} + +.footer { + position: absolute; + bottom: 0; + width: 100%; + white-space: nowrap; + line-height: 60px; +} diff --git a/TestApp/Views/Shared/_ValidationScriptsPartial.cshtml b/TestApp/Views/Shared/_ValidationScriptsPartial.cshtml new file mode 100644 index 00000000000..5d1f6857627 --- /dev/null +++ b/TestApp/Views/Shared/_ValidationScriptsPartial.cshtml @@ -0,0 +1,2 @@ + + diff --git a/TestApp/Views/_ViewImports.cshtml b/TestApp/Views/_ViewImports.cshtml new file mode 100644 index 00000000000..07af67cf44c --- /dev/null +++ b/TestApp/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using TestApp +@using TestApp.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/TestApp/Views/_ViewStart.cshtml b/TestApp/Views/_ViewStart.cshtml new file mode 100644 index 00000000000..a5f10045db9 --- /dev/null +++ b/TestApp/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/TestApp/appsettings.Development.json b/TestApp/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/TestApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/TestApp/appsettings.json b/TestApp/appsettings.json new file mode 100644 index 00000000000..10f68b8c8b4 --- /dev/null +++ b/TestApp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/TestApp/wwwroot/css/site.css b/TestApp/wwwroot/css/site.css new file mode 100644 index 00000000000..819f61241bb --- /dev/null +++ b/TestApp/wwwroot/css/site.css @@ -0,0 +1,31 @@ +html { + font-size: 14px; +} + +@media (min-width: 768px) { + html { + font-size: 16px; + } +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +html { + position: relative; + min-height: 100%; +} + +body { + margin-bottom: 60px; +} + +.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { + color: var(--bs-secondary-color); + text-align: end; +} + +.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { + text-align: start; +} \ No newline at end of file diff --git a/TestApp/wwwroot/favicon.ico b/TestApp/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..63e859b476eff5055e0e557aaa151ca8223fbeef GIT binary patch literal 5430 zcmc&&Yj2xp8Fqnv;>&(QB_ve7>^E#o2mu=cO~A%R>DU-_hfbSRv1t;m7zJ_AMrntN zy0+^f&8be>q&YYzH%(88lQ?#KwiCzaCO*ZEo%j&v;<}&Lj_stKTKK>#U3nin@AF>w zb3ONSAFR{u(S1d?cdw53y}Gt1b-Hirbh;;bm(Rcbnoc*%@jiaXM|4jU^1WO~`TYZ~ zC-~jh9~b-f?fX`DmwvcguQzn*uV}c^Vd&~?H|RUs4Epv~gTAfR(B0lT&?RWQOtduM z^1vUD9{HQsW!{a9|0crA34m7Z6lpG^}f6f?={zD+ zXAzk^i^aKN_}s2$eX81wjSMONE#WVdzf|MT)Ap*}Vsn!XbvsI#6o&ij{87^d%$|A{ z=F{KB%)g%@z76yBzbb7seW**Ju8r4e*Z3PWNX3_tTDgzZatz7)Q6ytwB%@&@A|XT; zecM`Snxx5po$C)%yCP!KEtos~eOS)@2=kX-RIm)4glMCoagTEFxrBeSX%Euz734Fk z%7)x(k~T!@Hbg_37NSQL!vlTBXoURSzt~I**Zw`&F24fH*&kx=%nvZv|49SC*daD( zIw<~%#=lk8{2-l(BcIjy^Q$Q&m#KlWL9?UG{b8@qhlD z;umc+6p%|NsAT~0@DgV4-NKgQuWPWrmPIK&&XhV&n%`{l zOl^bbWYjQNuVXTXESO)@|iUKVmErPUDfz2Wh`4dF@OFiaCW|d`3paV^@|r^8T_ZxM)Z+$p5qx# z#K=z@%;aBPO=C4JNNGqVv6@UGolIz;KZsAro``Rz8X%vq_gpi^qEV&evgHb_=Y9-l z`)imdx0UC>GWZYj)3+3aKh?zVb}=@%oNzg7a8%kfVl)SV-Amp1Okw&+hEZ3|v(k8vRjXW9?ih`&FFM zV$~{j3IzhtcXk?Mu_!12;=+I7XK-IR2>Yd%VB^?oI9c^E&Chb&&je$NV0P-R;ujkP z;cbLCCPEF6|22NDj=S`F^2e~XwT1ZnRX8ra0#DaFa9-X|8(xNW_+JhD75WnSd7cxo z2>I_J5{c|WPfrgl7E2R)^c}F7ry()Z>$Jhk9CzZxiPKL#_0%`&{MX>P_%b~Dx0D^S z7xP1(DQ!d_Icpk!RN3I1w@~|O1ru#CO==h#9M~S4Chx*@?=EKUPGBv$tmU+7Zs_al z`!jR?6T&Z7(%uVq>#yLu`abWk!FBlnY{RFNHlj~6zh*;@u}+}viRKsD`IIxN#R-X3 z@vxu#EA_m}I503U(8Qmx^}u;)KfGP`O9E1H1Q|xeeksX8jC%@!{YT1)!lWgO=+Y3*jr=iSxvOW1}^HSy=y){tOMQJ@an>sOl4FYniE z;GOxd7AqxZNbYFNqobpv&HVO$c-w!Y*6r;$2oJ~h(a#(Bp<-)dg*mNigX~9rPqcHv z^;c*|Md?tD)$y?6FO$DWl$jUGV`F1G_^E&E>sY*YnA~ruv3=z9F8&&~Xpm<<75?N3 z>x~`I&M9q)O1=zWZHN9hZWx>RQ}zLP+iL57Q)%&_^$Sme^^G7;e-P~CR?kqU#Io#( z(nH1Wn*Ig)|M>WLGrxoU?FZrS`4GO&w;+39A3f8w{{Q7eg|$+dIlNFPAe+tN=FOYU z{A&Fg|H73+w1IK(W=j*L>JQgz$g0 z7JpKXLHIh}#$wm|N`s}o-@|L_`>*(gTQ~)wr3Eap7g%PVNisKw82im;Gdv#85x#s+ zoqqtnwu4ycd>cOQgRh-=aEJbnvVK`}ja%+FZx}&ehtX)n(9nVfe4{mn0bgijUbNr7Tf5X^$*{qh2%`?--%+sbSrjE^;1e3>% zqa%jdY16{Y)a1hSy*mr0JGU05Z%=qlx5vGvTjSpTt6k%nR06q}1DU`SQh_ZAeJ}A@`hL~xvv05U?0%=spP`R>dk?cOWM9^KNb7B?xjex>OZo%JMQQ1Q zB|q@}8RiP@DWn-(fB;phPaIOP2Yp)XN3-Fsn)S3w($4&+p8f5W_f%gac}QvmkHfCj$2=!t`boCvQ zCW;&Dto=f8v##}dy^wg3VNaBy&kCe3N;1|@n@pUaMPT?(aJ9b*(gJ28$}(2qFt$H~u5z94xcIQkcOI++)*exzbrk?WOOOf*|%k5#KV zL=&ky3)Eirv$wbRJ2F2s_ILQY--D~~7>^f}W|Aw^e7inXr#WLI{@h`0|jHud2Y~cI~Yn{r_kU^Vo{1gja + { + public string Html { get; set; } + } + + public class MyApp + { + public string MyProperty { get; set; } + } + """); + + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToAssembly(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertLinePragmas(compiled.CodeDocument); + } + + [Fact] + public void MalformedKeyedInject_Runtime() + { + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: false); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode()); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertLinePragmas(compiled.CodeDocument); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ1016", Assert.Single(diagnotics).Id); + } + [Fact] public void InjectWithSemicolon_Runtime() { @@ -1326,7 +1380,27 @@ public class MyApp } [Fact] - public void InjectWithKeyAfter_DesignTime() + public void MalformedKeyedInject_DesignTime() + { + // Arrange + var projectItem = CreateProjectItemFromFile(); + + // Act + var compiled = CompileToCSharp(projectItem, designTime: true); + + // Assert + AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode()); + AssertHtmlDocumentMatchesBaseline(RazorHtmlWriter.GetHtmlDocument(compiled.CodeDocument)); + AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); + AssertLinePragmas(compiled.CodeDocument); + AssertSourceMappingsMatchBaseline(compiled.CodeDocument); + + var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; + Assert.Equal("RZ1016", Assert.Single(diagnotics).Id); + } + + [Fact] + public void MixKeyedInject_DesignTime() { // Arrange AddCSharpSyntaxTree(""" diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKeyAfter.cshtml b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKeyAfter.cshtml deleted file mode 100644 index 3b9d9f02d3f..00000000000 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InjectWithKeyAfter.cshtml +++ /dev/null @@ -1,5 +0,0 @@ -@model MyModel -@keyedinject MyApp MyPropertyName "KeyOne" -@keyedinject MyService ServiceOne "KeyOne" -@keyedinject MyApp MyPropertyName2 "SomeKey" ; -@inject MyService ServiceOne diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml new file mode 100644 index 00000000000..09ae006bb44 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml @@ -0,0 +1,2 @@ +@model MyModel +@keyedinject MyApp MyPropertyName "KeyOne diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.codegen.cs new file mode 100644 index 00000000000..c6df967a9f7 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.codegen.cs @@ -0,0 +1,86 @@ +// +#pragma warning disable 1591 +namespace AspNetCoreGeneratedDocument +{ + #line default + using TModel = global::System.Object; + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Mvc; + using global::Microsoft.AspNetCore.Mvc.Rendering; + using global::Microsoft.AspNetCore.Mvc.ViewFeatures; + #line default + #line hidden + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml")] + [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] + #nullable restore + internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedKeyedInject : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage + #nullable disable + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((global::System.Action)(() => { +#nullable restore +#line 1 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml" +MyModel __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml" +MyApp __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml" +global::System.Object MyPropertyName = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; + #nullable disable + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.codegen.html b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.codegen.html new file mode 100644 index 00000000000..fbd71d01ad2 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.codegen.html @@ -0,0 +1,2 @@ +/*~~*/ /*~~~*/ +/*~~~~~~~~*/ /*~*/ /*~~~~~~~~~~*/ "KeyOne diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.diagnostics.txt new file mode 100644 index 00000000000..77970f18d15 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.diagnostics.txt @@ -0,0 +1 @@ +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml(2,35): Error RZ1016: The 'keyedinject' directive expects a string surrounded by double quotes. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.ir.txt new file mode 100644 index 00000000000..678b7199aee --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.ir.txt @@ -0,0 +1,48 @@ +Document - + NamespaceDeclaration - - AspNetCoreGeneratedDocument + UsingDirective - - TModel = global::System.Object + UsingDirective - (1:0,1 [20] ) - global::System + UsingDirective - (24:1,1 [40] ) - global::System.Collections.Generic + UsingDirective - (67:2,1 [25] ) - global::System.Linq + UsingDirective - (95:3,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (134:4,1 [38] ) - global::Microsoft.AspNetCore.Mvc + UsingDirective - (175:5,1 [48] ) - global::Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (226:6,1 [51] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures + RazorCompiledItemMetadataAttribute - + CreateNewOnMetadataUpdateAttribute - + ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedKeyedInject - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + DesignTimeDirective - + DirectiveToken - (287:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper + DirectiveToken - (350:7,71 [4] ) - Html + DirectiveToken - (364:8,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper + DirectiveToken - (419:8,63 [4] ) - Json + DirectiveToken - (433:9,8 [53] ) - global::Microsoft.AspNetCore.Mvc.IViewComponentHelper + DirectiveToken - (487:9,62 [9] ) - Component + DirectiveToken - (506:10,8 [43] ) - global::Microsoft.AspNetCore.Mvc.IUrlHelper + DirectiveToken - (550:10,52 [3] ) - Url + DirectiveToken - (563:11,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider + DirectiveToken - (634:11,79 [23] ) - ModelExpressionProvider + DirectiveToken - (673:12,14 [104] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (793:13,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (904:14,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (7:0,7 [7] MalformedKeyedInject.cshtml) - MyModel + DirectiveToken - (29:1,13 [5] MalformedKeyedInject.cshtml) - MyApp + DirectiveToken - (35:1,19 [14] MalformedKeyedInject.cshtml) - MyPropertyName + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + MalformedDirective - (16:1,0 [34] MalformedKeyedInject.cshtml) - keyedinject + DirectiveToken - (29:1,13 [5] MalformedKeyedInject.cshtml) - MyApp + DirectiveToken - (35:1,19 [14] MalformedKeyedInject.cshtml) - MyPropertyName + HtmlContent - (50:1,34 [9] MalformedKeyedInject.cshtml) + LazyIntermediateToken - (50:1,34 [9] MalformedKeyedInject.cshtml) - Html - "KeyOne\n + Inject - + Inject - + Inject - + Inject - + Inject - + KeyedInject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.mappings.txt new file mode 100644 index 00000000000..288e4e4a43f --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_DesignTime.mappings.txt @@ -0,0 +1,15 @@ +Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml) +|MyModel| +Generated Location: (1239:26,0 [7] ) +|MyModel| + +Source Location: (29:1,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml) +|MyApp| +Generated Location: (1506:36,0 [5] ) +|MyApp| + +Source Location: (35:1,19 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml) +|MyPropertyName| +Generated Location: (1793:46,22 [14] ) +|MyPropertyName| + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.codegen.cs new file mode 100644 index 00000000000..dc5d96aebee --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.codegen.cs @@ -0,0 +1,79 @@ +#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "ca787f5e43a2e7d60ca0544b240c00db625c45b9221d3dcf328d1a0874362a24" +// +#pragma warning disable 1591 +[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedKeyedInject), @"mvc.1.0.view", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml")] +namespace AspNetCoreGeneratedDocument +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Mvc; + using global::Microsoft.AspNetCore.Mvc.Rendering; + using global::Microsoft.AspNetCore.Mvc.ViewFeatures; + #line default + #line hidden + [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"Sha256", @"ca787f5e43a2e7d60ca0544b240c00db625c45b9221d3dcf328d1a0874362a24", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml")] + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml")] + [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] + #nullable restore + internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedKeyedInject : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage< +#nullable restore +#line (1,8)-(1,15) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml" +MyModel + +#line default +#line hidden +#nullable disable + > + #nullable disable + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + WriteLiteral("\"KeyOne\r\n"); + } + #pragma warning restore 1998 + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; + #nullable disable + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public +#nullable restore +#line (2,14)-(2,19) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml" +MyApp + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (2,20)-(2,34) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml" +MyPropertyName + +#line default +#line hidden +#nullable disable + { get; private set; } + = default!; + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.diagnostics.txt new file mode 100644 index 00000000000..77970f18d15 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.diagnostics.txt @@ -0,0 +1 @@ +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject.cshtml(2,35): Error RZ1016: The 'keyedinject' directive expects a string surrounded by double quotes. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.ir.txt new file mode 100644 index 00000000000..3a427254cbc --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedKeyedInject_Runtime.ir.txt @@ -0,0 +1,26 @@ +Document - + RazorCompiledItemAttribute - + NamespaceDeclaration - - AspNetCoreGeneratedDocument + UsingDirective - (1:0,1 [20] ) - global::System + UsingDirective - (24:1,1 [40] ) - global::System.Collections.Generic + UsingDirective - (67:2,1 [25] ) - global::System.Linq + UsingDirective - (95:3,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (134:4,1 [38] ) - global::Microsoft.AspNetCore.Mvc + UsingDirective - (175:5,1 [48] ) - global::Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (226:6,1 [51] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures + RazorSourceChecksumAttribute - + RazorCompiledItemMetadataAttribute - + CreateNewOnMetadataUpdateAttribute - + ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedKeyedInject - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + MalformedDirective - (16:1,0 [34] MalformedKeyedInject.cshtml) - keyedinject + DirectiveToken - (29:1,13 [5] MalformedKeyedInject.cshtml) - MyApp + DirectiveToken - (35:1,19 [14] MalformedKeyedInject.cshtml) - MyPropertyName + HtmlContent - (50:1,34 [9] MalformedKeyedInject.cshtml) + LazyIntermediateToken - (50:1,34 [9] MalformedKeyedInject.cshtml) - Html - "KeyOne\n + Inject - + Inject - + Inject - + Inject - + Inject - + KeyedInject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml new file mode 100644 index 00000000000..1217fba3796 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml @@ -0,0 +1,9 @@ +@model MyModel +@keyedinject MyApp MyPropertyName "KeyOne" +@inject MyApp MyPropertyName +@inject MyService ServiceOne +@keyedinject MyService ServiceOne "KeyOne" +@keyedinject MyService ServiceOne "KeyTwo" +@keyedinject MyApp MyPropertyName2 "SomeKey" +@inject MyApp MyPropertyName2 +@keyedinject MyApp MyPropertyName3 "KeyThree" diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.codegen.cs new file mode 100644 index 00000000000..1b118b32191 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.codegen.cs @@ -0,0 +1,292 @@ +// +#pragma warning disable 1591 +namespace AspNetCoreGeneratedDocument +{ + #line default + using TModel = global::System.Object; + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Mvc; + using global::Microsoft.AspNetCore.Mvc.Rendering; + using global::Microsoft.AspNetCore.Mvc.ViewFeatures; + #line default + #line hidden + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml")] + [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] + #nullable restore + internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MixKeyedInject : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage + #nullable disable + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((global::System.Action)(() => { +#nullable restore +#line 1 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyModel __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyApp __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object MyPropertyName = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object __typeHelper = "KeyOne"; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyApp __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 3 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object MyPropertyName = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyService __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object ServiceOne = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyService __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object ServiceOne = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 5 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object __typeHelper = "KeyOne"; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 6 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyService __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 6 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object ServiceOne = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 6 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object __typeHelper = "KeyTwo"; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 7 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyApp __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 7 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object MyPropertyName2 = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 7 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object __typeHelper = "SomeKey"; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 8 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyApp __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 8 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object MyPropertyName2 = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 9 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyApp __typeHelper = default!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 9 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object MyPropertyName3 = null!; + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore +#line 9 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +global::System.Object __typeHelper = "KeyThree"; + +#line default +#line hidden +#nullable disable + } + ))(); + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public MyApp MyPropertyName2 { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public MyService ServiceOne { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public MyApp MyPropertyName { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "KeyThree")] + public MyApp MyPropertyName3 { get; private set; } = default!; + #nullable disable + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.codegen.html b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.codegen.html new file mode 100644 index 00000000000..30594788d40 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.codegen.html @@ -0,0 +1,9 @@ +/*~~*/ /*~~~*/ +/*~~~~~~~~*/ /*~*/ /*~~~~~~~~~~*/ /*~~~~*/ +/*~~~*/ /*~*/ /*~~~~~~~~~~*/ +/*~~~*/ /*~~~~~~~~~~~~~*/ /*~~~~~~*/ +/*~~~~~~~~*/ /*~~~~~~~~~~~~~*/ /*~~~~~~*/ /*~~~~*/ +/*~~~~~~~~*/ /*~~~~~~~~~~~~~*/ /*~~~~~~*/ /*~~~~*/ +/*~~~~~~~~*/ /*~*/ /*~~~~~~~~~~~*/ /*~~~~~*/ +/*~~~*/ /*~*/ /*~~~~~~~~~~~*/ +/*~~~~~~~~*/ /*~*/ /*~~~~~~~~~~~*/ /*~~~~~~*/ diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.ir.txt new file mode 100644 index 00000000000..0f8546b29a9 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.ir.txt @@ -0,0 +1,65 @@ +Document - + NamespaceDeclaration - - AspNetCoreGeneratedDocument + UsingDirective - - TModel = global::System.Object + UsingDirective - (1:0,1 [20] ) - global::System + UsingDirective - (24:1,1 [40] ) - global::System.Collections.Generic + UsingDirective - (67:2,1 [25] ) - global::System.Linq + UsingDirective - (95:3,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (134:4,1 [38] ) - global::Microsoft.AspNetCore.Mvc + UsingDirective - (175:5,1 [48] ) - global::Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (226:6,1 [51] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures + RazorCompiledItemMetadataAttribute - + CreateNewOnMetadataUpdateAttribute - + ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MixKeyedInject - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + DesignTimeDirective - + DirectiveToken - (287:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper + DirectiveToken - (350:7,71 [4] ) - Html + DirectiveToken - (364:8,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper + DirectiveToken - (419:8,63 [4] ) - Json + DirectiveToken - (433:9,8 [53] ) - global::Microsoft.AspNetCore.Mvc.IViewComponentHelper + DirectiveToken - (487:9,62 [9] ) - Component + DirectiveToken - (506:10,8 [43] ) - global::Microsoft.AspNetCore.Mvc.IUrlHelper + DirectiveToken - (550:10,52 [3] ) - Url + DirectiveToken - (563:11,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider + DirectiveToken - (634:11,79 [23] ) - ModelExpressionProvider + DirectiveToken - (673:12,14 [104] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (793:13,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (904:14,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (7:0,7 [7] MixKeyedInject.cshtml) - MyModel + DirectiveToken - (29:1,13 [5] MixKeyedInject.cshtml) - MyApp + DirectiveToken - (35:1,19 [14] MixKeyedInject.cshtml) - MyPropertyName + DirectiveToken - (50:1,34 [8] MixKeyedInject.cshtml) - "KeyOne" + DirectiveToken - (68:2,8 [5] MixKeyedInject.cshtml) - MyApp + DirectiveToken - (74:2,14 [14] MixKeyedInject.cshtml) - MyPropertyName + DirectiveToken - (98:3,8 [17] MixKeyedInject.cshtml) - MyService + DirectiveToken - (116:3,26 [10] MixKeyedInject.cshtml) - ServiceOne + DirectiveToken - (141:4,13 [17] MixKeyedInject.cshtml) - MyService + DirectiveToken - (159:4,31 [10] MixKeyedInject.cshtml) - ServiceOne + DirectiveToken - (170:4,42 [8] MixKeyedInject.cshtml) - "KeyOne" + DirectiveToken - (193:5,13 [17] MixKeyedInject.cshtml) - MyService + DirectiveToken - (211:5,31 [10] MixKeyedInject.cshtml) - ServiceOne + DirectiveToken - (222:5,42 [8] MixKeyedInject.cshtml) - "KeyTwo" + DirectiveToken - (245:6,13 [5] MixKeyedInject.cshtml) - MyApp + DirectiveToken - (251:6,19 [15] MixKeyedInject.cshtml) - MyPropertyName2 + DirectiveToken - (267:6,35 [9] MixKeyedInject.cshtml) - "SomeKey" + DirectiveToken - (286:7,8 [5] MixKeyedInject.cshtml) - MyApp + DirectiveToken - (292:7,14 [15] MixKeyedInject.cshtml) - MyPropertyName2 + DirectiveToken - (322:8,13 [5] MixKeyedInject.cshtml) - MyApp + DirectiveToken - (328:8,19 [15] MixKeyedInject.cshtml) - MyPropertyName3 + DirectiveToken - (344:8,35 [10] MixKeyedInject.cshtml) - "KeyThree" + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + Inject - + Inject - + Inject - + Inject - + Inject - + Inject - + Inject - + Inject - + KeyedInject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.mappings.txt new file mode 100644 index 00000000000..99b8b616416 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_DesignTime.mappings.txt @@ -0,0 +1,110 @@ +Source Location: (7:0,7 [7] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyModel| +Generated Location: (1221:26,0 [7] ) +|MyModel| + +Source Location: (29:1,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyApp| +Generated Location: (1482:36,0 [5] ) +|MyApp| + +Source Location: (35:1,19 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyPropertyName| +Generated Location: (1763:46,22 [14] ) +|MyPropertyName| + +Source Location: (50:1,34 [8] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|"KeyOne"| +Generated Location: (2052:56,37 [8] ) +|"KeyOne"| + +Source Location: (68:2,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyApp| +Generated Location: (2290:66,0 [5] ) +|MyApp| + +Source Location: (74:2,14 [14] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyPropertyName| +Generated Location: (2571:76,22 [14] ) +|MyPropertyName| + +Source Location: (98:3,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyService| +Generated Location: (2823:86,0 [17] ) +|MyService| + +Source Location: (116:3,26 [10] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|ServiceOne| +Generated Location: (3116:96,22 [10] ) +|ServiceOne| + +Source Location: (141:4,13 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyService| +Generated Location: (3364:106,0 [17] ) +|MyService| + +Source Location: (159:4,31 [10] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|ServiceOne| +Generated Location: (3657:116,22 [10] ) +|ServiceOne| + +Source Location: (170:4,42 [8] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|"KeyOne"| +Generated Location: (3942:126,37 [8] ) +|"KeyOne"| + +Source Location: (193:5,13 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyService| +Generated Location: (4180:136,0 [17] ) +|MyService| + +Source Location: (211:5,31 [10] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|ServiceOne| +Generated Location: (4473:146,22 [10] ) +|ServiceOne| + +Source Location: (222:5,42 [8] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|"KeyTwo"| +Generated Location: (4758:156,37 [8] ) +|"KeyTwo"| + +Source Location: (245:6,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyApp| +Generated Location: (4996:166,0 [5] ) +|MyApp| + +Source Location: (251:6,19 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyPropertyName2| +Generated Location: (5277:176,22 [15] ) +|MyPropertyName2| + +Source Location: (267:6,35 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|"SomeKey"| +Generated Location: (5567:186,37 [9] ) +|"SomeKey"| + +Source Location: (286:7,8 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyApp| +Generated Location: (5806:196,0 [5] ) +|MyApp| + +Source Location: (292:7,14 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyPropertyName2| +Generated Location: (6087:206,22 [15] ) +|MyPropertyName2| + +Source Location: (322:8,13 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyApp| +Generated Location: (6340:216,0 [5] ) +|MyApp| + +Source Location: (328:8,19 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|MyPropertyName3| +Generated Location: (6621:226,22 [15] ) +|MyPropertyName3| + +Source Location: (344:8,35 [10] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml) +|"KeyThree"| +Generated Location: (6911:236,37 [10] ) +|"KeyThree"| + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_Runtime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_Runtime.codegen.cs new file mode 100644 index 00000000000..874c0db8455 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_Runtime.codegen.cs @@ -0,0 +1,135 @@ +#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "0c66f7428f024653c40cbe464ecbbc61c0469cca0652db4f6439f5889a52d9e1" +// +#pragma warning disable 1591 +[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MixKeyedInject), @"mvc.1.0.view", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml")] +namespace AspNetCoreGeneratedDocument +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Mvc; + using global::Microsoft.AspNetCore.Mvc.Rendering; + using global::Microsoft.AspNetCore.Mvc.ViewFeatures; + #line default + #line hidden + [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"Sha256", @"0c66f7428f024653c40cbe464ecbbc61c0469cca0652db4f6439f5889a52d9e1", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml")] + [global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml")] + [global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute] + #nullable restore + internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MixKeyedInject : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage< +#nullable restore +#line (1,8)-(1,15) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyModel + +#line default +#line hidden +#nullable disable + > + #nullable disable + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public +#nullable restore +#line (8,9)-(8,14) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyApp + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (8,15)-(8,30) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyPropertyName2 + +#line default +#line hidden +#nullable disable + { get; private set; } + = default!; + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public +#nullable restore +#line (4,9)-(4,18) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyService + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (4,27)-(4,37) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +ServiceOne + +#line default +#line hidden +#nullable disable + { get; private set; } + = default!; + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public +#nullable restore +#line (3,9)-(3,14) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyApp + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (3,15)-(3,29) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyPropertyName + +#line default +#line hidden +#nullable disable + { get; private set; } + = default!; + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!; + #nullable disable + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } = default!; + #nullable disable + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "KeyThree")] + public +#nullable restore +#line (9,14)-(9,19) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyApp + +#line default +#line hidden +#nullable disable + +#nullable restore +#line (9,20)-(9,35) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject.cshtml" +MyPropertyName3 + +#line default +#line hidden +#nullable disable + { get; private set; } + = default!; + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_Runtime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_Runtime.ir.txt new file mode 100644 index 00000000000..d30e2d2e469 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MixKeyedInject_Runtime.ir.txt @@ -0,0 +1,24 @@ +Document - + RazorCompiledItemAttribute - + NamespaceDeclaration - - AspNetCoreGeneratedDocument + UsingDirective - (1:0,1 [20] ) - global::System + UsingDirective - (24:1,1 [40] ) - global::System.Collections.Generic + UsingDirective - (67:2,1 [25] ) - global::System.Linq + UsingDirective - (95:3,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (134:4,1 [38] ) - global::Microsoft.AspNetCore.Mvc + UsingDirective - (175:5,1 [48] ) - global::Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (226:6,1 [51] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures + RazorSourceChecksumAttribute - + RazorCompiledItemMetadataAttribute - + CreateNewOnMetadataUpdateAttribute - + ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MixKeyedInject - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + Inject - + Inject - + Inject - + Inject - + Inject - + Inject - + Inject - + Inject - + KeyedInject - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewComponentTagHelperOptionalParam.cshtml b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewComponentTagHelperOptionalParam.cshtml index eaef98e676d..7aaa935b851 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewComponentTagHelperOptionalParam.cshtml +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewComponentTagHelperOptionalParam.cshtml @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/CSharpCodeParser.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/CSharpCodeParser.cs index 9e9b47b7d43..d3ac5dd7461 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/CSharpCodeParser.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/CSharpCodeParser.cs @@ -1677,6 +1677,8 @@ private void ParseExtensibleDirective(in SyntaxListBuilder buil break; case DirectiveTokenKind.String: + // Either make this a move on if the currenttoken is a semicolon and it is optional or change order. + // Changing order probably simpler if (At(SyntaxKind.StringLiteral) && !CurrentToken.ContainsDiagnostics) { AcceptAndMoveNext(); diff --git a/src/Shared/files/LanguageSupport/IsExternalInit.cs b/src/Shared/files/LanguageSupport/IsExternalInit.cs index e7d2d5e6590..337a5abaef7 100644 --- a/src/Shared/files/LanguageSupport/IsExternalInit.cs +++ b/src/Shared/files/LanguageSupport/IsExternalInit.cs @@ -6,7 +6,6 @@ using System.ComponentModel; namespace System.Runtime.CompilerServices; - /// /// Reserved to be used by the compiler for tracking metadata. /// This class should not be used by developers in source code. From 110b957ba6778c625bf7373118e7f15f21909e81 Mon Sep 17 00:00:00 2001 From: wlsquid Date: Wed, 11 Feb 2026 01:14:48 +1000 Subject: [PATCH 13/14] fixed up unit tests and removed random files --- TestApp/Controllers/HomeController.cs | 27 ---- TestApp/Models/ErrorViewModel.cs | 11 -- TestApp/Program.cs | 37 ----- TestApp/Properties/launchSettings.json | 23 --- TestApp/TestApp.csproj | 13 -- TestApp/TestClass.cs | 35 ---- TestApp/Views/Home/Index.cshtml | 9 -- TestApp/Views/Home/Privacy.cshtml | 6 - TestApp/Views/Shared/Error.cshtml | 25 --- TestApp/Views/Shared/_Layout.cshtml | 50 ------ TestApp/Views/Shared/_Layout.cshtml.css | 48 ------ .../Shared/_ValidationScriptsPartial.cshtml | 2 - TestApp/Views/_ViewImports.cshtml | 3 - TestApp/Views/_ViewStart.cshtml | 3 - TestApp/appsettings.Development.json | 8 - TestApp/appsettings.json | 9 -- TestApp/wwwroot/css/site.css | 31 ---- TestApp/wwwroot/favicon.ico | Bin 5430 -> 0 bytes TestApp/wwwroot/js/site.js | 4 - TestApp/wwwroot/lib/bootstrap/LICENSE | 22 --- .../jquery-validation-unobtrusive/LICENSE.txt | 23 --- .../wwwroot/lib/jquery-validation/LICENSE.md | 22 --- TestApp/wwwroot/lib/jquery/LICENSE.txt | 21 --- .../test/InjectDirectiveTest.cs | 26 --- .../test/InjectTargetExtensionTest.cs | 28 ---- .../test/KeyedInjectDirectiveTest.cs | 152 ++++++++++++++++++ .../test/KeyedInjectTargetExtensionTest.cs | 45 ++++++ .../test/RazorProjectEngineTest.cs | 1 + 28 files changed, 198 insertions(+), 486 deletions(-) delete mode 100644 TestApp/Controllers/HomeController.cs delete mode 100644 TestApp/Models/ErrorViewModel.cs delete mode 100644 TestApp/Program.cs delete mode 100644 TestApp/Properties/launchSettings.json delete mode 100644 TestApp/TestApp.csproj delete mode 100644 TestApp/TestClass.cs delete mode 100644 TestApp/Views/Home/Index.cshtml delete mode 100644 TestApp/Views/Home/Privacy.cshtml delete mode 100644 TestApp/Views/Shared/Error.cshtml delete mode 100644 TestApp/Views/Shared/_Layout.cshtml delete mode 100644 TestApp/Views/Shared/_Layout.cshtml.css delete mode 100644 TestApp/Views/Shared/_ValidationScriptsPartial.cshtml delete mode 100644 TestApp/Views/_ViewImports.cshtml delete mode 100644 TestApp/Views/_ViewStart.cshtml delete mode 100644 TestApp/appsettings.Development.json delete mode 100644 TestApp/appsettings.json delete mode 100644 TestApp/wwwroot/css/site.css delete mode 100644 TestApp/wwwroot/favicon.ico delete mode 100644 TestApp/wwwroot/js/site.js delete mode 100644 TestApp/wwwroot/lib/bootstrap/LICENSE delete mode 100644 TestApp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt delete mode 100644 TestApp/wwwroot/lib/jquery-validation/LICENSE.md delete mode 100644 TestApp/wwwroot/lib/jquery/LICENSE.txt create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/KeyedInjectDirectiveTest.cs create mode 100644 src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/KeyedInjectTargetExtensionTest.cs diff --git a/TestApp/Controllers/HomeController.cs b/TestApp/Controllers/HomeController.cs deleted file mode 100644 index cf5f1e1fa01..00000000000 --- a/TestApp/Controllers/HomeController.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using Microsoft.AspNetCore.Mvc; -using TestApp.Models; - -namespace TestApp.Controllers; - -public class HomeController : Controller -{ - public IActionResult Index() - { - return View(); - } - - public IActionResult Privacy() - { - return View(); - } - - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); - } -} diff --git a/TestApp/Models/ErrorViewModel.cs b/TestApp/Models/ErrorViewModel.cs deleted file mode 100644 index 2cc414989d7..00000000000 --- a/TestApp/Models/ErrorViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace TestApp.Models; - -public class ErrorViewModel -{ - public string? RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); -} diff --git a/TestApp/Program.cs b/TestApp/Program.cs deleted file mode 100644 index 3dde32d4e8f..00000000000 --- a/TestApp/Program.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using TestApp; - -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. -builder.Services.AddSingleton(); -builder.Services.AddKeyedSingleton("NumberOne"); -builder.Services.AddKeyedSingleton("NumberTwo"); -builder.Services.AddControllersWithViews(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); -} - -app.UseHttpsRedirection(); -app.UseRouting(); - -app.UseAuthorization(); - -app.MapStaticAssets(); - -app.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}") - .WithStaticAssets(); - - -app.Run(); diff --git a/TestApp/Properties/launchSettings.json b/TestApp/Properties/launchSettings.json deleted file mode 100644 index 7d0531d7ebd..00000000000 --- a/TestApp/Properties/launchSettings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5222", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7129;http://localhost:5222", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/TestApp/TestApp.csproj b/TestApp/TestApp.csproj deleted file mode 100644 index 3f5522f6d29..00000000000 --- a/TestApp/TestApp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - net10.0 - enable - enable - - - - - - - diff --git a/TestApp/TestClass.cs b/TestApp/TestClass.cs deleted file mode 100644 index 8e5b8971aae..00000000000 --- a/TestApp/TestClass.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace TestApp; - - - public interface ITest - { - string DoSomething(); - } - public class TestClass : ITest - { - public string DoSomething() - { - return "TestClass"; - } - } - - public class TestNo2Class : ITest - { - public string DoSomething() - { - return "TestNo2Class"; - } - } - - public class NormalClass - { - public string DoABarrelRoll() - { - return "Did a barrell roll"; - } - } - - diff --git a/TestApp/Views/Home/Index.cshtml b/TestApp/Views/Home/Index.cshtml deleted file mode 100644 index 92a01bb6546..00000000000 --- a/TestApp/Views/Home/Index.cshtml +++ /dev/null @@ -1,9 +0,0 @@ -@{ - ViewData["Title"] = "Home Page"; -} -@inject NormalClass normalClass -@inject ITest Class1 "NumberOne" -
-

Welcome

-

Learn about building Web apps with ASP.NET Core.

-
diff --git a/TestApp/Views/Home/Privacy.cshtml b/TestApp/Views/Home/Privacy.cshtml deleted file mode 100644 index af4fb195a3c..00000000000 --- a/TestApp/Views/Home/Privacy.cshtml +++ /dev/null @@ -1,6 +0,0 @@ -@{ - ViewData["Title"] = "Privacy Policy"; -} -

@ViewData["Title"]

- -

Use this page to detail your site's privacy policy.

diff --git a/TestApp/Views/Shared/Error.cshtml b/TestApp/Views/Shared/Error.cshtml deleted file mode 100644 index a1e04783c67..00000000000 --- a/TestApp/Views/Shared/Error.cshtml +++ /dev/null @@ -1,25 +0,0 @@ -@model ErrorViewModel -@{ - ViewData["Title"] = "Error"; -} - -

Error.

-

An error occurred while processing your request.

- -@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} - -

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

diff --git a/TestApp/Views/Shared/_Layout.cshtml b/TestApp/Views/Shared/_Layout.cshtml deleted file mode 100644 index 081b7f67170..00000000000 --- a/TestApp/Views/Shared/_Layout.cshtml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - @ViewData["Title"] - TestApp - - - - - - -
- -
-
-
- @RenderBody() -
-
- -
-
- © 2026 - TestApp - Privacy -
-
- - - - @await RenderSectionAsync("Scripts", required: false) - - diff --git a/TestApp/Views/Shared/_Layout.cshtml.css b/TestApp/Views/Shared/_Layout.cshtml.css deleted file mode 100644 index c187c02e050..00000000000 --- a/TestApp/Views/Shared/_Layout.cshtml.css +++ /dev/null @@ -1,48 +0,0 @@ -/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification -for details on configuring this project to bundle and minify static web assets. */ - -a.navbar-brand { - white-space: normal; - text-align: center; - word-break: break-all; -} - -a { - color: #0077cc; -} - -.btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} - -.nav-pills .nav-link.active, .nav-pills .show > .nav-link { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} - -.border-top { - border-top: 1px solid #e5e5e5; -} -.border-bottom { - border-bottom: 1px solid #e5e5e5; -} - -.box-shadow { - box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); -} - -button.accept-policy { - font-size: 1rem; - line-height: inherit; -} - -.footer { - position: absolute; - bottom: 0; - width: 100%; - white-space: nowrap; - line-height: 60px; -} diff --git a/TestApp/Views/Shared/_ValidationScriptsPartial.cshtml b/TestApp/Views/Shared/_ValidationScriptsPartial.cshtml deleted file mode 100644 index 5d1f6857627..00000000000 --- a/TestApp/Views/Shared/_ValidationScriptsPartial.cshtml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/TestApp/Views/_ViewImports.cshtml b/TestApp/Views/_ViewImports.cshtml deleted file mode 100644 index 07af67cf44c..00000000000 --- a/TestApp/Views/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@using TestApp -@using TestApp.Models -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/TestApp/Views/_ViewStart.cshtml b/TestApp/Views/_ViewStart.cshtml deleted file mode 100644 index a5f10045db9..00000000000 --- a/TestApp/Views/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} diff --git a/TestApp/appsettings.Development.json b/TestApp/appsettings.Development.json deleted file mode 100644 index 0c208ae9181..00000000000 --- a/TestApp/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/TestApp/appsettings.json b/TestApp/appsettings.json deleted file mode 100644 index 10f68b8c8b4..00000000000 --- a/TestApp/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/TestApp/wwwroot/css/site.css b/TestApp/wwwroot/css/site.css deleted file mode 100644 index 819f61241bb..00000000000 --- a/TestApp/wwwroot/css/site.css +++ /dev/null @@ -1,31 +0,0 @@ -html { - font-size: 14px; -} - -@media (min-width: 768px) { - html { - font-size: 16px; - } -} - -.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { - box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; -} - -html { - position: relative; - min-height: 100%; -} - -body { - margin-bottom: 60px; -} - -.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { - color: var(--bs-secondary-color); - text-align: end; -} - -.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { - text-align: start; -} \ No newline at end of file diff --git a/TestApp/wwwroot/favicon.ico b/TestApp/wwwroot/favicon.ico deleted file mode 100644 index 63e859b476eff5055e0e557aaa151ca8223fbeef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5430 zcmc&&Yj2xp8Fqnv;>&(QB_ve7>^E#o2mu=cO~A%R>DU-_hfbSRv1t;m7zJ_AMrntN zy0+^f&8be>q&YYzH%(88lQ?#KwiCzaCO*ZEo%j&v;<}&Lj_stKTKK>#U3nin@AF>w zb3ONSAFR{u(S1d?cdw53y}Gt1b-Hirbh;;bm(Rcbnoc*%@jiaXM|4jU^1WO~`TYZ~ zC-~jh9~b-f?fX`DmwvcguQzn*uV}c^Vd&~?H|RUs4Epv~gTAfR(B0lT&?RWQOtduM z^1vUD9{HQsW!{a9|0crA34m7Z6lpG^}f6f?={zD+ zXAzk^i^aKN_}s2$eX81wjSMONE#WVdzf|MT)Ap*}Vsn!XbvsI#6o&ij{87^d%$|A{ z=F{KB%)g%@z76yBzbb7seW**Ju8r4e*Z3PWNX3_tTDgzZatz7)Q6ytwB%@&@A|XT; zecM`Snxx5po$C)%yCP!KEtos~eOS)@2=kX-RIm)4glMCoagTEFxrBeSX%Euz734Fk z%7)x(k~T!@Hbg_37NSQL!vlTBXoURSzt~I**Zw`&F24fH*&kx=%nvZv|49SC*daD( zIw<~%#=lk8{2-l(BcIjy^Q$Q&m#KlWL9?UG{b8@qhlD z;umc+6p%|NsAT~0@DgV4-NKgQuWPWrmPIK&&XhV&n%`{l zOl^bbWYjQNuVXTXESO)@|iUKVmErPUDfz2Wh`4dF@OFiaCW|d`3paV^@|r^8T_ZxM)Z+$p5qx# z#K=z@%;aBPO=C4JNNGqVv6@UGolIz;KZsAro``Rz8X%vq_gpi^qEV&evgHb_=Y9-l z`)imdx0UC>GWZYj)3+3aKh?zVb}=@%oNzg7a8%kfVl)SV-Amp1Okw&+hEZ3|v(k8vRjXW9?ih`&FFM zV$~{j3IzhtcXk?Mu_!12;=+I7XK-IR2>Yd%VB^?oI9c^E&Chb&&je$NV0P-R;ujkP z;cbLCCPEF6|22NDj=S`F^2e~XwT1ZnRX8ra0#DaFa9-X|8(xNW_+JhD75WnSd7cxo z2>I_J5{c|WPfrgl7E2R)^c}F7ry()Z>$Jhk9CzZxiPKL#_0%`&{MX>P_%b~Dx0D^S z7xP1(DQ!d_Icpk!RN3I1w@~|O1ru#CO==h#9M~S4Chx*@?=EKUPGBv$tmU+7Zs_al z`!jR?6T&Z7(%uVq>#yLu`abWk!FBlnY{RFNHlj~6zh*;@u}+}viRKsD`IIxN#R-X3 z@vxu#EA_m}I503U(8Qmx^}u;)KfGP`O9E1H1Q|xeeksX8jC%@!{YT1)!lWgO=+Y3*jr=iSxvOW1}^HSy=y){tOMQJ@an>sOl4FYniE z;GOxd7AqxZNbYFNqobpv&HVO$c-w!Y*6r;$2oJ~h(a#(Bp<-)dg*mNigX~9rPqcHv z^;c*|Md?tD)$y?6FO$DWl$jUGV`F1G_^E&E>sY*YnA~ruv3=z9F8&&~Xpm<<75?N3 z>x~`I&M9q)O1=zWZHN9hZWx>RQ}zLP+iL57Q)%&_^$Sme^^G7;e-P~CR?kqU#Io#( z(nH1Wn*Ig)|M>WLGrxoU?FZrS`4GO&w;+39A3f8w{{Q7eg|$+dIlNFPAe+tN=FOYU z{A&Fg|H73+w1IK(W=j*L>JQgz$g0 z7JpKXLHIh}#$wm|N`s}o-@|L_`>*(gTQ~)wr3Eap7g%PVNisKw82im;Gdv#85x#s+ zoqqtnwu4ycd>cOQgRh-=aEJbnvVK`}ja%+FZx}&ehtX)n(9nVfe4{mn0bgijUbNr7Tf5X^$*{qh2%`?--%+sbSrjE^;1e3>% zqa%jdY16{Y)a1hSy*mr0JGU05Z%=qlx5vGvTjSpTt6k%nR06q}1DU`SQh_ZAeJ}A@`hL~xvv05U?0%=spP`R>dk?cOWM9^KNb7B?xjex>OZo%JMQQ1Q zB|q@}8RiP@DWn-(fB;phPaIOP2Yp)XN3-Fsn)S3w($4&+p8f5W_f%gac}QvmkHfCj$2=!t`boCvQ zCW;&Dto=f8v##}dy^wg3VNaBy&kCe3N;1|@n@pUaMPT?(aJ9b*(gJ28$}(2qFt$H~u5z94xcIQkcOI++)*exzbrk?WOOOf*|%k5#KV zL=&ky3)Eirv$wbRJ2F2s_ILQY--D~~7>^f}W|Aw^e7inXr#WLI{@h`0|jHud2Y~cI~Yn{r_kU^Vo{1gja(); - - // Assert - var documentNode = processor.GetDocumentNode(); - var classNode = documentNode.GetClassNode(); - - Assert.Equal(2, classNode.Children.Count); - - var node = Assert.IsType(classNode.Children[1]); - Assert.Equal("PropertyType", node.TypeName); - Assert.Equal("PropertyName", node.MemberName); - Assert.Equal("\"PropertyKey\"", node.KeyName); - } [Fact] public void InjectDirectivePass_Execute_DedupesPropertiesByName() diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs index 82619059e9c..11a910ccc20 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/InjectTargetExtensionTest.cs @@ -40,34 +40,6 @@ public void InjectDirectiveTargetExtension_WritesProperty() context.CodeWriter.GetText().ToString()); } - - [Fact] - public void KeyedInjectDirectiveTargetExtension_WritesProperty() - { - // Arrange - using var context = TestCodeRenderingContext.CreateRuntime(); - var target = new KeyedInjectTargetExtension(considerNullabilityEnforcement: true); - var node = new KeyedInjectIntermediateNode() - { - TypeName = "PropertyType", - MemberName = "PropertyName", - KeyName = "\"PropertyKey\"", - }; - - // Act - target.WriteKeyedInjectProperty(context, node); - - // Assert - Assert.Equal(""" - #nullable restore - [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "PropertyKey")] - public PropertyType PropertyName { get; private set; } = default!; - #nullable disable - - """, - context.CodeWriter.GetText().ToString()); - } - [Fact] public void InjectDirectiveTargetExtension_WritesPropertyWithLinePragma_WhenSourceIsSet() { diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/KeyedInjectDirectiveTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/KeyedInjectDirectiveTest.cs new file mode 100644 index 00000000000..c04324a2f97 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/KeyedInjectDirectiveTest.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; + +public class KeyedInjectDirectiveTest : RazorProjectEngineTestBase +{ + protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_3_0; + + protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder) + { + // Notice we're not registering the InjectDirective.Pass here so we can run it on demand. + builder.AddDirective(InjectDirective.Directive); + builder.AddDirective(KeyedInjectDirective.Directive); + builder.AddDirective(ModelDirective.Directive); + + builder.Features.Add(new RazorPageDocumentClassifierPass()); + builder.Features.Add(new MvcViewDocumentClassifierPass()); + } + + protected override void ConfigureCodeDocumentProcessor(RazorCodeDocumentProcessor processor) + { + processor.ExecutePhasesThrough(); + } + + [Fact] + public void KeyedInjectDirectivePass_Execute_DefinesProperty() + { + // Arrange + var codeDocument = ProjectEngine.CreateCodeDocument(@" +@keyedinject PropertyType PropertyName ""PropertyKey"" +"); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var classNode = documentNode.GetClassNode(); + + Assert.Equal(2, classNode.Children.Count); + + var node = Assert.IsType(classNode.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + Assert.Equal("\"PropertyKey\"", node.KeyName); + } + + [Fact] + public void KeyedInjectDirectivePass_Execute_DedupesPropertiesByName() + { + // Arrange + var codeDocument = ProjectEngine.CreateCodeDocument(@" +@keyedinject PropertyType PropertyName ""SomeKey"" +@keyedinject PropertyType2 PropertyName ""SomeKey2"" +"); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var classNode = documentNode.GetClassNode(); + + Assert.Equal(2, classNode.Children.Count); + + var node = Assert.IsType(classNode.Children[1]); + Assert.Equal("PropertyType2", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + Assert.Equal("\"SomeKey2\"", node.KeyName); + } + + [Fact] + public void KeyedInjectDirectivePass_Execute_ExpandsTModel_WithDynamic() + { + // Arrange + var codeDocument = ProjectEngine.CreateCodeDocument(@" +@keyedinject PropertyType PropertyName ""SomeKey"" +"); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var classNode = documentNode.GetClassNode(); + + Assert.Equal(2, classNode.Children.Count); + + var node = Assert.IsType(classNode.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + Assert.Equal("\"SomeKey\"", node.KeyName); + } + + [Fact] + public void KeyedInjectDirectivePass_Execute_ExpandsTModel_WithModelTypeFirst() + { + // Arrange + var codeDocument = ProjectEngine.CreateCodeDocument(@" +@model ModelType +@keyedinject PropertyType PropertyName ""SomeKey"" +"); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var classNode = documentNode.GetClassNode(); + + Assert.Equal(2, classNode.Children.Count); + + var node = Assert.IsType(classNode.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + Assert.Equal("\"SomeKey\"", node.KeyName); + } + + [Fact] + public void KeyedInjectDirectivePass_Execute_ExpandsTModel_WithModelType() + { + // Arrange + var codeDocument = ProjectEngine.CreateCodeDocument(@" +@keyedinject PropertyType PropertyName ""SomeKey"" +@model ModelType +"); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var classNode = documentNode.GetClassNode(); + + Assert.Equal(2, classNode.Children.Count); + + var node = Assert.IsType(classNode.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + Assert.Equal("\"SomeKey\"", node.KeyName); + } +} diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/KeyedInjectTargetExtensionTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/KeyedInjectTargetExtensionTest.cs new file mode 100644 index 00000000000..9c40d276215 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/KeyedInjectTargetExtensionTest.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.AspNetCore.Razor.Language.Legacy; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; + +public class KeyedInjectTargetExtensionTest +{ + + [Fact] + public void KeyedInjectDirectiveTargetExtension_WritesProperty() + { + // Arrange + using var context = TestCodeRenderingContext.CreateRuntime(); + var target = new KeyedInjectTargetExtension(considerNullabilityEnforcement: true); + var node = new KeyedInjectIntermediateNode() + { + TypeName = "PropertyType", + MemberName = "PropertyName", + KeyName = "\"PropertyKey\"", + }; + + // Act + target.WriteKeyedInjectProperty(context, node); + + // Assert + Assert.Equal(""" + #nullable restore + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute(Key = "PropertyKey")] + public PropertyType PropertyName { get; private set; } = default!; + #nullable disable + + """, + context.CodeWriter.GetText().ToString()); + } + +} diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs index 6e493e2172f..acf4b7bf672 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs @@ -60,6 +60,7 @@ private static void AssertDefaultFeatures(RazorProjectEngine engine) feature => Assert.IsType(feature), feature => Assert.IsType(feature), feature => Assert.IsType(feature), + feature => Assert.IsType(feature), feature => Assert.IsType(feature), feature => Assert.IsType(feature), feature => Assert.IsType(feature), From 8539d852ee3922f6dbc10c7ad70b096719b85ebb Mon Sep 17 00:00:00 2001 From: wlsquid Date: Wed, 11 Feb 2026 22:34:15 +1000 Subject: [PATCH 14/14] changed comments --- .../Components/ComponentKeyedInjectIntermediateNode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs index b32bd8046bf..d963e1c4a68 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentKeyedInjectIntermediateNode.cs @@ -84,8 +84,8 @@ public override void WriteNode(CodeTarget target, CodeRenderingContext context) if (!context.Options.DesignTime || !IsMalformed) { - // I was just writing out string interpolation here with no source mappings but that was messing with the - // integration tests. Not sure what is preferred. + // I was just writing out the key with string interpolation here with no source mappings but that was messing with the + // integration tests. Leaving it like this but not sure what is preferred. context.CodeWriter.Write($"[global::{ComponentsApi.InjectAttribute.FullTypeName}("); context.CodeWriter.Write("Key = ");