diff --git a/AspNetCore.sln b/AspNetCore.sln
index 5a54800dae83..57a1bf9f0b3f 100644
--- a/AspNetCore.sln
+++ b/AspNetCore.sln
@@ -1814,6 +1814,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B32FF7A7-9
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.OpenApi.Tests", "src\OpenApi\test\Microsoft.AspNetCore.OpenApi.Tests\Microsoft.AspNetCore.OpenApi.Tests.csproj", "{B9BBC1A8-7F58-4F43-94C3-5F3CB125CEF7}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.App.SourceGenerators", "src\Framework\AspNetCoreAnalyzers\src\SourceGenerators\Microsoft.AspNetCore.App.SourceGenerators.csproj", "{C3928C15-1836-46DB-A09D-9EFBCCA33E08}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -10959,6 +10961,22 @@ Global
{B9BBC1A8-7F58-4F43-94C3-5F3CB125CEF7}.Release|x64.Build.0 = Release|Any CPU
{B9BBC1A8-7F58-4F43-94C3-5F3CB125CEF7}.Release|x86.ActiveCfg = Release|Any CPU
{B9BBC1A8-7F58-4F43-94C3-5F3CB125CEF7}.Release|x86.Build.0 = Release|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Debug|arm64.Build.0 = Debug|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Debug|x64.Build.0 = Debug|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Debug|x86.Build.0 = Debug|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|arm64.ActiveCfg = Release|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|arm64.Build.0 = Release|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x64.ActiveCfg = Release|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x64.Build.0 = Release|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x86.ActiveCfg = Release|Any CPU
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -11855,6 +11873,7 @@ Global
{757CBDE0-5D0A-4FD8-99F3-6C20BDDD4E63} = {5FE1FBC1-8CE3-4355-9866-44FE1307C5F1}
{B32FF7A7-9CB3-4DCD-AE97-3B2594DB9DAC} = {2299CCD8-8F9C-4F2B-A633-9BF4DA81022B}
{B9BBC1A8-7F58-4F43-94C3-5F3CB125CEF7} = {B32FF7A7-9CB3-4DCD-AE97-3B2594DB9DAC}
+ {C3928C15-1836-46DB-A09D-9EFBCCA33E08} = {B5D98AEB-9409-4280-8225-9C1EC6A791B2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
diff --git a/eng/Dependencies.props b/eng/Dependencies.props
index af9ef7fa88e3..11229e8c631c 100644
--- a/eng/Dependencies.props
+++ b/eng/Dependencies.props
@@ -68,6 +68,7 @@ and are generated based on the last package release.
+
diff --git a/eng/Versions.props b/eng/Versions.props
index 79edeec22b62..7c5e2dbae14c 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -261,6 +261,7 @@
3.3.3
1.1.2-beta1.24121.1
1.1.2-beta1.24121.1
+ 1.1.2-beta1.24121.1
1.0.0-20230414.1
$(IdentityModelVersion)
$(IdentityModelVersion)
diff --git a/src/Framework/App.Ref/src/Microsoft.AspNetCore.App.Ref.csproj b/src/Framework/App.Ref/src/Microsoft.AspNetCore.App.Ref.csproj
index 99eb568b96bb..82d88cfd500c 100644
--- a/src/Framework/App.Ref/src/Microsoft.AspNetCore.App.Ref.csproj
+++ b/src/Framework/App.Ref/src/Microsoft.AspNetCore.App.Ref.csproj
@@ -73,6 +73,10 @@ This package is an internal implementation of the .NET Core SDK and is not meant
Private="false"
ReferenceOutputAssembly="false" />
+
+
@@ -175,6 +179,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant
<_InitialRefPackContent Include="$(PkgMicrosoft_Internal_Runtime_AspNetCore_Transport)\$(AnalyzersPackagePath)**\*.*" PackagePath="$(AnalyzersPackagePath)" />
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.App.Analyzers\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.App.Analyzers.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
+ <_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.App.SourceGenerators\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.App.SourceGenerators.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.Components.Analyzers\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Components.Analyzers.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.App.CodeFixes\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.App.CodeFixes.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.Http.RequestDelegateGenerator\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Http.RequestDelegateGenerator.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
diff --git a/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/Microsoft.AspNetCore.App.SourceGenerators.csproj b/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/Microsoft.AspNetCore.App.SourceGenerators.csproj
new file mode 100644
index 000000000000..f623f6d11875
--- /dev/null
+++ b/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/Microsoft.AspNetCore.App.SourceGenerators.csproj
@@ -0,0 +1,18 @@
+
+
+ netstandard2.0
+ true
+ false
+ true
+ false
+ enable
+ true
+
+
+
+
+
+
+
+
+
diff --git a/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs b/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs
new file mode 100644
index 000000000000..4ea333b96252
--- /dev/null
+++ b/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.AspNetCore.SourceGenerators;
+
+[Generator]
+public class PublicProgramSourceGenerator : IIncrementalGenerator
+{
+ private const string PublicPartialProgramClassSource = """
+//
+public partial class Program { }
+""";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var internalGeneratedProgramClass = context.CompilationProvider
+ // Get the entry point associated with the compilation, this maps to the Main method definition
+ .Select((compilation, cancellationToken) => compilation.GetEntryPoint(cancellationToken))
+ // Get the containing symbol of the entry point, this maps to the Program class
+ .Select((symbol, _) => symbol?.ContainingSymbol)
+ // If the program class is already public, we don't need to generate anything.
+ .Select((symbol, _) => symbol?.DeclaredAccessibility == Accessibility.Public ? null : symbol)
+ // If the discovered `Program` type is not a class then its not
+ // generated and has been defined in source, so we can skip it
+ .Select((symbol, _) => symbol is INamedTypeSymbol { TypeKind: TypeKind.Class } ? symbol : null)
+ // If there are multiple partial declarations, then do nothing since we don't want
+ // to trample on visibility explicitly set by the user
+ .Select((symbol, _) => symbol is { DeclaringSyntaxReferences: { Length: 1 } declaringSyntaxReferences } ? declaringSyntaxReferences.Single() : null)
+ // If the `Program` class is already declared in user code, we don't need to generate anything.
+ .Select((declaringSyntaxReference, cancellationToken) => declaringSyntaxReference?.GetSyntax(cancellationToken) is ClassDeclarationSyntax ? null : declaringSyntaxReference);
+
+ context.RegisterSourceOutput(internalGeneratedProgramClass, (context, result) =>
+ {
+ if (result is not null)
+ {
+ context.AddSource("PublicTopLevelProgram.Generated.g.cs", PublicPartialProgramClassSource);
+ }
+ });
+ }
+}
diff --git a/src/Framework/AspNetCoreAnalyzers/test/Microsoft.AspNetCore.App.Analyzers.Test.csproj b/src/Framework/AspNetCoreAnalyzers/test/Microsoft.AspNetCore.App.Analyzers.Test.csproj
index 8070e80c0b51..5e75a5d1f451 100644
--- a/src/Framework/AspNetCoreAnalyzers/test/Microsoft.AspNetCore.App.Analyzers.Test.csproj
+++ b/src/Framework/AspNetCoreAnalyzers/test/Microsoft.AspNetCore.App.Analyzers.Test.csproj
@@ -15,6 +15,7 @@
+
@@ -24,6 +25,7 @@
+
diff --git a/src/Framework/AspNetCoreAnalyzers/test/SourceGenerators/PublicTopLevelProgramGeneratorTests.cs b/src/Framework/AspNetCoreAnalyzers/test/SourceGenerators/PublicTopLevelProgramGeneratorTests.cs
new file mode 100644
index 000000000000..c91a110031a7
--- /dev/null
+++ b/src/Framework/AspNetCoreAnalyzers/test/SourceGenerators/PublicTopLevelProgramGeneratorTests.cs
@@ -0,0 +1,134 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using VerifyCS = Microsoft.AspNetCore.Analyzers.Verifiers.CSharpSourceGeneratorVerifier;
+
+namespace Microsoft.AspNetCore.SourceGenerators.Tests;
+
+public class PublicTopLevelProgramGeneratorTests
+{
+ [Fact]
+ public async Task GeneratesSource_ProgramWithTopLevelStatements()
+ {
+ var source = """
+using Microsoft.AspNetCore.Builder;
+
+var app = WebApplication.Create();
+
+app.MapGet("/", () => "Hello, World!");
+
+app.Run();
+""";
+
+ var expected = """
+//
+public partial class Program { }
+""";
+
+ await VerifyCS.VerifyAsync(source, "PublicTopLevelProgram.Generated.g.cs", expected);
+ }
+
+ [Fact]
+ public async Task DoesNotGeneratesSource_IfProgramIsAlreadyPublic()
+ {
+ var source = """
+using Microsoft.AspNetCore.Builder;
+
+var app = WebApplication.Create();
+
+app.MapGet("/", () => "Hello, World!");
+
+app.Run();
+
+public partial class Program { }
+""";
+
+ await VerifyCS.VerifyAsync(source);
+ }
+
+ [Fact]
+ public async Task DoesNotGeneratesSource_IfProgramDeclaresExplicitInternalAccess()
+ {
+ var source = """
+using Microsoft.AspNetCore.Builder;
+
+var app = WebApplication.Create();
+
+app.MapGet("/", () => "Hello, World!");
+
+app.Run();
+
+internal partial class Program { }
+""";
+
+ await VerifyCS.VerifyAsync(source);
+ }
+
+ [Fact]
+ public async Task DoesNotGeneratorSource_ExplicitPublicProgramClass()
+ {
+ var source = """
+using Microsoft.AspNetCore.Builder;
+
+public class Program
+{
+ public static void Main()
+ {
+ var app = WebApplication.Create();
+
+ app.MapGet("/", () => "Hello, World!");
+
+ app.Run();
+ }
+}
+""";
+
+ await VerifyCS.VerifyAsync(source);
+ }
+
+ [Fact]
+ public async Task DoesNotGeneratorSource_ExplicitInternalProgramClass()
+ {
+ var source = """
+using Microsoft.AspNetCore.Builder;
+
+internal class Program
+{
+ public static void Main()
+ {
+ var app = WebApplication.Create();
+
+ app.MapGet("/", () => "Hello, World!");
+
+ app.Run();
+ }
+}
+""";
+
+ await VerifyCS.VerifyAsync(source);
+ }
+
+ [Theory]
+ [InlineData("interface")]
+ [InlineData("struct")]
+ public async Task DoesNotGeneratorSource_ExplicitInternalProgramType(string type)
+ {
+ var source = $$"""
+using Microsoft.AspNetCore.Builder;
+
+internal {{type}} Program
+{
+ public static void Main(string[] args)
+ {
+ var app = WebApplication.Create();
+
+ app.MapGet("/", () => "Hello, World!");
+
+ app.Run();
+ }
+}
+""";
+
+ await VerifyCS.VerifyAsync(source);
+ }
+}
diff --git a/src/Framework/AspNetCoreAnalyzers/test/Verifiers/CSharpSourceGeneratorVerifier.cs b/src/Framework/AspNetCoreAnalyzers/test/Verifiers/CSharpSourceGeneratorVerifier.cs
new file mode 100644
index 000000000000..544adc924754
--- /dev/null
+++ b/src/Framework/AspNetCoreAnalyzers/test/Verifiers/CSharpSourceGeneratorVerifier.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+using Microsoft.AspNetCore.Analyzers.WebApplicationBuilder;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.AspNetCore.Analyzers.Verifiers;
+
+public static partial class CSharpSourceGeneratorVerifier
+ where TSourceGenerator : IIncrementalGenerator, new()
+{
+ public static async Task VerifyAsync(string source, string generatedFileName, string generatedSource)
+ {
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source.ReplaceLineEndings() },
+ OutputKind = OutputKind.ConsoleApplication,
+ GeneratedSources =
+ {
+ (typeof(TSourceGenerator), generatedFileName, SourceText.From(generatedSource, Encoding.UTF8))
+ },
+ ReferenceAssemblies = CSharpAnalyzerVerifier.GetReferenceAssemblies()
+ },
+ };
+ await test.RunAsync(CancellationToken.None);
+ }
+
+ public static async Task VerifyAsync(string source)
+ {
+ var test = new CSharpSourceGeneratorTest
+ {
+ TestState =
+ {
+ Sources = { source.ReplaceLineEndings() },
+ OutputKind = OutputKind.ConsoleApplication,
+ ReferenceAssemblies = CSharpAnalyzerVerifier.GetReferenceAssemblies()
+ },
+ };
+ await test.RunAsync(CancellationToken.None);
+ }
+}