File: ValidatableTypesBenchmark.cs
Web Access
Project: src\src\Http\Http\perf\Microbenchmarks\Microsoft.AspNetCore.Http.Microbenchmarks.csproj (Microsoft.AspNetCore.Http.Microbenchmarks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http.Validation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
 
namespace Microsoft.AspNetCore.Http.Microbenchmarks;
 
public class ValidatableTypeInfoBenchmark
{
    private IValidatableInfo _simpleTypeInfo = null!;
    private IValidatableInfo _complexTypeInfo = null!;
    private IValidatableInfo _hierarchicalTypeInfo = null!;
    private IValidatableInfo _ivalidatableObjectTypeInfo = null!;
 
    private ValidateContext _context = null!;
    private SimpleModel _simpleModel = null!;
    private ComplexModel _complexModel = null!;
    private HierarchicalModel _hierarchicalModel = null!;
    private ValidatableObjectModel _validatableObjectModel = null!;
 
    [GlobalSetup]
    public void Setup()
    {
        var services = new ServiceCollection();
        var mockResolver = new MockValidatableTypeInfoResolver();
 
        services.AddValidation(options =>
        {
            // Register our mock resolver
            options.Resolvers.Insert(0, mockResolver);
        });
 
        var serviceProvider = services.BuildServiceProvider();
        var validationOptions = serviceProvider.GetRequiredService<IOptions<ValidationOptions>>().Value;
 
        _context = new ValidateContext
        {
            ValidationOptions = validationOptions,
            ValidationContext = new ValidationContext(new object(), serviceProvider, null),
            ValidationErrors = new Dictionary<string, string[]>(StringComparer.Ordinal)
        };
 
        // Create the model instances
        _simpleModel = new SimpleModel
        {
            Id = 1,
            Name = "Test Name",
            Email = "test@example.com"
        };
 
        _complexModel = new ComplexModel
        {
            Id = 1,
            Name = "Complex Model",
            Properties = new Dictionary<string, string>
            {
                ["Prop1"] = "Value1",
                ["Prop2"] = "Value2"
            },
            Items = ["Item1", "Item2", "Item3"],
            CreatedOn = DateTime.UtcNow
        };
 
        _hierarchicalModel = new HierarchicalModel
        {
            Id = 1,
            Name = "Parent Model",
            Child = new ChildModel
            {
                Id = 2,
                Name = "Child Model",
                ParentId = 1
            },
            Siblings =
            [
                new SimpleModel { Id = 3, Name = "Sibling 1", Email = "sibling1@example.com" },
                new SimpleModel { Id = 4, Name = "Sibling 2", Email = "sibling2@example.com" }
            ]
        };
 
        _validatableObjectModel = new ValidatableObjectModel
        {
            Id = 1,
            Name = "Validatable Model",
            CustomField = "Valid Value"
        };
 
        // Get the type info instances from validation options using the mock resolver
        validationOptions.TryGetValidatableTypeInfo(typeof(SimpleModel), out _simpleTypeInfo);
        validationOptions.TryGetValidatableTypeInfo(typeof(ComplexModel), out _complexTypeInfo);
        validationOptions.TryGetValidatableTypeInfo(typeof(HierarchicalModel), out _hierarchicalTypeInfo);
        validationOptions.TryGetValidatableTypeInfo(typeof(ValidatableObjectModel), out _ivalidatableObjectTypeInfo);
 
        // Ensure we have all type infos (this should not be needed with our mock resolver)
        if (_simpleTypeInfo == null || _complexTypeInfo == null ||
            _hierarchicalTypeInfo == null || _ivalidatableObjectTypeInfo == null)
        {
            throw new InvalidOperationException("Failed to register one or more type infos with mock resolver");
        }
    }
 
    [Benchmark(Description = "Validate Simple Model")]
    [BenchmarkCategory("Simple")]
    public async Task ValidateSimpleModel()
    {
        _context.ValidationErrors.Clear();
        await _simpleTypeInfo.ValidateAsync(_simpleModel, _context, default);
    }
 
    [Benchmark(Description = "Validate Complex Model")]
    [BenchmarkCategory("Complex")]
    public async Task ValidateComplexModel()
    {
        _context.ValidationErrors.Clear();
        await _complexTypeInfo.ValidateAsync(_complexModel, _context, default);
    }
 
    [Benchmark(Description = "Validate Hierarchical Model")]
    [BenchmarkCategory("Hierarchical")]
    public async Task ValidateHierarchicalModel()
    {
        _context.ValidationErrors.Clear();
        await _hierarchicalTypeInfo.ValidateAsync(_hierarchicalModel, _context, default);
    }
 
    [Benchmark(Description = "Validate IValidatableObject Model")]
    [BenchmarkCategory("IValidatableObject")]
    public async Task ValidateIValidatableObjectModel()
    {
        _context.ValidationErrors.Clear();
        await _ivalidatableObjectTypeInfo.ValidateAsync(_validatableObjectModel, _context, default);
    }
 
    [Benchmark(Description = "Validate invalid Simple Model")]
    [BenchmarkCategory("Invalid")]
    public async Task ValidateInvalidSimpleModel()
    {
        _context.ValidationErrors.Clear();
        _simpleModel.Email = "invalid-email";
        await _simpleTypeInfo.ValidateAsync(_simpleModel, _context, default);
    }
 
    [Benchmark(Description = "Validate invalid IValidatableObject Model")]
    [BenchmarkCategory("Invalid")]
    public async Task ValidateInvalidIValidatableObjectModel()
    {
        _context.ValidationErrors.Clear();
        _validatableObjectModel.CustomField = "Invalid";
        await _ivalidatableObjectTypeInfo.ValidateAsync(_validatableObjectModel, _context, default);
    }
 
    #region Helper methods to create type info instances manually if needed
 
    private ValidatablePropertyInfo CreatePropertyInfo(string name, Type type, params ValidationAttribute[] attributes)
    {
        return new MockValidatablePropertyInfo(
            typeof(SimpleModel),
            type,
            name,
            name,
            attributes);
    }
 
    #endregion
 
    #region Test Models
 
    public class SimpleModel
    {
        public int Id { get; set; }
 
        [Required]
        public string Name { get; set; }
 
        [EmailAddress]
        public string Email { get; set; }
    }
 
    public class ComplexModel
    {
        public int Id { get; set; }
 
        [Required]
        public string Name { get; set; }
 
        public Dictionary<string, string> Properties { get; set; }
 
        public List<string> Items { get; set; }
 
        public DateTime CreatedOn { get; set; }
    }
 
    public class ChildModel
    {
        public int Id { get; set; }
 
        [Required]
        public string Name { get; set; }
 
        public int ParentId { get; set; }
    }
 
    public class HierarchicalModel
    {
        public int Id { get; set; }
 
        [Required]
        public string Name { get; set; }
 
        public ChildModel Child { get; set; }
 
        public List<SimpleModel> Siblings { get; set; }
    }
 
    public class ValidatableObjectModel : IValidatableObject
    {
        public int Id { get; set; }
 
        [Required]
        public string Name { get; set; }
 
        public string CustomField { get; set; }
 
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (CustomField == "Invalid")
            {
                yield return new ValidationResult("CustomField has an invalid value", new[] { nameof(CustomField) });
            }
        }
    }
 
    #endregion
 
    #region Mock Implementations for Testing
 
    private class MockValidatableTypeInfo(Type type, ValidatablePropertyInfo[] members) : ValidatableTypeInfo(type, members)
    {
    }
 
    private class MockValidatablePropertyInfo(
        Type containingType,
        Type propertyType,
        string name,
        string displayName,
        ValidationAttribute[] validationAttributes) : ValidatablePropertyInfo(containingType, propertyType, name, displayName)
    {
        private readonly ValidationAttribute[] _validationAttributes = validationAttributes;
 
        protected override ValidationAttribute[] GetValidationAttributes() => _validationAttributes;
    }
 
    #endregion
 
    #region Mock Resolver Implementation
 
    private class MockValidatableTypeInfoResolver : IValidatableInfoResolver
    {
        private readonly Dictionary<Type, ValidatableTypeInfo> _typeInfoCache = [];
 
        public MockValidatableTypeInfoResolver()
        {
            // Initialize the cache with our test models
            _typeInfoCache[typeof(SimpleModel)] = CreateSimpleModelTypeInfo();
            _typeInfoCache[typeof(ComplexModel)] = CreateComplexModelTypeInfo();
            _typeInfoCache[typeof(HierarchicalModel)] = CreateHierarchicalModelTypeInfo();
            _typeInfoCache[typeof(ValidatableObjectModel)] = CreateValidatableObjectModelTypeInfo();
 
            // Add child models that might be validated separately
            _typeInfoCache[typeof(ChildModel)] = CreateChildModelTypeInfo();
        }
 
        private ValidatableTypeInfo CreateSimpleModelTypeInfo()
        {
            return new MockValidatableTypeInfo(
                typeof(SimpleModel),
                [
                    CreatePropertyInfo(typeof(SimpleModel), "Id", typeof(int)),
                    CreatePropertyInfo(typeof(SimpleModel), "Name", typeof(string)),
                    CreatePropertyInfo(typeof(SimpleModel), "Email", typeof(string), new EmailAddressAttribute())
                ]);
        }
 
        private ValidatableTypeInfo CreateComplexModelTypeInfo()
        {
            return new MockValidatableTypeInfo(
                typeof(ComplexModel),
                [
                    CreatePropertyInfo(typeof(ComplexModel), "Id", typeof(int)),
                    CreatePropertyInfo(typeof(ComplexModel), "Name", typeof(string)),
                    CreatePropertyInfo(typeof(ComplexModel), "Properties", typeof(Dictionary<string, string>)),
                    CreatePropertyInfo(typeof(ComplexModel), "Items", typeof(List<string>)),
                    CreatePropertyInfo(typeof(ComplexModel), "CreatedOn", typeof(DateTime))
                ]);
        }
 
        private ValidatableTypeInfo CreateChildModelTypeInfo()
        {
            return new MockValidatableTypeInfo(
                typeof(ChildModel),
                [
                    CreatePropertyInfo(typeof(ChildModel), "Id", typeof(int)),
                    CreatePropertyInfo(typeof(ChildModel), "Name", typeof(string)),
                    CreatePropertyInfo(typeof(ChildModel), "ParentId", typeof(int))
                ]);
        }
 
        private ValidatableTypeInfo CreateHierarchicalModelTypeInfo()
        {
            return new MockValidatableTypeInfo(
                typeof(HierarchicalModel),
                [
                    CreatePropertyInfo(typeof(HierarchicalModel), "Id", typeof(int)),
                    CreatePropertyInfo(typeof(HierarchicalModel), "Name", typeof(string)),
                    CreatePropertyInfo(typeof(HierarchicalModel), "Child", typeof(ChildModel)),
                    CreatePropertyInfo(typeof(HierarchicalModel), "Siblings", typeof(List<SimpleModel>))
                ]);
        }
 
        private ValidatableTypeInfo CreateValidatableObjectModelTypeInfo()
        {
            return new MockValidatableTypeInfo(
                typeof(ValidatableObjectModel),
                [
                    CreatePropertyInfo(typeof(ValidatableObjectModel), "Id", typeof(int)),
                    CreatePropertyInfo(typeof(ValidatableObjectModel), "Name", typeof(string)),
                    CreatePropertyInfo(typeof(ValidatableObjectModel), "CustomField", typeof(string))
                ]);
        }
 
        private ValidatablePropertyInfo CreatePropertyInfo(Type containingType, string name, Type type, params ValidationAttribute[] attributes)
        {
            return new MockValidatablePropertyInfo(
                containingType,
                type,
                name,
                name, // Use name as display name
                attributes);
        }
 
        public bool TryGetValidatableTypeInfo(Type type, out IValidatableInfo validatableInfo)
        {
            if (_typeInfoCache.TryGetValue(type, out var typeInfo))
            {
                validatableInfo = typeInfo;
                return true;
            }
            validatableInfo = null;
            return false;
        }
 
        public bool TryGetValidatableParameterInfo(ParameterInfo parameterInfo, out IValidatableInfo validatableInfo)
        {
            validatableInfo = null;
            return false;
        }
    }
    #endregion
}