* Resolve #11727

- Add a BuildServiceProviderValidator for checking startup method 'ConfigureServices(IServiceCollection,..)'

* Fix file name and class name not match

* Resolve conversation:
- helpLink point to 'aka.ms'
- Update DiagnosticDescriptor title
- Update DiagnosticDescriptor messageFormat
This commit is contained in:
WuYafeng 2019-07-10 23:46:32 +08:00 committed by Ryan Nowak
parent 99e99c167a
commit d061d0d95b
5 changed files with 93 additions and 2 deletions

View File

@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.AspNetCore.Analyzers
{
internal class BuildServiceProviderValidator : StartupDiagnosticValidator
{
public static BuildServiceProviderValidator CreateAndInitialize(CompilationAnalysisContext context, ConcurrentBag<StartupComputedAnalysis> analyses)
{
if (analyses == null)
{
throw new ArgumentNullException(nameof(analyses));
}
var validator = new BuildServiceProviderValidator();
foreach (var serviceAnalysis in analyses.OfType<ServicesAnalysis>())
{
foreach (var serviceItem in serviceAnalysis.Services)
{
if (serviceItem.UseMethod.Name == "BuildServiceProvider")
{
context.ReportDiagnostic(Diagnostic.Create(
StartupAnalzyer.BuildServiceProviderShouldNotCalledInConfigureServicesMethod,
serviceItem.Operation.Syntax.GetLocation(),
serviceItem.UseMethod.Name,
serviceAnalysis.ConfigureServicesMethod.Name));
}
}
}
return validator;
}
}
}

View File

@ -16,5 +16,15 @@ namespace Microsoft.AspNetCore.Analyzers
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: "https://aka.ms/YJggeFn");
internal readonly static DiagnosticDescriptor BuildServiceProviderShouldNotCalledInConfigureServicesMethod = new DiagnosticDescriptor(
"MVC1007",
"Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices'",
"Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.",
"Design",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: "https://aka.ms/AA5k895"
);
}
}

View File

@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Analyzers
private readonly static Func<CompilationAnalysisContext, ConcurrentBag<StartupComputedAnalysis>, StartupDiagnosticValidator>[] DiagnosticValidatorFactories = new Func<CompilationAnalysisContext, ConcurrentBag<StartupComputedAnalysis>, StartupDiagnosticValidator>[]
{
UseMvcDiagnosticValidator.CreateAndInitialize,
BuildServiceProviderValidator.CreateAndInitialize
};
#pragma warning restore RS1008 // Avoid storing per-compilation data into the fields of a diagnostic analyzer.
@ -36,6 +37,7 @@ namespace Microsoft.AspNetCore.Analyzers
SupportedDiagnostics = ImmutableArray.Create<DiagnosticDescriptor>(new[]
{
UnsupportedUseMvcWithEndpointRouting,
BuildServiceProviderShouldNotCalledInConfigureServicesMethod
});
// By default the analyzer will only run for files ending with Startup.cs

View File

@ -68,11 +68,11 @@ namespace Microsoft.AspNetCore.Analyzers
Assert.Empty(diagnostics);
Assert.Collection(
ConfigureServicesMethods.OrderBy(m => m.Name),
ConfigureServicesMethods.OrderBy(m => m.Name),
m => Assert.Equal("ConfigureServices", m.Name));
Assert.Collection(
ConfigureMethods.OrderBy(m => m.Name),
ConfigureMethods.OrderBy(m => m.Name),
m => Assert.Equal("Configure", m.Name),
m => Assert.Equal("ConfigureProduction", m.Name));
}
@ -209,7 +209,25 @@ namespace Microsoft.AspNetCore.Analyzers
AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM3"], diagnostic.Location);
});
}
[Fact]
public async Task StartupAnalyzer_ServicesAnalysis_CallBuildServiceProvider()
{
// Arrange
var source = ReadSource("ConfigureServices_BuildServiceProvider");
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
var servicesAnalysis = Assert.Single(Analyses.OfType<ServicesAnalysis>());
Assert.NotEmpty(servicesAnalysis.Services);
Assert.Collection(diagnostics,
diagnostic =>
{
Assert.Same(StartupAnalzyer.BuildServiceProviderShouldNotCalledInConfigureServicesMethod, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location);
});
}
private TestSource ReadSource(string fileName)
{
return MvcTestSource.Read(nameof(StartupAnalyzerTest), fileName);

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class ConfigureServices_BuildServiceProvider
{
public void ConfigureServices(IServiceCollection services)
{
/*MM1*/services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app)
{
}
}
}