From fab158f16a00bc8d5449d0b72ce063d6ae2ce8b5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 16 Jan 2026 00:30:36 +0000
Subject: [PATCH 1/3] Initial plan
From 13976f9f39437defb750d4677bd0da09c88adafd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 16 Jan 2026 00:43:20 +0000
Subject: [PATCH 2/3] Add support for @layout null in Blazor components
Co-authored-by: dbreshears <3432571+dbreshears@users.noreply.github.com>
---
.../ComponentCodeGenerationTestBase.cs | 14 ++++++++
.../ComponentDirectiveIntegrationTest.cs | 12 +++++++
.../TestComponent.codegen.cs | 34 +++++++++++++++++++
.../LayoutDirective_Null/TestComponent.ir.txt | 17 ++++++++++
.../TestComponent.codegen.cs | 24 +++++++++++++
.../LayoutDirective_Null/TestComponent.ir.txt | 9 +++++
.../ComponentLayoutDirectivePass.cs | 6 ++++
.../DesignTimeDirectiveTargetExtension.cs | 6 ++++
8 files changed, 122 insertions(+)
create mode 100644 src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs
create mode 100644 src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt
create mode 100644 src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs
create mode 100644 src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt
diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs
index cb969d332a5..58c5c870d71 100644
--- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs
+++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs
@@ -2373,6 +2373,20 @@ @layout System.Object
CompileToAssembly(generated);
}
+ [IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/8340")]
+ public void LayoutDirective_Null()
+ {
+ // Act
+ var generated = CompileToCSharp("""
+ @layout null
+ """);
+
+ // Assert
+ AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
+ AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
+ CompileToAssembly(generated);
+ }
+
[IntegrationTestFact]
public void Component_AddContent_Multiline()
{
diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDirectiveIntegrationTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDirectiveIntegrationTest.cs
index 9ec30ba0ba9..d06a74a5fbd 100644
--- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDirectiveIntegrationTest.cs
+++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDirectiveIntegrationTest.cs
@@ -44,6 +44,18 @@ public void SupportsLayoutDeclarations()
Assert.NotNull(layoutAttribute);
}
+ [Fact]
+ public void SupportsLayoutNullDeclaration()
+ {
+ // Arrange/Act
+ var component = CompileToComponent(
+ "@layout null\n" +
+ "Hello");
+
+ // Assert - when layout is set to null, no layout attribute should be generated
+ Assert.Null(component.GetAttributes().FirstOrDefault(a => a.AttributeClass.Name == "LayoutAttribute"));
+ }
+
[Fact]
public void SupportsImplementsDeclarations()
{
diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs
new file mode 100644
index 00000000000..2a53b15489f
--- /dev/null
+++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs
@@ -0,0 +1,34 @@
+//
+#pragma warning disable 1591
+namespace Test
+{
+ #line default
+ using global::System;
+ using global::System.Collections.Generic;
+ using global::System.Linq;
+ using global::System.Threading.Tasks;
+ using global::Microsoft.AspNetCore.Components;
+ #line default
+ #line hidden
+ #nullable restore
+ public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase
+ #nullable disable
+ {
+ #pragma warning disable 219
+ private void __RazorDirectiveTokenHelpers__() {
+ ((global::System.Action)(() => {
+ }
+ ))();
+ }
+ #pragma warning restore 219
+ #pragma warning disable 0414
+ private static object __o = null;
+ #pragma warning restore 0414
+ #pragma warning disable 1998
+ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
+ {
+ }
+ #pragma warning restore 1998
+ }
+}
+#pragma warning restore 1591
diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt
new file mode 100644
index 00000000000..1a11a374c2a
--- /dev/null
+++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt
@@ -0,0 +1,17 @@
+Document -
+ NamespaceDeclaration - - Test
+ UsingDirective - (3:1,1 [20] ) - global::System
+ UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic
+ UsingDirective - (69:3,1 [25] ) - global::System.Linq
+ UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks
+ UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components
+ ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase -
+ DesignTimeDirective -
+ DirectiveToken - (8:0,8 [4] x:\dir\subdir\Test\TestComponent.cshtml) - null
+ CSharpCode -
+ IntermediateToken - - CSharp - #pragma warning disable 0414
+ CSharpCode -
+ IntermediateToken - - CSharp - private static object __o = null;
+ CSharpCode -
+ IntermediateToken - - CSharp - #pragma warning restore 0414
+ MethodDeclaration - - protected override - void - BuildRenderTree
diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs
new file mode 100644
index 00000000000..9dfa26c2a18
--- /dev/null
+++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs
@@ -0,0 +1,24 @@
+//
+#pragma warning disable 1591
+namespace Test
+{
+ #line default
+ using global::System;
+ using global::System.Collections.Generic;
+ using global::System.Linq;
+ using global::System.Threading.Tasks;
+ using global::Microsoft.AspNetCore.Components;
+ #line default
+ #line hidden
+ #nullable restore
+ public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase
+ #nullable disable
+ {
+ #pragma warning disable 1998
+ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
+ {
+ }
+ #pragma warning restore 1998
+ }
+}
+#pragma warning restore 1591
diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt
new file mode 100644
index 00000000000..5271eee9142
--- /dev/null
+++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt
@@ -0,0 +1,9 @@
+Document -
+ NamespaceDeclaration - - Test
+ UsingDirective - (3:1,1 [20] ) - global::System
+ UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic
+ UsingDirective - (69:3,1 [25] ) - global::System.Linq
+ UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks
+ UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components
+ ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase -
+ MethodDeclaration - - protected override - void - BuildRenderTree
diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs
index fbf56b34cc3..297ba14c36b 100644
--- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs
+++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs
@@ -33,6 +33,12 @@ protected override void ExecuteCore(
return;
}
+ // If the layout is explicitly set to null, don't generate a layout attribute
+ if (string.Equals(token.Content, "null", System.StringComparison.Ordinal))
+ {
+ return;
+ }
+
var attributeNode = new CSharpCodeIntermediateNode();
attributeNode.Children.AddRange([
IntermediateNodeFactory.CSharpToken($"[global::{ComponentsApi.LayoutAttribute.FullTypeName}(typeof("),
diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs
index 1f286f1cb31..eac169b1f48 100644
--- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs
+++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs
@@ -73,6 +73,12 @@ private void WriteDesignTimeDirectiveToken(CodeRenderingContext context, DesignT
break;
}
+ // Skip validation for "null" which is used to disable layouts
+ if (string.Equals(node.Content, "null", StringComparison.Ordinal))
+ {
+ break;
+ }
+
// {node.Content} __typeHelper = default({node.Content});
using (context.BuildLinePragma(node.Source))
{
From 03a1aa8fe38ce613995ec870b680f9513f7d9253 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 16 Jan 2026 00:47:58 +0000
Subject: [PATCH 3/3] Address code review feedback - fix namespace
qualification
Co-authored-by: dbreshears <3432571+dbreshears@users.noreply.github.com>
---
.../src/Language/Components/ComponentLayoutDirectivePass.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs
index 297ba14c36b..23023a1d5f3 100644
--- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs
+++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs
@@ -1,6 +1,7 @@
// 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.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@@ -34,7 +35,7 @@ protected override void ExecuteCore(
}
// If the layout is explicitly set to null, don't generate a layout attribute
- if (string.Equals(token.Content, "null", System.StringComparison.Ordinal))
+ if (string.Equals(token.Content, "null", StringComparison.Ordinal))
{
return;
}