Ignore parameters that specify a model binder type
This commit is contained in:
parent
200a70bb86
commit
af770ede87
|
|
@ -31,6 +31,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||||
|
|
||||||
public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata";
|
public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata";
|
||||||
|
|
||||||
|
public const string IBinderTypeProviderMetadata = "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata";
|
||||||
|
|
||||||
public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult";
|
public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult";
|
||||||
|
|
||||||
public const string IConvertToActionResult = "Microsoft.AspNetCore.Mvc.IConvertToActionResult";
|
public const string IConvertToActionResult = "Microsoft.AspNetCore.Mvc.IConvertToActionResult";
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SpecifiesModelType(symbolCache, parameter))
|
||||||
|
{
|
||||||
|
// Ignore parameters that specify a model type.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var parameterName = GetName(symbolCache, parameter);
|
var parameterName = GetName(symbolCache, parameter);
|
||||||
|
|
||||||
var type = parameter.Type;
|
var type = parameter.Type;
|
||||||
|
|
@ -144,6 +150,39 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||||
return symbol.Name;
|
return symbol.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool SpecifiesModelType(in SymbolCache symbolCache, IParameterSymbol parameterSymbol)
|
||||||
|
{
|
||||||
|
foreach (var attribute in parameterSymbol.GetAttributes(symbolCache.IBinderTypeProviderMetadata))
|
||||||
|
{
|
||||||
|
// Look for a attribute property named BinderType being assigned. This would match
|
||||||
|
// [ModelBinder(BinderType = typeof(SomeBinder))]
|
||||||
|
for (var i = 0; i < attribute.NamedArguments.Length; i++)
|
||||||
|
{
|
||||||
|
var namedArgument = attribute.NamedArguments[i];
|
||||||
|
var namedArgumentValue = namedArgument.Value;
|
||||||
|
if (string.Equals(namedArgument.Key, "BinderType", StringComparison.Ordinal) &&
|
||||||
|
namedArgumentValue.Kind == TypedConstantKind.Type)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for the binder type being specified in the constructor. This would match
|
||||||
|
// [ModelBinder(typeof(SomeBinder))]
|
||||||
|
var constructorParameters = attribute.AttributeConstructor?.Parameters ?? ImmutableArray<IParameterSymbol>.Empty;
|
||||||
|
for (var i = 0; i < constructorParameters.Length; i++)
|
||||||
|
{
|
||||||
|
if (string.Equals(constructorParameters[i].Name, "binderType", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
// A constructor that requires binderType was used.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
internal readonly struct SymbolCache
|
internal readonly struct SymbolCache
|
||||||
{
|
{
|
||||||
public SymbolCache(Compilation compilation)
|
public SymbolCache(Compilation compilation)
|
||||||
|
|
@ -152,6 +191,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||||
ControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.ControllerAttribute);
|
ControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.ControllerAttribute);
|
||||||
FromBodyAttribute = compilation.GetTypeByMetadataName(SymbolNames.FromBodyAttribute);
|
FromBodyAttribute = compilation.GetTypeByMetadataName(SymbolNames.FromBodyAttribute);
|
||||||
IApiBehaviorMetadata = compilation.GetTypeByMetadataName(SymbolNames.IApiBehaviorMetadata);
|
IApiBehaviorMetadata = compilation.GetTypeByMetadataName(SymbolNames.IApiBehaviorMetadata);
|
||||||
|
IBinderTypeProviderMetadata = compilation.GetTypeByMetadataName(SymbolNames.IBinderTypeProviderMetadata);
|
||||||
IModelNameProvider = compilation.GetTypeByMetadataName(SymbolNames.IModelNameProvider);
|
IModelNameProvider = compilation.GetTypeByMetadataName(SymbolNames.IModelNameProvider);
|
||||||
NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute);
|
NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute);
|
||||||
NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute);
|
NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute);
|
||||||
|
|
@ -165,6 +205,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||||
public INamedTypeSymbol ControllerAttribute { get; }
|
public INamedTypeSymbol ControllerAttribute { get; }
|
||||||
public INamedTypeSymbol FromBodyAttribute { get; }
|
public INamedTypeSymbol FromBodyAttribute { get; }
|
||||||
public INamedTypeSymbol IApiBehaviorMetadata { get; }
|
public INamedTypeSymbol IApiBehaviorMetadata { get; }
|
||||||
|
public INamedTypeSymbol IBinderTypeProviderMetadata { get; }
|
||||||
public INamedTypeSymbol IModelNameProvider { get; }
|
public INamedTypeSymbol IModelNameProvider { get; }
|
||||||
public INamedTypeSymbol NonControllerAttribute { get; }
|
public INamedTypeSymbol NonControllerAttribute { get; }
|
||||||
public INamedTypeSymbol NonActionAttribute { get; }
|
public INamedTypeSymbol NonActionAttribute { get; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
|
||||||
|
{
|
||||||
|
public class IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder
|
||||||
|
{
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public void ActionMethod([ModelBinder(typeof(object))] IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder model) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
|
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
|
||||||
{
|
{
|
||||||
public class IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter
|
public class IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter
|
||||||
{
|
{
|
||||||
public string Model { get; set; }
|
public string Model { get; set; }
|
||||||
|
|
||||||
public void ActionMethod([FromRoute(Name = "id")] IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter model) { }
|
public void ActionMethod([FromRoute(Name = "id")] IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter model) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
|
||||||
|
{
|
||||||
|
public class IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty
|
||||||
|
{
|
||||||
|
[FromQuery(Name = "different")]
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public void ActionMethod(IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty model) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
|
|
||||||
{
|
|
||||||
public class IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty
|
|
||||||
{
|
|
||||||
[FromQuery(Name = "different")]
|
|
||||||
public string Model { get; set; }
|
|
||||||
|
|
||||||
public void ActionMethod([Bind(Prefix = nameof(Model))] IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty different) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
|
||||||
|
{
|
||||||
|
public class IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter
|
||||||
|
{
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public void ActionMethod([ModelBinder(Name = "model")] IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter different) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
|
||||||
|
{
|
||||||
|
public class SpecifiesModelTypeTests
|
||||||
|
{
|
||||||
|
public void SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType([ModelBinder(Name = "Name")] object model) { }
|
||||||
|
|
||||||
|
public void SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor([ModelBinder(typeof(object))] object model) { }
|
||||||
|
|
||||||
|
public void SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty([ModelBinder(BinderType = typeof(object))] object model) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -61,14 +61,21 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter()
|
public async Task IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter()
|
||||||
|
{
|
||||||
|
var result = await IsProblematicParameterTest();
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty()
|
||||||
{
|
{
|
||||||
var result = await IsProblematicParameterTest();
|
var result = await IsProblematicParameterTest();
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty()
|
public async Task IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter()
|
||||||
{
|
{
|
||||||
var result = await IsProblematicParameterTest();
|
var result = await IsProblematicParameterTest();
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
@ -81,6 +88,13 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder()
|
||||||
|
{
|
||||||
|
var result = await IsProblematicParameterTest();
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task IsProblematicParameter_IgnoresStaticProperties()
|
public async Task IsProblematicParameter_IgnoresStaticProperties()
|
||||||
{
|
{
|
||||||
|
|
@ -199,6 +213,60 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||||
return compilation;
|
return compilation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType()
|
||||||
|
{
|
||||||
|
var testMethod = nameof(SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType);
|
||||||
|
var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests");
|
||||||
|
var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });
|
||||||
|
|
||||||
|
var compilation = await project.GetCompilationAsync();
|
||||||
|
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
|
||||||
|
|
||||||
|
var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName);
|
||||||
|
var method = (IMethodSymbol)type.GetMembers(testMethod).First();
|
||||||
|
|
||||||
|
var parameter = method.Parameters[0];
|
||||||
|
var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter);
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor()
|
||||||
|
{
|
||||||
|
var testMethod = nameof(SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor);
|
||||||
|
var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests");
|
||||||
|
var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });
|
||||||
|
|
||||||
|
var compilation = await project.GetCompilationAsync();
|
||||||
|
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
|
||||||
|
|
||||||
|
var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName);
|
||||||
|
var method = (IMethodSymbol)type.GetMembers(testMethod).First();
|
||||||
|
|
||||||
|
var parameter = method.Parameters[0];
|
||||||
|
var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter);
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty()
|
||||||
|
{
|
||||||
|
var testMethod = nameof(SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty);
|
||||||
|
var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests");
|
||||||
|
var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });
|
||||||
|
|
||||||
|
var compilation = await project.GetCompilationAsync();
|
||||||
|
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
|
||||||
|
|
||||||
|
var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName);
|
||||||
|
var method = (IMethodSymbol)type.GetMembers(testMethod).First();
|
||||||
|
|
||||||
|
var parameter = method.Parameters[0];
|
||||||
|
var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter);
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "")
|
private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "")
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue