diff --git a/docs/list-of-diagnostics.md b/docs/list-of-diagnostics.md
index e6195634513e..34dacca5a243 100644
--- a/docs/list-of-diagnostics.md
+++ b/docs/list-of-diagnostics.md
@@ -35,6 +35,7 @@
| __`ASP0027`__ | Unnecessary public Program class declaration |
| __`ASP0028`__ | Consider using ListenAnyIP() instead of Listen(IPAddress.Any) |
| __`ASP0029`__ | Experimental warning for validations resolver APIs |
+| __`ASP0030`__ | Use Host.CreateDefaultBuilder instead of WebHost.CreateDefaultBuilder |
### API (`API1000-API1003`)
diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs
index e48dd9445bfc..c2cd9b184e79 100644
--- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs
+++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs
@@ -248,4 +248,13 @@ internal static class DiagnosticDescriptors
DiagnosticSeverity.Info,
isEnabledByDefault: true,
helpLinkUri: AnalyzersLink);
+
+ internal static readonly DiagnosticDescriptor UseCreateHostBuilderInsteadOfCreateWebHostBuilder = new(
+ "ASP0030",
+ CreateLocalizableResourceString(nameof(Resources.Analyzer_UseCreateHostBuilderInsteadOfCreateWebHostBuilder_Title)),
+ CreateLocalizableResourceString(nameof(Resources.Analyzer_UseCreateHostBuilderInsteadOfCreateWebHostBuilder_Message)),
+ Usage,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ helpLinkUri: AnalyzersLink);
}
diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Resources.resx b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Resources.resx
index 8c9397f5be64..fea73d77333e 100644
--- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Resources.resx
+++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Resources.resx
@@ -333,4 +333,10 @@
If the server does not specifically reject IPv6, IPAddress.IPv6Any is preferred over IPAddress.Any usage for safety and performance reasons. See https://aka.ms/aspnetcore-warnings/ASP0028 for more details.
+
+ Use Host.CreateDefaultBuilder instead of WebHost.CreateDefaultBuilder
+
+
+ WebHost is deprecated. Use Host.CreateDefaultBuilder and ConfigureWebHostDefaults instead.
+
diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/UseCreateHostBuilderInsteadOfCreateWebHostBuilderAnalyzer.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/UseCreateHostBuilderInsteadOfCreateWebHostBuilderAnalyzer.cs
new file mode 100644
index 000000000000..9e0fea9013c2
--- /dev/null
+++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/UseCreateHostBuilderInsteadOfCreateWebHostBuilderAnalyzer.cs
@@ -0,0 +1,109 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+using WellKnownType = Microsoft.AspNetCore.App.Analyzers.Infrastructure.WellKnownTypeData.WellKnownType;
+
+namespace Microsoft.AspNetCore.Analyzers.WebApplicationBuilder;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class UseCreateHostBuilderInsteadOfCreateWebHostBuilderAnalyzer : DiagnosticAnalyzer
+{
+ public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(
+ DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder
+ );
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterCompilationStartAction(context =>
+ {
+ var compilation = context.Compilation;
+ var wellKnownTypes = WellKnownTypes.GetOrCreate(compilation);
+
+ context.RegisterOperationAction(context =>
+ {
+ var invocation = (IInvocationOperation)context.Operation;
+ var targetMethod = invocation.TargetMethod;
+
+ // Check if this is WebHost.CreateDefaultBuilder
+ if (IsWebHostCreateDefaultBuilderCall(targetMethod, wellKnownTypes))
+ {
+ var diagnostic = Diagnostic.Create(
+ DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder,
+ invocation.Syntax.GetLocation()
+ );
+ context.ReportDiagnostic(diagnostic);
+ }
+ }, OperationKind.Invocation);
+
+ context.RegisterSyntaxNodeAction(context =>
+ {
+ var methodDeclaration = (MethodDeclarationSyntax)context.Node;
+ var semantic = context.SemanticModel;
+ var symbol = semantic.GetDeclaredSymbol(methodDeclaration);
+
+ // Check if this method returns IWebHostBuilder
+ if (symbol != null && IsWebHostBuilderReturnType(symbol, wellKnownTypes))
+ {
+ // Check if the method body contains WebHost.CreateDefaultBuilder
+ if (ContainsWebHostCreateDefaultBuilder(methodDeclaration))
+ {
+ var diagnostic = Diagnostic.Create(
+ DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder,
+ methodDeclaration.ReturnType.GetLocation()
+ );
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+ }, SyntaxKind.MethodDeclaration);
+ });
+ }
+
+ private static bool IsWebHostCreateDefaultBuilderCall(IMethodSymbol method, WellKnownTypes wellKnownTypes)
+ {
+ // Check if this is WebHost.CreateDefaultBuilder (not other WebHost methods)
+ if (method.Name == "CreateDefaultBuilder" &&
+ SymbolEqualityComparer.Default.Equals(method.ContainingType, wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_WebHost)))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsWebHostBuilderReturnType(IMethodSymbol method, WellKnownTypes wellKnownTypes)
+ {
+ // Check if the return type is IWebHostBuilder
+ var returnType = method.ReturnType;
+ return SymbolEqualityComparer.Default.Equals(returnType, wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Hosting_IWebHostBuilder));
+ }
+
+ private static bool ContainsWebHostCreateDefaultBuilder(MethodDeclarationSyntax methodDeclaration)
+ {
+ // Check if the method contains WebHost.CreateDefaultBuilder calls
+ var descendants = methodDeclaration.DescendantNodes().OfType();
+
+ foreach (var invocation in descendants)
+ {
+ if (invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
+ memberAccess.Expression is IdentifierNameSyntax identifier &&
+ identifier.Identifier.ValueText == "WebHost" &&
+ memberAccess.Name.Identifier.ValueText == "CreateDefaultBuilder")
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/Framework/AspNetCoreAnalyzers/src/CodeFixes/UseCreateHostBuilderInsteadOfCreateWebHostBuilderFixer.cs b/src/Framework/AspNetCoreAnalyzers/src/CodeFixes/UseCreateHostBuilderInsteadOfCreateWebHostBuilderFixer.cs
new file mode 100644
index 000000000000..96e6c40d7b6d
--- /dev/null
+++ b/src/Framework/AspNetCoreAnalyzers/src/CodeFixes/UseCreateHostBuilderInsteadOfCreateWebHostBuilderFixer.cs
@@ -0,0 +1,472 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.AspNetCore.Analyzers.WebApplicationBuilder.Fixers;
+
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseCreateHostBuilderInsteadOfCreateWebHostBuilderFixer)), Shared]
+public sealed class UseCreateHostBuilderInsteadOfCreateWebHostBuilderFixer : CodeFixProvider
+{
+ public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(
+ DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder.Id
+ );
+
+ public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
+
+ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+ if (root == null)
+ {
+ return;
+ }
+
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ var node = root.FindNode(diagnostic.Location.SourceSpan);
+
+ // Handle method return type case (IWebHostBuilder return type)
+ if (node is TypeSyntax returnType &&
+ returnType.Parent is MethodDeclarationSyntax methodDeclaration)
+ {
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: "Convert to IHostBuilder and use Host.CreateDefaultBuilder",
+ createChangedDocument: c => ConvertWebHostBuilderMethod(context.Document, methodDeclaration, c),
+ equivalenceKey: "ConvertWebHostBuilderMethod"),
+ diagnostic);
+ }
+
+ // Handle invocation case (WebHost.CreateDefaultBuilder)
+ else if (node.FirstAncestorOrSelf() is InvocationExpressionSyntax invocation)
+ {
+ if (IsWebHostCreateDefaultBuilderInvocation(invocation))
+ {
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: "Replace with Host.CreateDefaultBuilder",
+ createChangedDocument: c => ConvertWebHostCreateDefaultBuilder(context.Document, invocation, c),
+ equivalenceKey: "ConvertWebHostCreateDefaultBuilder"),
+ diagnostic);
+ }
+ }
+ }
+ }
+
+ private static bool IsWebHostCreateDefaultBuilderInvocation(InvocationExpressionSyntax invocation)
+ {
+ if (invocation.Expression is MemberAccessExpressionSyntax memberAccess)
+ {
+ return memberAccess.Expression is IdentifierNameSyntax { Identifier.ValueText: "WebHost" } &&
+ memberAccess.Name.Identifier.ValueText == "CreateDefaultBuilder";
+ }
+ return false;
+ }
+
+ private static async Task ConvertWebHostBuilderMethod(Document document, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken)
+ {
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ if (root == null)
+ {
+ return document;
+ }
+
+ var newMethodDeclaration = methodDeclaration;
+
+ // Change return type from IWebHostBuilder to IHostBuilder
+ if (IsWebHostBuilderType(methodDeclaration.ReturnType))
+ {
+ var newReturnType = SyntaxFactory.IdentifierName("IHostBuilder");
+ newMethodDeclaration = newMethodDeclaration.WithReturnType(newReturnType);
+ }
+
+ // Transform the method body to use Host.CreateDefaultBuilder and ConfigureWebHostDefaults
+ if (methodDeclaration.Body != null)
+ {
+ var transformedBody = TransformMethodBody(methodDeclaration.Body);
+ newMethodDeclaration = newMethodDeclaration.WithBody(transformedBody);
+ }
+ else if (methodDeclaration.ExpressionBody != null)
+ {
+ var transformedExpressionBody = TransformExpressionBody(methodDeclaration.ExpressionBody);
+ newMethodDeclaration = newMethodDeclaration.WithExpressionBody(transformedExpressionBody);
+ }
+
+ var newRoot = root.ReplaceNode(methodDeclaration, newMethodDeclaration.WithLeadingTrivia(methodDeclaration.GetLeadingTrivia()));
+
+ // Add the required using statement if not already present
+ if (root is CompilationUnitSyntax compilationUnit)
+ {
+ var hasHostingUsing = compilationUnit.Usings.Any(u =>
+ u.Name?.ToString() == "Microsoft.Extensions.Hosting");
+
+ if (!hasHostingUsing)
+ {
+ var hostingUsing = SyntaxFactory.UsingDirective(
+ SyntaxFactory.QualifiedName(
+ SyntaxFactory.QualifiedName(
+ SyntaxFactory.IdentifierName("Microsoft"),
+ SyntaxFactory.IdentifierName("Extensions")),
+ SyntaxFactory.IdentifierName("Hosting")));
+
+ newRoot = ((CompilationUnitSyntax)newRoot).AddUsings(hostingUsing);
+ }
+ }
+
+ return document.WithSyntaxRoot(newRoot);
+ }
+
+ private static bool IsWebHostBuilderType(TypeSyntax typeSyntax)
+ {
+ return typeSyntax.ToString().Equals("IWebHostBuilder");
+ }
+
+ private static async Task ConvertWebHostCreateDefaultBuilder(Document document, InvocationExpressionSyntax invocation, CancellationToken cancellationToken)
+ {
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ if (root == null)
+ {
+ return document;
+ }
+
+ // Find the full expression chain that contains this invocation
+ var fullExpression = GetFullExpressionChain(invocation);
+
+ SyntaxTriviaList leadingTrivia = fullExpression.GetLeadingTrivia();
+ if (!fullExpression.HasLeadingTrivia)
+ {
+ // Try to find some leading trivia from a parent
+ // e.g. using (var host = WebHost.CreateDefaultBuilder(args)) would not have leading trivia on the WebHost call
+ var parent = fullExpression.Parent;
+ while (parent != null && !parent.HasLeadingTrivia)
+ {
+ parent = parent.Parent;
+ }
+ leadingTrivia = parent?.GetLeadingTrivia() ?? SyntaxFactory.TriviaList();
+ }
+
+ // Transform the entire expression
+ var transformedExpression = TransformExpression(fullExpression, leadingTrivia);
+
+ var newRoot = root.ReplaceNode(fullExpression, transformedExpression);
+
+ // Add the required using statement if not already present
+ if (root is CompilationUnitSyntax compilationUnit)
+ {
+ var hasHostingUsing = compilationUnit.Usings.Any(u =>
+ u.Name?.ToString() == "Microsoft.Extensions.Hosting");
+
+ if (!hasHostingUsing)
+ {
+ var hostingUsing = SyntaxFactory.UsingDirective(
+ SyntaxFactory.QualifiedName(
+ SyntaxFactory.QualifiedName(
+ SyntaxFactory.IdentifierName("Microsoft"),
+ SyntaxFactory.IdentifierName("Extensions")),
+ SyntaxFactory.IdentifierName("Hosting")));
+
+ newRoot = ((CompilationUnitSyntax)newRoot).AddUsings(hostingUsing);
+ }
+ }
+
+ return document.WithSyntaxRoot(newRoot);
+ }
+
+ private static ExpressionSyntax GetFullExpressionChain(ExpressionSyntax expression)
+ {
+ // Walk up the tree to find the root of the method call chain
+ // The input expression is WebHost.CreateDefaultBuilder(), but we need to find the full chain
+ var current = expression;
+
+ // Walk up the parent hierarchy to find the outermost invocation in the chain
+ while (current.Parent != null)
+ {
+ if (current.Parent is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression == current)
+ {
+ // This current expression is the left side of a member access, so there's more to the chain
+ if (memberAccess.Parent is InvocationExpressionSyntax parentInvocation)
+ {
+ current = parentInvocation;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ // We've reached the end of the chain
+ break;
+ }
+ }
+
+ return current;
+ }
+
+ private static BlockSyntax TransformMethodBody(BlockSyntax body)
+ {
+ var transformedStatements = body.Statements.Select(TransformStatement).ToArray();
+ return body.WithStatements(SyntaxFactory.List(transformedStatements));
+ }
+
+ private static ArrowExpressionClauseSyntax TransformExpressionBody(ArrowExpressionClauseSyntax expressionBody)
+ {
+ var transformedExpression = TransformExpression(expressionBody.Expression, expressionBody.Expression.GetLeadingTrivia());
+ return expressionBody.WithExpression(transformedExpression);
+ }
+
+ private static StatementSyntax TransformStatement(StatementSyntax statement)
+ {
+ if (statement is ReturnStatementSyntax returnStatement && returnStatement.Expression != null)
+ {
+ var transformedExpression = TransformExpression(returnStatement.Expression,
+ new SyntaxTriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed).AddRange(returnStatement.GetLeadingTrivia()).Add(SyntaxFactory.ElasticTab));
+ return returnStatement.WithExpression(transformedExpression);
+ }
+ return statement;
+ }
+
+ private static ExpressionSyntax TransformExpression(ExpressionSyntax expression, SyntaxTriviaList leadingTrivia)
+ {
+ // Transform WebHost.CreateDefaultBuilder(args).ConfigureServices(...).UseStartup()
+ // to Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => webBuilder.ConfigureServices(...).UseStartup())
+
+ // Find the WebHost.CreateDefaultBuilder call and extract everything after it
+ var (webHostCreateCall, chainedCalls, remainingChain) = ExtractWebHostBuilderChain(expression);
+
+ if (webHostCreateCall != null && chainedCalls.Count > 0)
+ {
+ // Create Host.CreateDefaultBuilder
+ var hostCreateCall = SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName("Host"),
+ SyntaxFactory.IdentifierName("CreateDefaultBuilder")))
+ .WithArgumentList(webHostCreateCall.ArgumentList);
+
+ // Create the webBuilder expression chain
+ var webBuilderChain = CreateWebBuilderChain(chainedCalls, leadingTrivia);
+
+ // Create the ConfigureWebHostDefaults lambda with proper formatting
+ var lambda = SyntaxFactory.SimpleLambdaExpression(
+ SyntaxFactory.Parameter(SyntaxFactory.Identifier("webBuilder")),
+ webBuilderChain);
+
+ // Create Host.CreateDefaultBuilder().ConfigureWebHostDefaults(...)
+ var configureCall = SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ hostCreateCall.WithLeadingTrivia(leadingTrivia),
+ SyntaxFactory.IdentifierName("ConfigureWebHostDefaults"))
+ .WithOperatorToken(SyntaxFactory.Token(SyntaxKind.DotToken)
+ .WithLeadingTrivia(leadingTrivia)
+ ))
+ .WithArgumentList(SyntaxFactory.ArgumentList(
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.Argument(lambda))))
+ // Adds new line and indentation for remaining chain calls e.g. Build(), Run(), etc.
+ .WithTrailingTrivia(new SyntaxTriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed).AddRange(leadingTrivia));
+
+ // If there's a remaining chain (like .Build()), append it
+ ExpressionSyntax result = configureCall;
+ if (remainingChain != null)
+ {
+ var placeHolder = remainingChain.DescendantNodes().OfType()
+ .FirstOrDefault(n => n.Identifier.ValueText == "HOST_PLACEHOLDER");
+ if (placeHolder != null)
+ {
+ // Replace the placeholder with the actual configure call
+ result = remainingChain.ReplaceNode(placeHolder, configureCall);
+ }
+ }
+
+ return result;
+ }
+
+ // Handle standalone WebHost.CreateDefaultBuilder without chaining
+ if (expression is InvocationExpressionSyntax invocation &&
+ invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
+ memberAccess.Expression is IdentifierNameSyntax { Identifier.ValueText: "WebHost" } &&
+ memberAccess.Name.Identifier.ValueText == "CreateDefaultBuilder")
+ {
+ return SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName("Host"),
+ SyntaxFactory.IdentifierName("CreateDefaultBuilder")))
+ .WithArgumentList(invocation.ArgumentList)
+ .WithTrailingTrivia(invocation.GetTrailingTrivia())
+ .WithLeadingTrivia(invocation.GetLeadingTrivia());
+ }
+
+ return expression;
+ }
+
+ private static readonly HashSet WebHostBuilderMethods = new HashSet
+ {
+ "UseStartup",
+ "ConfigureServices",
+ "ConfigureKestrel",
+ "Configure",
+ "UseUrls",
+ "UseContentRoot",
+ "UseEnvironment",
+ "UseWebRoot",
+ "ConfigureLogging",
+ "ConfigureAppConfiguration",
+ "UseIISIntegration",
+ "UseIIS",
+ "UseKestrel",
+ "UseSockets",
+ "UseQuic",
+ "UseHttpSys",
+ "UseDefaultServiceProvider"
+ };
+
+ private static (InvocationExpressionSyntax? webHostCreateCall, List<(SimpleNameSyntax methodName, ArgumentListSyntax arguments, SyntaxTriviaList leadingTrivia)> chainedCalls, ExpressionSyntax? remainingChain) ExtractWebHostBuilderChain(ExpressionSyntax expression)
+ {
+ var chainedCalls = new List<(SimpleNameSyntax methodName, ArgumentListSyntax arguments, SyntaxTriviaList leadingTrivia)>();
+ InvocationExpressionSyntax? webHostCreateCall = null;
+ ExpressionSyntax? remainingChain = null;
+
+ // Walk the expression chain from the top level down to find all method calls
+ var currentExpr = expression;
+ var methodCalls = new Stack<(SimpleNameSyntax methodName, ArgumentListSyntax arguments, InvocationExpressionSyntax invocation)>();
+
+ // Traverse the chain by following invocation expressions
+ while (currentExpr is InvocationExpressionSyntax invocation)
+ {
+ if (invocation.Expression is MemberAccessExpressionSyntax memberAccess)
+ {
+ var methodName = memberAccess.Name; // This preserves generic arguments
+ methodCalls.Push((methodName, invocation.ArgumentList, invocation));
+
+ // Move to the next expression in the chain
+ currentExpr = memberAccess.Expression;
+ }
+ else
+ {
+ // This could be the WebHost.CreateDefaultBuilder call itself
+ break;
+ }
+ }
+
+ // Now process the stack to find WebHost.CreateDefaultBuilder and everything after it
+ bool foundWebHostCall = false;
+ var nonWebHostMethods = new List<(SimpleNameSyntax methodName, ArgumentListSyntax arguments, InvocationExpressionSyntax invocation)>();
+
+ while (methodCalls.Count > 0)
+ {
+ var (methodName, arguments, invocationExpr) = methodCalls.Pop();
+
+ if (!foundWebHostCall && methodName.Identifier.ValueText == "CreateDefaultBuilder")
+ {
+ // Check if this is WebHost.CreateDefaultBuilder
+ if (invocationExpr.Expression is MemberAccessExpressionSyntax memberAccess &&
+ memberAccess.Expression is IdentifierNameSyntax identifier &&
+ identifier.Identifier.ValueText == "WebHost")
+ {
+ webHostCreateCall = invocationExpr;
+ foundWebHostCall = true;
+ }
+ }
+ else if (foundWebHostCall)
+ {
+ // This is a method chained after WebHost.CreateDefaultBuilder
+ // Check if it's a WebHostBuilder method that should go inside ConfigureWebHostDefaults
+ if (WebHostBuilderMethods.Contains(methodName.Identifier.ValueText))
+ {
+ chainedCalls.Add((methodName, arguments, invocationExpr.GetLeadingTrivia()));
+ }
+ else
+ {
+ // This method should remain outside the lambda (like Build(), Run(), etc.)
+ nonWebHostMethods.Add((methodName, arguments, invocationExpr));
+ // Add any remaining methods to the non-WebHostBuilder list
+ nonWebHostMethods.AddRange(methodCalls);
+ methodCalls.Clear();
+ // Stop processing once we hit a non-WebHostBuilder method
+ break;
+ }
+ }
+ }
+
+ // Build the remaining chain from non-WebHostBuilder methods
+ if (nonWebHostMethods.Count > 0)
+ {
+ // Create a placeholder for the Host.CreateDefaultBuilder().ConfigureWebHostDefaults(...) call
+ // This will be replaced later, but we need something to chain the remaining methods to
+ var placeholder = SyntaxFactory.IdentifierName("HOST_PLACEHOLDER");
+
+ // Chain the remaining methods
+ ExpressionSyntax current = placeholder;
+ foreach (var (methodName, arguments, invocation) in nonWebHostMethods)
+ {
+ SyntaxTriviaList leadingTrivia = default;
+ if (invocation.Expression is MemberAccessExpressionSyntax memberAccessExpr)
+ {
+ leadingTrivia = memberAccessExpr.Expression.GetLeadingTrivia();
+ }
+
+ var memberAccess = SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ // Since we're appending method calls,
+ // we need to add trailing trivia after each one to affect the new method calls formatting
+ current.WithTrailingTrivia(current.GetTrailingTrivia().AddRange(leadingTrivia)),
+ methodName);
+
+ current = SyntaxFactory.InvocationExpression(memberAccess, arguments);
+ }
+
+ remainingChain = current;
+ }
+
+ return (webHostCreateCall, chainedCalls, remainingChain);
+ }
+
+ private static ExpressionSyntax CreateWebBuilderChain(List<(SimpleNameSyntax methodName, ArgumentListSyntax arguments, SyntaxTriviaList leadingTrivia)> chainedCalls, SyntaxTriviaList leadingTrivia2)
+ {
+ if (chainedCalls.Count == 0)
+ {
+ return SyntaxFactory.IdentifierName("webBuilder");
+ }
+
+ // Start with webBuilder
+ ExpressionSyntax current = SyntaxFactory.IdentifierName("webBuilder");
+
+ // Chain all the method calls with proper formatting
+ for (int i = 0; i < chainedCalls.Count; i++)
+ {
+ var (methodName, arguments, leadingTrivia) = chainedCalls[i];
+
+ var memberAccess = SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ current,
+ methodName); // Use the SimpleNameSyntax directly to preserve generics
+
+ current = SyntaxFactory.InvocationExpression(memberAccess, arguments);
+ current = current.WithTrailingTrivia();
+
+ if (i < chainedCalls.Count - 1)
+ {
+ // Add a line break and indentation for all but the last method call
+ var triviaList = new SyntaxTriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed).AddRange(leadingTrivia);
+ triviaList = triviaList.Add(SyntaxFactory.ElasticTab);
+ current = current.WithTrailingTrivia(triviaList);
+ }
+ }
+
+ return current;
+ }
+}
diff --git a/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/UseCreateHostBuilderInsteadOfCreateWebHostBuilderTest.cs b/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/UseCreateHostBuilderInsteadOfCreateWebHostBuilderTest.cs
new file mode 100644
index 000000000000..49166404d1cf
--- /dev/null
+++ b/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/UseCreateHostBuilderInsteadOfCreateWebHostBuilderTest.cs
@@ -0,0 +1,450 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis.Testing;
+using VerifyAnalyzer = Microsoft.AspNetCore.Analyzers.Verifiers.CSharpAnalyzerVerifier<
+ Microsoft.AspNetCore.Analyzers.WebApplicationBuilder.UseCreateHostBuilderInsteadOfCreateWebHostBuilderAnalyzer>;
+using VerifyCS = Microsoft.AspNetCore.Analyzers.Verifiers.CSharpCodeFixVerifier<
+ Microsoft.AspNetCore.Analyzers.WebApplicationBuilder.UseCreateHostBuilderInsteadOfCreateWebHostBuilderAnalyzer,
+ Microsoft.AspNetCore.Analyzers.WebApplicationBuilder.Fixers.UseCreateHostBuilderInsteadOfCreateWebHostBuilderFixer>;
+
+namespace Microsoft.AspNetCore.Analyzers.WebApplicationBuilder;
+
+public class UseCreateHostBuilderInsteadOfCreateWebHostBuilderTest
+{
+ [Fact]
+ public async Task DoesNotWarnWhenUsingHostCreateDefaultBuilder()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.Extensions.Hosting;
+using Microsoft.AspNetCore.Builder;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder => { });
+}
+";
+ // Assert
+ await VerifyCS.VerifyCodeFixAsync(source, source);
+ }
+
+ [Fact]
+ public async Task WarnsWhenUsingWebHostCreateDefaultBuilder()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static {|#0:IWebHostBuilder|} CreateWebHostBuilder(string[] args) =>
+ {|#1:WebHost.CreateDefaultBuilder(args)|}
+ .UseStartup();
+}
+public class Startup { }
+";
+
+ var fixedSource = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+using Microsoft.Extensions.Hosting;
+
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateWebHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup());
+}
+public class Startup { }
+";
+
+ var diagnostic = new DiagnosticResult(DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder)
+ .WithMessage(Resources.Analyzer_UseCreateHostBuilderInsteadOfCreateWebHostBuilder_Message);
+
+ var expectedDiagnostics = new[]
+ {
+ diagnostic.WithLocation(0),
+ diagnostic.WithLocation(1),
+ };
+
+ // Assert
+ await VerifyCS.VerifyCodeFixAsync(source, expectedDiagnostics, fixedSource);
+ }
+
+ [Fact]
+ public async Task WarnsWhenUsingCreateWebHostBuilderWithBlockBody()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static {|#0:IWebHostBuilder|} CreateWebHostBuilder(string[] args)
+ {
+ return {|#1:WebHost.CreateDefaultBuilder(args)|}.UseStartup();
+ }
+}
+public class Startup { }
+";
+
+ var fixedSource = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+using Microsoft.Extensions.Hosting;
+
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateWebHostBuilder(string[] args)
+ {
+ return Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup());
+ }
+}
+public class Startup { }
+";
+
+ var diagnostic = new DiagnosticResult(DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder)
+ .WithMessage(Resources.Analyzer_UseCreateHostBuilderInsteadOfCreateWebHostBuilder_Message);
+
+ var expectedDiagnostics = new[]
+ {
+ diagnostic.WithLocation(0),
+ diagnostic.WithLocation(1),
+ };
+
+ // Assert
+ await VerifyCS.VerifyCodeFixAsync(source, expectedDiagnostics, fixedSource);
+ }
+
+ [Fact]
+ public async Task WarnsOnlyForWebHostCreateDefaultBuilder()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static {|#0:IWebHostBuilder|} CreateWebHostBuilder(string[] args) =>
+ {|#1:WebHost.CreateDefaultBuilder(args)|}.UseStartup();
+
+ public static void StartWeb() =>
+ WebHost.Start(""http://localhost:5000"", (c) => { });
+}
+public class Startup { }
+";
+
+ var expectedDiagnostics = new[]
+ {
+ VerifyAnalyzer.Diagnostic().WithLocation(0),
+ VerifyAnalyzer.Diagnostic().WithLocation(1)
+ };
+
+ await VerifyAnalyzer.VerifyAnalyzerAsync(source, expectedDiagnostics);
+ }
+
+ [Fact]
+ public async Task WarnsForMultipleMethods()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static {|#0:IWebHostBuilder|} CreateWebHostBuilder(string[] args) =>
+ {|#1:WebHost.CreateDefaultBuilder(args)|}.UseStartup();
+
+ public static {|#2:IWebHostBuilder|} CreateWebHostBuilderForTesting(string[] args) =>
+ {|#3:WebHost.CreateDefaultBuilder(args)|}.UseStartup();
+}
+public class Startup { }
+";
+
+ var expectedDiagnostics = new[]
+ {
+ VerifyAnalyzer.Diagnostic().WithLocation(0),
+ VerifyAnalyzer.Diagnostic().WithLocation(1),
+ VerifyAnalyzer.Diagnostic().WithLocation(2),
+ VerifyAnalyzer.Diagnostic().WithLocation(3)
+ };
+
+ await VerifyAnalyzer.VerifyAnalyzerAsync(source, expectedDiagnostics);
+ }
+
+ [Fact]
+ public async Task WarnsForInstanceMethod()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public class Program
+{
+ public static void Main(string[] args)
+ {
+ }
+
+ public {|#0:IWebHostBuilder|} CreateWebHostBuilder(string[] args) =>
+ {|#1:WebHost.CreateDefaultBuilder(args)|}.UseStartup();
+}
+public class Startup { }
+";
+
+ var expectedDiagnostics = new[]
+ {
+ VerifyAnalyzer.Diagnostic().WithLocation(0),
+ VerifyAnalyzer.Diagnostic().WithLocation(1)
+ };
+
+ // No diagnostics expected
+ await VerifyAnalyzer.VerifyAnalyzerAsync(source, expectedDiagnostics);
+ }
+
+ [Fact]
+ public async Task WarnsForPrivateMethods()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ private static {|#0:IWebHostBuilder|} CreateWebHostBuilder(string[] args) =>
+ {|#1:WebHost.CreateDefaultBuilder(args)|}.UseStartup();
+}
+public class Startup { }
+";
+
+ var expectedDiagnostics = new[]
+ {
+ VerifyAnalyzer.Diagnostic().WithLocation(0),
+ VerifyAnalyzer.Diagnostic().WithLocation(1)
+ };
+
+ // No diagnostics expected
+ await VerifyAnalyzer.VerifyAnalyzerAsync(source, expectedDiagnostics);
+ }
+
+ [Fact]
+ public async Task CodeFixWorksInsideUsingStatement()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ using (var host = {|#0:WebHost.CreateDefaultBuilder(args)|}
+ .UseStartup()
+ .Build())
+ {
+ host.Run();
+ }
+ }
+}
+public class Startup { }
+";
+
+ var fixedSource = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+using Microsoft.Extensions.Hosting;
+
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ using (var host = Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup())
+ .Build())
+ {
+ host.Run();
+ }
+ }
+}
+public class Startup { }
+";
+
+ var diagnostic = new DiagnosticResult(DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder)
+ .WithMessage(Resources.Analyzer_UseCreateHostBuilderInsteadOfCreateWebHostBuilder_Message);
+
+ var expectedDiagnostics = new[]
+ {
+ diagnostic.WithLocation(0),
+ };
+
+ // Assert
+ await VerifyCS.VerifyCodeFixAsync(source, expectedDiagnostics, fixedSource);
+ }
+
+ [Fact]
+ public async Task FixerWorksWithMultipleNonWebHostChainedMethods()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ {|#0:WebHost.CreateDefaultBuilder(args)|}
+ .UseStartup()
+ .Build()
+ .RunAsync(default);
+ }
+}
+public class Startup { }
+";
+
+ var fixedSource = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+using Microsoft.Extensions.Hosting;
+
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup())
+ .Build()
+ .RunAsync(default);
+ }
+}
+public class Startup { }
+";
+
+ var diagnostic = new DiagnosticResult(DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder)
+ .WithMessage(Resources.Analyzer_UseCreateHostBuilderInsteadOfCreateWebHostBuilder_Message);
+
+ var expectedDiagnostics = new[]
+ {
+ diagnostic.WithLocation(0),
+ };
+
+ // Assert
+ await VerifyCS.VerifyCodeFixAsync(source, expectedDiagnostics, fixedSource);
+ }
+
+ [Fact]
+ public async Task CodeFixWorksWithManyChainedCalls()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ {|#0:WebHost.CreateDefaultBuilder(new[] { ""--cliKey"", ""cliValue"" })|}
+ .ConfigureServices((context, service) => { })
+ .ConfigureKestrel(options =>
+ options.Configure(options.ConfigurationLoader.Configuration))
+ .Configure(app =>
+ {
+ });
+ }
+}
+";
+
+ var fixedSource = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+using Microsoft.Extensions.Hosting;
+
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ Host.CreateDefaultBuilder(new[] { ""--cliKey"", ""cliValue"" })
+ .ConfigureWebHostDefaults(webBuilder => webBuilder.ConfigureServices((context, service) => { })
+ .ConfigureKestrel(options =>
+ options.Configure(options.ConfigurationLoader.Configuration))
+ .Configure(app =>
+ {
+ }));
+ }
+}
+";
+
+ var diagnostic = new DiagnosticResult(DiagnosticDescriptors.UseCreateHostBuilderInsteadOfCreateWebHostBuilder)
+ .WithMessage(Resources.Analyzer_UseCreateHostBuilderInsteadOfCreateWebHostBuilder_Message);
+
+ var expectedDiagnostics = new[]
+ {
+ diagnostic.WithLocation(0),
+ };
+
+ // Assert
+ await VerifyCS.VerifyCodeFixAsync(source, expectedDiagnostics, fixedSource);
+ }
+
+ [Fact]
+ public async Task DoesNotWarnForIWebHostBuilderMethodWithoutWebHostUsage()
+ {
+ // Arrange
+ var source = @"
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Builder;
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args) => null!;
+}
+";
+ // Assert
+ await VerifyCS.VerifyCodeFixAsync(source, source);
+ }
+}
diff --git a/src/Shared/RoslynUtils/WellKnownTypeData.cs b/src/Shared/RoslynUtils/WellKnownTypeData.cs
index 1afb045fc713..c2fa022bc37f 100644
--- a/src/Shared/RoslynUtils/WellKnownTypeData.cs
+++ b/src/Shared/RoslynUtils/WellKnownTypeData.cs
@@ -124,6 +124,8 @@ public enum WellKnownType
System_ComponentModel_DataAnnotations_RequiredAttribute,
System_ComponentModel_DataAnnotations_CustomValidationAttribute,
System_Type,
+ Microsoft_AspNetCore_Hosting_IWebHostBuilder,
+ Microsoft_AspNetCore_WebHost,
}
public static string[] WellKnownTypeNames =
@@ -245,5 +247,7 @@ public enum WellKnownType
"System.ComponentModel.DataAnnotations.RequiredAttribute",
"System.ComponentModel.DataAnnotations.CustomValidationAttribute",
"System.Type",
+ "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+ "Microsoft.AspNetCore.WebHost",
];
}