Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 eng/RequiresDelayedBuildProjects.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@
<RequiresDelayedBuild Include="$(RepoRoot)src\ProjectTemplates\test\Templates.Tests\Templates.Tests.csproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\SignalR\server\SignalR\test\Microsoft.AspNetCore.SignalR.TrimmingTests\Microsoft.AspNetCore.SignalR.TrimmingTests.proj" />
<RequiresDelayedBuild Include="$(RepoRoot)eng\Npm.Workspace.FunctionalTests.nodeproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\Validation\test\Microsoft.Extensions.Validation.NativeAotTests\Microsoft.Extensions.Validation.NativeAotTests.proj"/>
<RequiresDelayedBuild Include="$(RepoRoot)src\Validation\test\Microsoft.Extensions.Validation.TrimmingTests\Microsoft.Extensions.Validation.TrimmingTests.proj"/>
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions eng/testing/linker/SupportFiles/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
<ProjectReference Include="$(RepoRoot)src\SignalR\clients\csharp\Client\src\Microsoft.AspNetCore.SignalR.Client.csproj" />
<ProjectReference Include="$(RepoRoot)src\SignalR\server\SignalR\src\Microsoft.AspNetCore.SignalR.csproj" />
<ProjectReference Include="$(RepoRoot)src\OpenApi\src\Microsoft.AspNetCore.OpenApi.csproj" />
<ProjectReference Include="$(RepoRoot)src\Validation\src\Microsoft.Extensions.Validation.csproj" />
<ProjectReference Include="$(RepoRoot)src\Validation\gen\Microsoft.Extensions.Validation.ValidationsGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(RepoRoot)src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.RequestDelegateGenerator\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion eng/testing/linker/project.csproj.template
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<!-- Workaround while there is no SDK available that understands the TFM; suppress unsupported version errors. -->
<NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
<_ExtraTrimmerArgs>{ExtraTrimmerArgs} $(_ExtraTrimmerArgs)</_ExtraTrimmerArgs>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.AspNetCore.Http.Generated</InterceptorsPreviewNamespaces>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.AspNetCore.Http.Generated;Microsoft.Extensions.Validation.Generated</InterceptorsPreviewNamespaces>
<!-- Ensure individual warnings are shown when publishing -->
<TrimmerSingleWarn>false</TrimmerSingleWarn>
{AdditionalProperties}
Expand Down
12 changes: 8 additions & 4 deletions src/Validation/gen/Emitters/ValidationsGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ namespace Microsoft.Extensions.Validation.Generated
file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo
{
public GeneratedValidatablePropertyInfo(
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)]
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
global::System.Type containingType,
global::System.Type propertyType,
string name,
Expand All @@ -67,7 +67,7 @@ public GeneratedValidatablePropertyInfo(
Name = name;
}

[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)]
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
internal global::System.Type ContainingType { get; }
internal string Name { get; }

Expand Down Expand Up @@ -123,11 +123,15 @@ file static class GeneratedServiceCollectionExtensions
{{GeneratedCodeAttribute}}
file static class ValidationAttributeCache
{
private sealed record CacheKey([property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] global::System.Type ContainingType, string PropertyName);
private sealed record CacheKey(
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
[property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
global::System.Type ContainingType,
string PropertyName);
private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<CacheKey, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> _cache = new();

public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes(
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)]
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
global::System.Type containingType,
string propertyName)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
//HintName: ValidatableInfoResolver.g.cs
#nullable enable annotations
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
#pragma warning disable ASP0029

namespace System.Runtime.CompilerServices
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : System.Attribute
{
public InterceptsLocationAttribute(int version, string data)
{
}
}
}

namespace Microsoft.Extensions.Validation.Generated
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo
{
public GeneratedValidatablePropertyInfo(
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
global::System.Type containingType,
global::System.Type propertyType,
string name,
string displayName) : base(containingType, propertyType, name, displayName)
{
ContainingType = containingType;
Name = name;
}

[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
internal global::System.Type ContainingType { get; }
internal string Name { get; }

protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes()
=> ValidationAttributeCache.GetValidationAttributes(ContainingType, Name);
}

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo
{
public GeneratedValidatableTypeInfo(
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
global::System.Type type,
ValidatablePropertyInfo[] members) : base(type, members) { }
}

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver
{
public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
{
validatableInfo = null;
if (type == typeof(global::SubType))
{
validatableInfo = new GeneratedValidatableTypeInfo(
type: typeof(global::SubType),
members: [
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::SubType),
propertyType: typeof(string),
name: "RequiredProperty",
displayName: "RequiredProperty"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::SubType),
propertyType: typeof(string),
name: "StringWithLength",
displayName: "StringWithLength"
),
]
);
return true;
}
if (type == typeof(global::SubTypeWithInheritance))
{
validatableInfo = new GeneratedValidatableTypeInfo(
type: typeof(global::SubTypeWithInheritance),
members: [
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::SubTypeWithInheritance),
propertyType: typeof(string),
name: "EmailString",
displayName: "EmailString"
),
]
);
return true;
}
if (type == typeof(global::ComplexType))
{
validatableInfo = new GeneratedValidatableTypeInfo(
type: typeof(global::ComplexType),
members: [
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexType),
propertyType: typeof(int),
name: "IntegerWithRange",
displayName: "IntegerWithRange"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexType),
propertyType: typeof(int),
name: "IntegerWithRangeAndDisplayName",
displayName: "Valid identifier"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexType),
propertyType: typeof(global::SubType),
name: "PropertyWithMemberAttributes",
displayName: "PropertyWithMemberAttributes"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexType),
propertyType: typeof(global::SubType),
name: "PropertyWithoutMemberAttributes",
displayName: "PropertyWithoutMemberAttributes"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexType),
propertyType: typeof(global::SubTypeWithInheritance),
name: "PropertyWithInheritance",
displayName: "PropertyWithInheritance"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexType),
propertyType: typeof(global::System.Collections.Generic.List<global::SubType>),
name: "ListOfSubTypes",
displayName: "ListOfSubTypes"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexType),
propertyType: typeof(int),
name: "IntegerWithCustomValidationAttribute",
displayName: "IntegerWithCustomValidationAttribute"
),
new GeneratedValidatablePropertyInfo(
containingType: typeof(global::ComplexType),
propertyType: typeof(int),
name: "PropertyWithMultipleAttributes",
displayName: "PropertyWithMultipleAttributes"
),
]
);
return true;
}

return false;
}

// No-ops, rely on runtime code for ParameterInfo-based resolution
public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
{
validatableInfo = null;
return false;
}
}

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class GeneratedServiceCollectionExtensions
{
[InterceptsLocation]
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action<global::Microsoft.Extensions.Validation.ValidationOptions>? configureOptions = null)
{
// Use non-extension method to avoid infinite recursion.
return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options =>
{
options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver());
if (configureOptions is not null)
{
configureOptions(options);
}
});
}
}

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
file static class ValidationAttributeCache
{
private sealed record CacheKey(
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
[property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
global::System.Type ContainingType,
string PropertyName);
private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<CacheKey, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> _cache = new();

public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes(
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
global::System.Type containingType,
string propertyName)
{
var key = new CacheKey(containingType, propertyName);
return _cache.GetOrAdd(key, static k =>
{
var results = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>();

// Get attributes from the property
var property = k.ContainingType.GetProperty(k.PropertyName);
if (property != null)
{
var propertyAttributes = global::System.Reflection.CustomAttributeExtensions
.GetCustomAttributes<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(property, inherit: true);

results.AddRange(propertyAttributes);
}

// Check constructors for parameters that match the property name
// to handle record scenarios
foreach (var constructor in k.ContainingType.GetConstructors())
{
// Look for parameter with matching name (case insensitive)
var parameter = global::System.Linq.Enumerable.FirstOrDefault(
constructor.GetParameters(),
p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase));

if (parameter != null)
{
var paramAttributes = global::System.Reflection.CustomAttributeExtensions
.GetCustomAttributes<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(parameter, inherit: true);

results.AddRange(paramAttributes);

break;
}
}

return results.ToArray();
});
}
}
}
Loading
Loading