Skip to content

Commit 6bb9f11

Browse files
[Validation] Use [JsonPropertName] for display
Use `[JsonPropertyName("{value}")]` for the display name, if `[Display(Name = "{value}")` is not specified, instead of the property's name. Contributes to #63290.
1 parent 89bd338 commit 6bb9f11

File tree

5 files changed

+77
-7
lines changed

5 files changed

+77
-7
lines changed

src/Shared/RoslynUtils/WellKnownTypeData.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public enum WellKnownType
120120
System_AttributeUsageAttribute,
121121
System_Text_Json_Serialization_JsonDerivedTypeAttribute,
122122
System_Text_Json_Serialization_JsonIgnoreAttribute,
123+
System_Text_Json_Serialization_JsonPropertyNameAttribute,
123124
System_ComponentModel_DataAnnotations_DisplayAttribute,
124125
System_ComponentModel_DataAnnotations_ValidationAttribute,
125126
System_ComponentModel_DataAnnotations_RequiredAttribute,
@@ -243,6 +244,7 @@ public enum WellKnownType
243244
"System.AttributeUsageAttribute",
244245
"System.Text.Json.Serialization.JsonDerivedTypeAttribute",
245246
"System.Text.Json.Serialization.JsonIgnoreAttribute",
247+
"System.Text.Json.Serialization.JsonPropertyNameAttribute",
246248
"System.ComponentModel.DataAnnotations.DisplayAttribute",
247249
"System.ComponentModel.DataAnnotations.ValidationAttribute",
248250
"System.ComponentModel.DataAnnotations.RequiredAttribute",

src/Validation/gen/Extensions/ISymbolExtensions.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Validation;
1010

1111
internal static class ISymbolExtensions
1212
{
13-
public static string GetDisplayName(this ISymbol property, INamedTypeSymbol displayAttribute)
13+
public static string? GetDisplayName(this ISymbol property, INamedTypeSymbol displayAttribute)
1414
{
1515
var displayNameAttribute = property.GetAttributes()
1616
.FirstOrDefault(attribute =>
@@ -19,19 +19,33 @@ attribute.AttributeClass is { } attributeClass &&
1919

2020
if (displayNameAttribute is not null)
2121
{
22+
// For the [Foo(Name = "bar")] case
2223
if (!displayNameAttribute.NamedArguments.IsDefaultOrEmpty)
2324
{
2425
foreach (var namedArgument in displayNameAttribute.NamedArguments)
2526
{
2627
if (string.Equals(namedArgument.Key, "Name", StringComparison.Ordinal))
2728
{
28-
return namedArgument.Value.Value?.ToString() ?? property.Name;
29+
if (namedArgument.Value.Value?.ToString() is { } name)
30+
{
31+
return name;
32+
}
2933
}
3034
}
3135
}
36+
37+
// For the [Foo("bar")] case
38+
if (displayNameAttribute.ConstructorArguments.Length is 1)
39+
{
40+
var arg = displayNameAttribute.ConstructorArguments[0];
41+
if (arg.Kind == TypedConstantKind.Primitive && arg.Value is string name)
42+
{
43+
return name;
44+
}
45+
}
3246
}
3347

34-
return property.Name;
48+
return null;
3549
}
3650

3751
public static bool IsEqualityContract(this IPropertySymbol prop, WellKnownTypes wellKnownTypes) =>

src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Immutable;
66
using System.Linq;
77
using Microsoft.AspNetCore.Analyzers.Infrastructure;
8-
using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
98
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
109
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel;
1110
using Microsoft.CodeAnalysis;
@@ -195,12 +194,19 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
195194
ref validatableTypes,
196195
ref visitedTypes);
197196

197+
var displayName =
198+
parameter.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)) ??
199+
parameter.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonPropertyNameAttribute)) ??
200+
correspondingProperty.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)) ??
201+
correspondingProperty.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonPropertyNameAttribute)) ??
202+
parameter.Name ??
203+
correspondingProperty.Name;
204+
198205
members.Add(new ValidatableProperty(
199206
ContainingType: correspondingProperty.ContainingType,
200207
Type: correspondingProperty.Type,
201208
Name: correspondingProperty.Name,
202-
DisplayName: parameter.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)) ??
203-
correspondingProperty.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)),
209+
DisplayName: displayName,
204210
Attributes: []));
205211
}
206212
}
@@ -252,11 +258,16 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
252258
continue;
253259
}
254260

261+
var displayName =
262+
member.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)) ??
263+
member.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonPropertyNameAttribute)) ??
264+
member.Name;
265+
255266
members.Add(new ValidatableProperty(
256267
ContainingType: member.ContainingType,
257268
Type: member.Type,
258269
Name: member.Name,
259-
DisplayName: member.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)),
270+
DisplayName: displayName,
260271
Attributes: attributes));
261272
}
262273

src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public async Task CanValidateComplexTypesWithJsonIgnore()
3232
3333
app.MapPost("/complex-type-with-json-ignore", (ComplexTypeWithJsonIgnore complexType) => Results.Ok("Passed"!));
3434
app.MapPost("/record-type-with-json-ignore", (RecordTypeWithJsonIgnore recordType) => Results.Ok("Passed"!));
35+
app.MapPost("/complex-type-with-json-property-name", (ComplexTypeWithJsonPropertyName complexType) => Results.Ok("Passed"!));
3536
3637
app.Run();
3738
@@ -76,6 +77,21 @@ public record CircularReferenceRecord
7677
7778
public string Name { get; set; } = "test";
7879
}
80+
81+
public class ComplexTypeWithJsonPropertyName
82+
{
83+
[Range(10, 100)]
84+
public int DefaultPropertyName { get; set; } = 10;
85+
86+
[Range(10, 100)]
87+
[JsonPropertyName("custom-property-name")]
88+
public int CustomJsonPropertyName { get; set; } = 20;
89+
90+
[Display(Name = "display-name")]
91+
[Range(10, 100)]
92+
[JsonPropertyName("custom-property-name-with-display-name")]
93+
public int CustomJsonPropertyNameWithDisplayName { get; set; } = 30;
94+
}
7995
""";
8096
await Verify(source, out var compilation);
8197
await VerifyEndpoint(compilation, "/complex-type-with-json-ignore", async (endpoint, serviceProvider) =>

src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,33 @@ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.
101101
);
102102
return true;
103103
}
104+
if (type == typeof(global::ComplexTypeWithJsonPropertyName))
105+
{
106+
validatableInfo = new GeneratedValidatableTypeInfo(
107+
type: typeof(global::ComplexTypeWithJsonPropertyName),
108+
members: [
109+
new GeneratedValidatablePropertyInfo(
110+
containingType: typeof(global::ComplexTypeWithJsonPropertyName),
111+
propertyType: typeof(int),
112+
name: "DefaultPropertyName",
113+
displayName: "DefaultPropertyName"
114+
),
115+
new GeneratedValidatablePropertyInfo(
116+
containingType: typeof(global::ComplexTypeWithJsonPropertyName),
117+
propertyType: typeof(int),
118+
name: "CustomJsonPropertyName",
119+
displayName: "custom-property-name"
120+
),
121+
new GeneratedValidatablePropertyInfo(
122+
containingType: typeof(global::ComplexTypeWithJsonPropertyName),
123+
propertyType: typeof(int),
124+
name: "CustomJsonPropertyNameWithDisplayName",
125+
displayName: "display-name"
126+
),
127+
]
128+
);
129+
return true;
130+
}
104131

105132
return false;
106133
}

0 commit comments

Comments
 (0)