Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
14 changes: 14 additions & 0 deletions src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,18 @@ attr.AttributeClass is not null &&
(attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, fromKeyedServiceAttributeSymbol)));
}

/// <summary>
/// Checks if the property is marked with [FromService] or [FromKeyedService] attributes.
/// </summary>
/// <param name="property">The property to check.</param>
/// <param name="fromServiceMetadataSymbol">The symbol representing the [FromService] attribute.</param>
/// <param name="fromKeyedServiceAttributeSymbol">The symbol representing the [FromKeyedService] attribute.</param>
internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol)
{
return property.GetAttributes().Any(attr =>
attr.AttributeClass is not null &&
(attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, fromKeyedServiceAttributeSymbol)));
}
}
21 changes: 19 additions & 2 deletions src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
var members = new List<ValidatableProperty>();
var resolvedRecordProperty = new List<IPropertySymbol>();

var fromServiceMetadataSymbol = wellKnownTypes.Get(
WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata);
var fromKeyedServiceAttributeSymbol = wellKnownTypes.Get(
WellKnownTypeData.WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute);

// Special handling for record types to extract properties from
// the primary constructor.
if (typeSymbol is INamedTypeSymbol { IsRecord: true } namedType)
Expand Down Expand Up @@ -137,6 +142,12 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
{
resolvedRecordProperty.Add(correspondingProperty);

// Skip parameters that are injected as services
if (parameter.IsServiceParameter(fromServiceMetadataSymbol, fromKeyedServiceAttributeSymbol))
{
continue;
}

// Check if the property's type is validatable, this resolves
// validatable types in the inheritance hierarchy
var hasValidatableType = TryExtractValidatableType(
Expand All @@ -162,13 +173,19 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
{
// Skip compiler generated properties and properties already processed via
// the record processing logic above.
if (member.IsImplicitlyDeclared
|| member.IsEqualityContract(wellKnownTypes)
if (member.IsImplicitlyDeclared
|| member.IsEqualityContract(wellKnownTypes)
|| resolvedRecordProperty.Contains(member, SymbolEqualityComparer.Default))
{
continue;
}

// Skip properties that are injected as services
if (member.IsServiceProperty(fromServiceMetadataSymbol, fromKeyedServiceAttributeSymbol))
{
continue;
}

var hasValidatableType = TryExtractValidatableType(member.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes);
var attributes = ExtractValidationAttributes(member, wellKnownTypes, out var isRequired);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ public async Task CanValidateComplexTypes()
using Microsoft.Extensions.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder();

builder.Services.AddValidation();
builder.Services.AddSingleton<TestService>();

var app = builder.Build();

Expand Down Expand Up @@ -58,6 +60,10 @@ public class ComplexType

[DerivedValidation, Range(10, 100)]
public int PropertyWithMultipleAttributes { get; set; } = 10;

[FromServices]
[Required] // This should be ignored because of [FromServices]
public TestService ServiceProperty { get; set; } = null!;
}

public class DerivedValidationAttribute : ValidationAttribute
Expand Down Expand Up @@ -96,6 +102,12 @@ public static ValidationResult Validate(int number, ValidationContext validation
return ValidationResult.Success;
}
}

public class TestService
{
[Range(10, 100)]
public int Value { get; set; } = 4;
}
""";
await Verify(source, out var compilation);
await VerifyEndpoint(compilation, "/complex-type", async (endpoint, serviceProvider) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ public async Task CanValidateRecordTypes()
using Microsoft.Extensions.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder();

builder.Services.AddValidation();
builder.Services.AddSingleton<TestService>();

var app = builder.Build();

Expand All @@ -48,6 +50,10 @@ public record SubTypeWithoutConstructor

[StringLength(10)]
public string? StringWithLength { get; set; }

[FromServices]
[Required]
public TestService ServiceProperty { get; set; } = null!;
}

public static class CustomValidators
Expand All @@ -66,6 +72,12 @@ public static ValidationResult Validate(int number, ValidationContext validation
}
}

public class TestService
{
[Range(10, 100)]
public int Value { get; set; } = 4;
}

public record ValidatableRecord(
[Range(10, 100)]
int IntegerWithRange = 10,
Expand All @@ -81,7 +93,9 @@ public record ValidatableRecord(
[CustomValidation(typeof(CustomValidators), nameof(CustomValidators.Validate))]
int IntegerWithCustomValidation = 0,
[DerivedValidation, Range(10, 100)]
int PropertyWithMultipleAttributes = 10
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]
);
""";
await Verify(source, out var compilation);
Expand Down
Loading