File: ValidationsGenerator.ClassAttributes.cs
Web Access
Project: src\src\Validation\test\Microsoft.Extensions.Validation.GeneratorTests\Microsoft.Extensions.Validation.GeneratorTests.csproj (Microsoft.Extensions.Validation.GeneratorTests)
#pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Validation;
 
namespace Microsoft.Extensions.Validation.GeneratorTests;
 
public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase
{
    [Fact]
    public async Task CanValidateValidationAttributesOnClasses()
    {
        var source = """
#pragma warning disable ASP0029
 
using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
 
var builder = WebApplication.CreateBuilder();
 
builder.Services.AddValidation();
 
var app = builder.Build();
 
app.Run();
 
[ValidatableType]
[SumLimit]
public class ComplexType : IPoint
{
    [Range(0, 15)]
    public int X { get; set; } = 10;
 
    [Range(0, 15)]
    public int Y { get; set; } = 10;
 
    public NestedType ObjectProperty { get; set; } = new NestedType();
}
 
// This class does not have any property-level validation attributes, but it has a class-level validation attribute.
// Therefore, its type info should still be emitted in the generator output.
[SumLimit]
public class NestedType : IPoint
{
    public int X { get; set; } = 10;
 
    public int Y { get; set; } = 10;
}
 
public interface IPoint
{
    int X { get; }
    int Y { get; }
}
 
public class SumLimitAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        if (value is IPoint point)
        {
            if (point.X + point.Y > 20)
            {
                return new ValidationResult($"Sum is too high");
            }
        }
        return ValidationResult.Success;
    }
}
""";
        await Verify(source, out var compilation);
        await VerifyValidatableType(compilation, "ComplexType", async (validationOptions, type) =>
        {
            Assert.True(validationOptions.TryGetValidatableTypeInfo(type, out var validatableTypeInfo));
 
            await InvalidPropertyAttributeCheck_ProducesError_AndShortCircuits(validatableTypeInfo);
            await ValidClassAttributeCheck_DoesNotProduceError(validatableTypeInfo);
            await InvalidClassAttributeCheck_ProducesError(validatableTypeInfo);
            await InvalidNestedClassAttributeCheck_ProducesError_AndShortCircuits(validatableTypeInfo);
 
            async Task InvalidPropertyAttributeCheck_ProducesError_AndShortCircuits(IValidatableInfo validatableInfo)
            {
                var instance = Activator.CreateInstance(type);
                type.GetProperty("X")?.SetValue(instance, 16);
                type.GetProperty("Y")?.SetValue(instance, 0);
 
                var context = new ValidateContext
                {
                    ValidationOptions = validationOptions,
                    ValidationContext = new ValidationContext(instance)
                };
 
                await validatableTypeInfo.ValidateAsync(instance, context, CancellationToken.None);
 
                Assert.NotNull(context.ValidationErrors);
                var propertyAttributeError = Assert.Single(context.ValidationErrors);
                Assert.Equal("X", propertyAttributeError.Key);
                Assert.Equal("The field X must be between 0 and 15.", propertyAttributeError.Value.Single());
            }
 
            async Task ValidClassAttributeCheck_DoesNotProduceError(IValidatableInfo validatableInfo)
            {
                var instance = Activator.CreateInstance(type);
 
                var context = new ValidateContext
                {
                    ValidationOptions = validationOptions,
                    ValidationContext = new ValidationContext(instance)
                };
 
                await validatableTypeInfo.ValidateAsync(instance, context, CancellationToken.None);
 
                Assert.Null(context.ValidationErrors);
            }
 
            async Task InvalidClassAttributeCheck_ProducesError(IValidatableInfo validatableInfo)
            {
                var instance = Activator.CreateInstance(type);
                type.GetProperty("X")?.SetValue(instance, 11);
                type.GetProperty("Y")?.SetValue(instance, 12);
 
                var context = new ValidateContext
                {
                    ValidationOptions = validationOptions,
                    ValidationContext = new ValidationContext(instance)
                };
 
                await validatableTypeInfo.ValidateAsync(instance, context, CancellationToken.None);
 
                Assert.NotNull(context.ValidationErrors);
                var classAttributeError = Assert.Single(context.ValidationErrors);
                Assert.Equal(string.Empty, classAttributeError.Key);
                Assert.Equal("Sum is too high", classAttributeError.Value.Single());
            }
 
            async Task InvalidNestedClassAttributeCheck_ProducesError_AndShortCircuits(IValidatableInfo validatableInfo)
            {
                var instance = Activator.CreateInstance(type);
                var objectPropertyInstance = type.GetProperty("ObjectProperty").GetValue(instance);
                objectPropertyInstance.GetType().GetProperty("X")?.SetValue(objectPropertyInstance, 11);
                objectPropertyInstance.GetType().GetProperty("Y")?.SetValue(objectPropertyInstance, 12);
 
                var context = new ValidateContext
                {
                    ValidationOptions = validationOptions,
                    ValidationContext = new ValidationContext(instance)
                };
 
                await validatableTypeInfo.ValidateAsync(instance, context, CancellationToken.None);
 
                Assert.NotNull(context.ValidationErrors);
                var classAttributeError = Assert.Single(context.ValidationErrors);
                Assert.Equal("ObjectProperty", classAttributeError.Key);
                Assert.Equal("Sum is too high", classAttributeError.Value.Single());
            }
        });
    }
}