Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Shared/RoslynUtils/WellKnownTypeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public enum WellKnownType
System_AttributeUsageAttribute,
System_Text_Json_Serialization_JsonDerivedTypeAttribute,
System_Text_Json_Serialization_JsonIgnoreAttribute,
System_Text_Json_Serialization_JsonPropertyNameAttribute,
System_ComponentModel_DataAnnotations_DisplayAttribute,
System_ComponentModel_DataAnnotations_ValidationAttribute,
System_ComponentModel_DataAnnotations_RequiredAttribute,
Expand Down Expand Up @@ -243,6 +244,7 @@ public enum WellKnownType
"System.AttributeUsageAttribute",
"System.Text.Json.Serialization.JsonDerivedTypeAttribute",
"System.Text.Json.Serialization.JsonIgnoreAttribute",
"System.Text.Json.Serialization.JsonPropertyNameAttribute",
"System.ComponentModel.DataAnnotations.DisplayAttribute",
"System.ComponentModel.DataAnnotations.ValidationAttribute",
"System.ComponentModel.DataAnnotations.RequiredAttribute",
Expand Down
31 changes: 28 additions & 3 deletions src/Validation/gen/Extensions/ISymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Validation;

internal static class ISymbolExtensions
{
public static string GetDisplayName(this ISymbol property, INamedTypeSymbol displayAttribute)
public static string? GetDisplayName(this ISymbol property, INamedTypeSymbol displayAttribute)
{
var displayNameAttribute = property.GetAttributes()
.FirstOrDefault(attribute =>
Expand All @@ -25,13 +25,38 @@ attribute.AttributeClass is { } attributeClass &&
{
if (string.Equals(namedArgument.Key, "Name", StringComparison.Ordinal))
{
return namedArgument.Value.Value?.ToString() ?? property.Name;
if (namedArgument.Value.Value?.ToString() is { } name)
{
return name;
}
}
}
}
}

return property.Name;
return null;
}

public static string? GetJsonPropertyName(this ISymbol property, INamedTypeSymbol nameAttribute)
{
var jsonPropertyNameAttribute = property.GetAttributes()
.FirstOrDefault(attribute =>
attribute.AttributeClass is { } attributeClass &&
SymbolEqualityComparer.Default.Equals(attributeClass, nameAttribute));

if (jsonPropertyNameAttribute is not null)
{
if (jsonPropertyNameAttribute.ConstructorArguments.Length is 1)
{
var arg = jsonPropertyNameAttribute.ConstructorArguments[0];
if (arg.Kind == TypedConstantKind.Primitive && arg.Value is string name)
{
return name;
}
}
}

return null;
}

public static bool IsEqualityContract(this IPropertySymbol prop, WellKnownTypes wellKnownTypes) =>
Expand Down
19 changes: 15 additions & 4 deletions src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.AspNetCore.Analyzers.Infrastructure;
using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -195,12 +194,19 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
ref validatableTypes,
ref visitedTypes);

var displayName =
parameter.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)) ??
correspondingProperty.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)) ??
parameter.GetJsonPropertyName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonPropertyNameAttribute)) ??
correspondingProperty.GetJsonPropertyName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonPropertyNameAttribute)) ??
parameter.Name ??
correspondingProperty.Name;

members.Add(new ValidatableProperty(
ContainingType: correspondingProperty.ContainingType,
Type: correspondingProperty.Type,
Name: correspondingProperty.Name,
DisplayName: parameter.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)) ??
correspondingProperty.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)),
DisplayName: displayName,
Attributes: []));
}
}
Expand Down Expand Up @@ -252,11 +258,16 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
continue;
}

var displayName =
member.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)) ??
member.GetJsonPropertyName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonPropertyNameAttribute)) ??
member.Name;

members.Add(new ValidatableProperty(
ContainingType: member.ContainingType,
Type: member.Type,
Name: member.Name,
DisplayName: member.GetDisplayName(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_ComponentModel_DataAnnotations_DisplayAttribute)),
DisplayName: displayName,
Attributes: attributes));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ public async Task CanValidateComplexTypesWithJsonIgnore()
using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateBuilder();

Expand All @@ -32,6 +32,7 @@ public async Task CanValidateComplexTypesWithJsonIgnore()

app.MapPost("/complex-type-with-json-ignore", (ComplexTypeWithJsonIgnore complexType) => Results.Ok("Passed"!));
app.MapPost("/record-type-with-json-ignore", (RecordTypeWithJsonIgnore recordType) => Results.Ok("Passed"!));
app.MapPost("/complex-type-with-json-property-name", (ComplexTypeWithJsonPropertyName complexType) => Results.Ok("Passed"!));

app.Run();

Expand Down Expand Up @@ -76,6 +77,21 @@ public record CircularReferenceRecord

public string Name { get; set; } = "test";
}

public class ComplexTypeWithJsonPropertyName
{
[Range(10, 100)]
public int DefaultPropertyName { get; set; } = 10;

[Range(10, 100)]
[JsonPropertyName("custom-property-name")]
public int CustomJsonPropertyName { get; set; } = 20;

[Display(Name = "display-name")]
[Range(10, 100)]
[JsonPropertyName("custom-property-name-with-display-name")]
public int CustomJsonPropertyNameWithDisplayName { get; set; } = 30;
}
""";
await Verify(source, out var compilation);
await VerifyEndpoint(compilation, "/complex-type-with-json-ignore", async (endpoint, serviceProvider) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public async Task CanValidateRecordTypes()
using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
Expand All @@ -31,6 +32,7 @@ public async Task CanValidateRecordTypes()
var app = builder.Build();

app.MapPost("/validatable-record", (ValidatableRecord validatableRecord) => Results.Ok("Passed"!));
app.MapPost("/validatable-record-with-json-property-name", (ValidatableRecordWithJsonPropertyNames validatableRecord) => Results.Ok("Passed"!));

app.Run();

Expand Down Expand Up @@ -95,7 +97,20 @@ public record ValidatableRecord(
[DerivedValidation, Range(10, 100)]
int PropertyWithMultipleAttributes = 10,
[FromServices] [Required] TestService ServiceProperty = null!, // This should be ignored because of [FromServices]
[FromKeyedServices("serviceKey")] [Range(10, 100)] int KeyedServiceProperty = 5 // This should be ignored because of [FromKeyedServices]
[FromKeyedServices("serviceKey")] [Range(10, 100)] int KeyedServiceProperty = 5, // This should be ignored because of [FromKeyedServices]
[Display(Name = "display-name")] [Range(10, 100)] int IntegerWithRangeAndDisplay = 10
);

public record ValidatableRecordWithJsonPropertyNames(
[Range(10, 100)]
int DefaultPropertyName = 10,
[Range(10, 100)]
[property: JsonPropertyName("custom-property-name")]
int CustomJsonPropertyName = 20,
[Display(Name = "display-name")]
[Range(10, 100)]
[property: JsonPropertyName("custom-property-name-with-display-name")]
int CustomJsonPropertyNameWithDisplayName = 30
);
""";
await Verify(source, out var compilation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,33 @@ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.
);
return true;
}
if (type == typeof(global::ComplexTypeWithJsonPropertyName))
{
validatableInfo = new GeneratedValidatableTypeInfo(
type: typeof(global::ComplexTypeWithJsonPropertyName),
members: [
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexTypeWithJsonPropertyName),
propertyType: typeof(int),
name: "DefaultPropertyName",
displayName: "DefaultPropertyName"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexTypeWithJsonPropertyName),
propertyType: typeof(int),
name: "CustomJsonPropertyName",
displayName: "custom-property-name"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexTypeWithJsonPropertyName),
propertyType: typeof(int),
name: "CustomJsonPropertyNameWithDisplayName",
displayName: "display-name"
),
]
);
return true;
}

return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,39 @@ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.
name: "PropertyWithMultipleAttributes",
displayName: "PropertyWithMultipleAttributes"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ValidatableRecord),
propertyType: typeof(int),
name: "IntegerWithRangeAndDisplay",
displayName: "display-name"
),
]
);
return true;
}
if (type == typeof(global::ValidatableRecordWithJsonPropertyNames))
{
validatableInfo = new GeneratedValidatableTypeInfo(
type: typeof(global::ValidatableRecordWithJsonPropertyNames),
members: [
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ValidatableRecordWithJsonPropertyNames),
propertyType: typeof(int),
name: "DefaultPropertyName",
displayName: "DefaultPropertyName"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ValidatableRecordWithJsonPropertyNames),
propertyType: typeof(int),
name: "CustomJsonPropertyName",
displayName: "custom-property-name"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ValidatableRecordWithJsonPropertyNames),
propertyType: typeof(int),
name: "CustomJsonPropertyNameWithDisplayName",
displayName: "display-name"
),
]
);
return true;
Expand Down
Loading