Skip to content

Commit 2b6318a

Browse files
committed
Add support for handling FromRoute attribute with route parameter renaming.
1 parent 45fcfb7 commit 2b6318a

File tree

2 files changed

+93
-5
lines changed

2 files changed

+93
-5
lines changed

src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ private static void DisallowNonParsableComplexTypesOnParameters(
6464
location
6565
)) { continue; }
6666

67+
var routeParameterName = ResolveRouteParameterName(handlerDelegateParameter, wellKnownTypes);
68+
6769
// Match handler parameter against route parameters. If it is a route parameter it needs to be parsable/bindable in some fashion.
68-
if (routeUsage.RoutePattern.TryGetRouteParameter(handlerDelegateParameter.Name, out var routeParameter))
70+
if (routeUsage.RoutePattern.TryGetRouteParameter(routeParameterName, out var routeParameter))
6971
{
7072
var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes);
7173
var bindability = ParsabilityHelper.GetBindability(parameterTypeSymbol, wellKnownTypes);
@@ -77,7 +79,7 @@ private static void DisallowNonParsableComplexTypesOnParameters(
7779
context.ReportDiagnostic(Diagnostic.Create(
7880
descriptor,
7981
location,
80-
routeParameter.Name,
82+
handlerDelegateParameter.Name,
8183
parameterTypeSymbol.Name
8284
));
8385
}
@@ -86,10 +88,36 @@ private static void DisallowNonParsableComplexTypesOnParameters(
8688
}
8789
}
8890

89-
static bool HasAttributeImplementingFromMetadataInterfaceType(IParameterSymbol parameter, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes)
91+
static string ResolveRouteParameterName(IParameterSymbol parameterSymbol, WellKnownTypes wellKnownTypes)
92+
{
93+
if (!TryGetFromMetadataInterfaceTypeAttributeData(parameterSymbol, WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata, wellKnownTypes, out var attributeData))
94+
{
95+
return parameterSymbol.Name; // No route metadata attribute!
96+
}
97+
98+
foreach (var namedArgument in attributeData.NamedArguments)
99+
{
100+
if (namedArgument.Key == "Name")
101+
{
102+
var routeParameterNameConstant = namedArgument.Value;
103+
var routeParameterName = (string)routeParameterNameConstant.Value!;
104+
return routeParameterName; // Have attribute & name is specified.
105+
}
106+
}
107+
108+
return parameterSymbol.Name; // We have the attribute, but name isn't specified!
109+
}
110+
111+
static bool TryGetFromMetadataInterfaceTypeAttributeData(IParameterSymbol parameterSymbol, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellknowTypes, out AttributeData attributeData)
112+
{
113+
var fromMetadataInterfaceTypeSymbol = wellknowTypes.Get(fromMetadataInterfaceType);
114+
attributeData = parameterSymbol.GetAttributes().SingleOrDefault(ad => ad.AttributeClass.Implements(fromMetadataInterfaceTypeSymbol))!;
115+
return attributeData != null;
116+
}
117+
118+
static bool HasAttributeImplementingFromMetadataInterfaceType(IParameterSymbol parameterSymbol, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes)
90119
{
91-
var fromMetadataInterfaceTypeSymbol = wellKnownTypes.Get(fromMetadataInterfaceType);
92-
return parameter.GetAttributes().Any(ad => ad.AttributeClass.Implements(fromMetadataInterfaceTypeSymbol));
120+
return TryGetFromMetadataInterfaceTypeAttributeData(parameterSymbol, fromMetadataInterfaceType, wellKnownTypes, out var _);
93121
}
94122

95123
static bool ReportFromAttributeDiagnostic(OperationAnalysisContext context, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes, IParameterSymbol parameter, INamedTypeSymbol parameterTypeSymbol, Location location)

src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,66 @@ public class Customer
203203
await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic);
204204
}
205205

206+
[Fact]
207+
public async Task Route_Parameter_withNameChanged_viaFromRoute_whenNotParsable_Fails()
208+
{
209+
// Arrange
210+
var source = $$"""
211+
using Microsoft.AspNetCore.Builder;
212+
using Microsoft.AspNetCore.Mvc;
213+
var webApp = WebApplication.Create();
214+
webApp.MapGet("/customers/{cust}", ({|#0:[FromRoute(Name = "cust")]Customer customer|}) => {});
215+
216+
public class Customer
217+
{
218+
}
219+
""";
220+
221+
var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable)
222+
.WithArguments("customer", "Customer")
223+
.WithLocation(0);
224+
225+
// Act
226+
await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic);
227+
}
228+
229+
[Fact]
230+
public async Task Route_Parameter_withNameChanged_viaFromRoute_whenParsable_Works()
231+
{
232+
// Arrange
233+
var source = $$"""
234+
using System;
235+
using Microsoft.AspNetCore.Builder;
236+
using Microsoft.AspNetCore.Mvc;
237+
var webApp = WebApplication.Create();
238+
webApp.MapGet("/customers/{cust}", ({|#0:[FromRoute(Name = "cust")]Customer customer|}) => {});
239+
240+
public class Customer : IParsable<Customer>
241+
{
242+
public static Customer Parse(string s, IFormatProvider provider)
243+
{
244+
if (TryParse(s, provider, out Customer customer))
245+
{
246+
return customer;
247+
}
248+
else
249+
{
250+
throw new ArgumentException(s);
251+
}
252+
}
253+
254+
public static bool TryParse(string s, IFormatProvider provider, out Customer result)
255+
{
256+
result = new Customer();
257+
return true;
258+
}
259+
}
260+
""";
261+
262+
// Act
263+
await VerifyCS.VerifyAnalyzerAsync(source);
264+
}
265+
206266
[Fact]
207267
public async Task Route_Parameter_withBindAsyncMethodThatReturnsTask_of_T_Fails()
208268
{

0 commit comments

Comments
 (0)