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..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;
@@ -33,6 +34,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", 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))
{