Skip to content

Warn that UseStartup/Configure/ConfigureWebHost not supported on WebApplicationBuilder #35884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

martincostello
Copy link
Member

@martincostello martincostello commented Aug 28, 2021

PR Title

Add WebApplicationBuilder analyzers for unsupported methods

PR Description

Add analyzers that warn on incorrect usage of UseStartup(), Configure() and ConfigureWebHost() when using WebApplicationBuilder.Host and WebApplicationBuilder.WebHost.

TODO

  • Review the exact diagnostic IDs to use
  • Review the exact diagnostic messages to use
  • Add tests for existing startup patterns prior to .NET 6 to verify they do not generate false positives
  • Refactor analyzer to reduce duplication in detecting similar usage patterns

Fixes #35814

@ghost ghost added area-infrastructure Includes: MSBuild projects/targets, build scripts, CI, Installers and shared framework community-contribution Indicates that the PR has been added by a community member labels Aug 28, 2021
@martincostello martincostello marked this pull request as ready for review August 28, 2021 14:03
Copy link
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great!

internal static readonly DiagnosticDescriptor DoNotUseConfigureWebHostWithConfigureHostBuilder = new(
"ASP0005",
"Do not use ConfigureWebHost with WebApplicationBuilder.Host",
"ConfigureWebHost cannot be used with WebApplicationBuilder.Host",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we say more about what the right thing to do would be? `Consider blah instead..."? @davidfowl what's the alternative?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I wasn't sure on the wording that would be wanted, so just went with something basic as a starting point.


public partial class WebApplicationBuilderAnalyzer : DiagnosticAnalyzer
{
private static void DisallowConfigureHostBuilderConfigureWebHost(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I'm super ok putting this in the same file as the anlayzer. The only reason I thought partials made sense for the minimal action one was because it had a bunch of different code to run per analysis.

IMethodSymbol methodSymbol,
INamedTypeSymbol disallowedReceiverType,
string disallowedMethodName,
params INamedTypeSymbol[] disallowedMethodTypes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to avoid the params since it allocates per call. We could statically cache the array

return false;
}

return disallowedMethodTypes.Any(type => SymbolEqualityComparer.Default.Equals(type, methodSymbol.ContainingType));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a regular for loop? We should treat all of the analyzers as if they were running in the hot path all the time.

public async Task WebApplicationBuilder_WebHostWithConfigureWithContext_ProducesDiagnostics()
{
// Arrange
var source = TestSource.Read(@"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it doesn't matter to the implementation, but could we add one test with producing diagnostics in a non-top level Program?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure - didn't think of that.

@davidfowl
Copy link
Member

I want to feature creep this PR as I realize there are more APIs I want to block but this is awesome

@martincostello
Copy link
Member Author

I want to feature creep this PR as I realize there are more APIs

😄

I'd be happy to add some more similar patterns as a follow-up post-merge if you've got a laundry list.

public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
/*MM*/builder.Host.ConfigureWebHost(webHostBuilder => { });
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the marker here supposed to be on the line start instead of at the problematic invocation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern I've been following is to emit the diagnostic at the point where the problematic invocation exists so if the user clicks on the diagnostic's location in the build output the cursor will be directed to the place where they need to make the fix.

It also makes it easier to write codefixes associated with the analyzer because you can get the syntax at the problematic location right away.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I figured that would be the case. I originally put it before ConfigureWebHost, but then the tests failed even though I was just using the syntax given to the analyzer to get the location for the invocation. I'll dig around more and see if I can work out why there's a mismatch when I do that so the warning is in the best place.

return;
}

INamedTypeSymbol[] configureTypes = { wellKnownTypes.WebHostBuilderExtensions };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't statically cache as the compilation is needed to get the well-known types, but I guess the lifetime of the class is "long enough" that these locals being reused in the lambda are OK?

@davidfowl
Copy link
Member

@pranavkm you are the gate here

@davidfowl
Copy link
Member

@captainsafia you as well.

@martincostello martincostello force-pushed the WebApplicationBuilder-Configure-Analyzers-35814 branch from 36a5bae to dedc5d7 Compare September 4, 2021 11:29
Comment on lines 127 to 132
var methodName = operation.Syntax
.DescendantNodes()
.OfType<SimpleNameSyntax>()
.Where(node => node is IdentifierNameSyntax || node is GenericNameSyntax)
.Where(node => string.Equals(node.Identifier.Value as string, operation.TargetMethod.Name, StringComparison.Ordinal))
.FirstOrDefault();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to refactor this if there's a better way I didn't work out for myself.

I assumed that using LINQ here was OK as we're already on the path where a diagnostic is going to be fired.

using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder();
builder.Host
.ConfigureServices(services => { }) // Because ConfigureServices() returns IHostBuilder, the type gets erased
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to remove this test if it doesn't add any value. It started as my check the analyzer did work, but then realising that as the receiver type on ConfigureWebHost() became IHostBuilder it could no longer be detected.

Alternatively I could try and make it actually fire, but that would mean walking up the invocation chain and trying to find the WebApplicationBuilder.Host at the root, but I'm not sure that's worth adding that additional complexity to try and cover so many possible usage patterns.

@martincostello martincostello force-pushed the WebApplicationBuilder-Configure-Analyzers-35814 branch from dedc5d7 to 358431d Compare September 21, 2021 10:56
}
}

// GetReceiverType() adapted from IOperationExtensions.GetReceiverType in dotnet/roslyn-analyzers.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We can move this to src/Shared/Roslyn.

@mkArtakMSFT mkArtakMSFT added old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels and removed area-infrastructure Includes: MSBuild projects/targets, build scripts, CI, Installers and shared framework labels Oct 6, 2021
Add analyzers that warn on incorrect usage of UseStartup(), Configure()
and ConfigureWebHost() when using WebApplicationBuilder.
Resolves dotnet#35814.
@martincostello martincostello force-pushed the WebApplicationBuilder-Configure-Analyzers-35814 branch from c2b5322 to 79f7251 Compare November 6, 2021 10:01
@pranavkm
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@captainsafia captainsafia merged commit bc064f5 into dotnet:main Jan 12, 2022
@ghost ghost added this to the 7.0-preview1 milestone Jan 12, 2022
@martincostello martincostello deleted the WebApplicationBuilder-Configure-Analyzers-35814 branch January 12, 2022 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community-contribution Indicates that the PR has been added by a community member old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Analyzer: Warn that UseStartup/Configure and ConfigureWebHost isn't supported on WebApplicationBuilder
6 participants