Skip to content

Commit 0f681b4

Browse files
Copilotcaptainsafia
andcommitted
Created project structure and moved source files
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
1 parent b65017b commit 0f681b4

File tree

65 files changed

+8667
-16
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+8667
-16
lines changed

eng/ProjectReferences.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Routing" ProjectPath="$(RepoRoot)src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj" />
3939
<ProjectReferenceProvider Include="Microsoft.AspNetCore.WebUtilities" ProjectPath="$(RepoRoot)src\Http\WebUtilities\src\Microsoft.AspNetCore.WebUtilities.csproj" />
4040
<ProjectReferenceProvider Include="Microsoft.Extensions.Http.Polly" ProjectPath="$(RepoRoot)src\HttpClientFactory\Polly\src\Microsoft.Extensions.Http.Polly.csproj" />
41+
<ProjectReferenceProvider Include="Microsoft.Extensions.Validation" ProjectPath="$(RepoRoot)src\Validation\src\Microsoft.Extensions.Validation.csproj" />
4142
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Html.Abstractions" ProjectPath="$(RepoRoot)src\Html.Abstractions\src\Microsoft.AspNetCore.Html.Abstractions.csproj" />
4243
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Identity" ProjectPath="$(RepoRoot)src\Identity\Core\src\Microsoft.AspNetCore.Identity.csproj" />
4344
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" ProjectPath="$(RepoRoot)src\Identity\EntityFrameworkCore\src\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj" />

src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Microsoft.AspNetCore.Http.HttpResponse</Description>
2121
<Reference Include="Microsoft.Net.Http.Headers" />
2222
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
2323
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
24+
<Reference Include="Microsoft.Extensions.Validation" />
2425

2526
<Compile Include="$(SharedSourceRoot)ParameterDefaultValue\*.cs" />
2627
<Compile Include="$(SharedSourceRoot)PropertyHelper\**\*.cs" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
// Forward validation-related types from Microsoft.Extensions.Validation
5+
// to maintain backward compatibility
6+
7+
using Microsoft.Extensions.DependencyInjection;
8+
using System.Runtime.CompilerServices;
9+
10+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.Validation.IValidatableInfo))]
11+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.Validation.IValidatableInfoResolver))]
12+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.Validation.ValidatableParameterInfo))]
13+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.Validation.ValidatablePropertyInfo))]
14+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.Validation.ValidatableTypeAttribute))]
15+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.Validation.ValidatableTypeInfo))]
16+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.Validation.ValidateContext))]
17+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.Validation.ValidationOptions))]
18+
[assembly: TypeForwardedTo(typeof(Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions))]

src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Microsoft.AspNetCore.Http.ValidationsGenerator.csproj

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,12 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="All" IsImplicitlyDefined="true" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" />
15-
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="All" IsImplicitlyDefined="true" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" />
14+
<Reference Include="Microsoft.Extensions.Validation.ValidationsGenerator" />
1615
</ItemGroup>
1716

1817
<ItemGroup>
19-
<InternalsVisibleTo Include="Microsoft.AspNetCore.Http.Extensions.Tests" />
20-
</ItemGroup>
21-
22-
<ItemGroup>
23-
<Compile Include="$(SharedSourceRoot)IsExternalInit.cs" LinkBase="Shared" />
24-
<Compile Include="$(SharedSourceRoot)HashCode.cs" LinkBase="Shared" />
25-
<Compile Include="$(SharedSourceRoot)RoslynUtils\BoundedCacheWithFactory.cs" LinkBase="Shared" />
26-
<Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypeData.cs" LinkBase="Shared" />
27-
<Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypes.cs" LinkBase="Shared" />
28-
<Compile Include="$(SharedSourceRoot)RoslynUtils\CodeWriter.cs" LinkBase="Shared" />
29-
<Compile Include="$(RepoRoot)\src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.RequestDelegateGenerator\StaticRouteHandlerModel\InvocationOperationExtensions.cs" LinkBase="Shared" />
30-
<Compile Include="$(SharedSourceRoot)Diagnostics\AnalyzerDebug.cs" LinkBase="Shared" />
31-
<Compile Include="$(SharedSourceRoot)RoslynUtils\ParsabilityHelper.cs" LinkBase="Shared" />
32-
<Compile Include="$(SharedSourceRoot)RoslynUtils\SymbolExtensions.cs" LinkBase="Shared" />
18+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="All" IsImplicitlyDefined="true" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" />
19+
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="All" IsImplicitlyDefined="true" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" />
3320
</ItemGroup>
3421

3522
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
using Microsoft.Extensions.Validation.ValidationsGenerator;
7+
8+
// This class forwards to the new generator implementation
9+
namespace Microsoft.AspNetCore.Http.ValidationsGenerator;
10+
11+
public sealed partial class ValidationsGenerator : IIncrementalGenerator
12+
{
13+
private static readonly Microsoft.Extensions.Validation.ValidationsGenerator.ValidationsGenerator _forwardingGenerator =
14+
new Microsoft.Extensions.Validation.ValidationsGenerator.ValidationsGenerator();
15+
16+
public void Initialize(IncrementalGeneratorInitializationContext context)
17+
{
18+
_forwardingGenerator.Initialize(context);
19+
}
20+
}

src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<Reference Include="Microsoft.AspNetCore.WebUtilities" />
4444
<Reference Include="Microsoft.Net.Http.Headers" />
4545
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
46+
<Reference Include="Microsoft.Extensions.Validation" />
4647
</ItemGroup>
4748

4849
<ItemGroup>

src/Validation/Validations.slnf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"solution": {
3+
"path": "..\\..\\AspNetCore.sln",
4+
"projects": [
5+
"src\\Validation\\src\\Microsoft.Extensions.Validation.csproj",
6+
"src\\Validation\\test\\Microsoft.Extensions.Validation.Tests\\Microsoft.Extensions.Validation.Tests.csproj",
7+
"src\\Validation\\gen\\Microsoft.Extensions.Validation.ValidationsGenerator.csproj",
8+
"src\\Validation\\test\\Microsoft.Extensions.Validation.ValidationsGenerator.Tests\\Microsoft.Extensions.Validation.ValidationsGenerator.Tests.csproj"
9+
]
10+
}
11+
}

src/Validation/build.cmd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@ECHO OFF
2+
SET RepoRoot=%~dp0..\..
3+
4+
call %RepoRoot%\eng\build.cmd -projects %RepoRoot%\src\Validation\**\*.csproj %*

src/Validation/build.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6+
"$DIR/../../eng/build.sh" --projects "$DIR/**/*.csproj" "$@"
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Immutable;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.Text;
7+
using System.Text;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using System.IO;
10+
11+
namespace Microsoft.Extensions.Validation.ValidationsGenerator;
12+
13+
public sealed partial class ValidationsGenerator : IIncrementalGenerator
14+
{
15+
public static string GeneratedCodeConstructor => $@"global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{typeof(ValidationsGenerator).Assembly.FullName}"", ""{typeof(ValidationsGenerator).Assembly.GetName().Version}"")";
16+
public static string GeneratedCodeAttribute => $"[{GeneratedCodeConstructor}]";
17+
18+
internal static void Emit(SourceProductionContext context, (InterceptableLocation? AddValidation, ImmutableArray<ValidatableType> ValidatableTypes) emitInputs)
19+
{
20+
if (emitInputs.AddValidation is null)
21+
{
22+
// Avoid generating code if no AddValidation call was found.
23+
return;
24+
}
25+
var source = Emit(emitInputs.AddValidation, emitInputs.ValidatableTypes);
26+
context.AddSource("ValidatableInfoResolver.g.cs", SourceText.From(source, Encoding.UTF8));
27+
}
28+
29+
private static string Emit(InterceptableLocation addValidation, ImmutableArray<ValidatableType> validatableTypes) => $$"""
30+
#nullable enable annotations
31+
//------------------------------------------------------------------------------
32+
// <auto-generated>
33+
// This code was generated by a tool.
34+
//
35+
// Changes to this file may cause incorrect behavior and will be lost if
36+
// the code is regenerated.
37+
// </auto-generated>
38+
//------------------------------------------------------------------------------
39+
#nullable enable
40+
#pragma warning disable ASP0029
41+
42+
namespace System.Runtime.CompilerServices
43+
{
44+
{{GeneratedCodeAttribute}}
45+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
46+
file sealed class InterceptsLocationAttribute : System.Attribute
47+
{
48+
public InterceptsLocationAttribute(int version, string data)
49+
{
50+
}
51+
}
52+
}
53+
54+
namespace Microsoft.Extensions.Validation.Generated
55+
{
56+
{{GeneratedCodeAttribute}}
57+
file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo
58+
{
59+
public GeneratedValidatablePropertyInfo(
60+
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)]
61+
global::System.Type containingType,
62+
global::System.Type propertyType,
63+
string name,
64+
string displayName) : base(containingType, propertyType, name, displayName)
65+
{
66+
ContainingType = containingType;
67+
Name = name;
68+
}
69+
70+
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)]
71+
internal global::System.Type ContainingType { get; }
72+
internal string Name { get; }
73+
74+
protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes()
75+
=> ValidationAttributeCache.GetValidationAttributes(ContainingType, Name);
76+
}
77+
78+
{{GeneratedCodeAttribute}}
79+
file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo
80+
{
81+
public GeneratedValidatableTypeInfo(
82+
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
83+
global::System.Type type,
84+
ValidatablePropertyInfo[] members) : base(type, members) { }
85+
}
86+
87+
{{GeneratedCodeAttribute}}
88+
file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver
89+
{
90+
public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
91+
{
92+
validatableInfo = null;
93+
{{EmitTypeChecks(validatableTypes)}}
94+
return false;
95+
}
96+
97+
// No-ops, rely on runtime code for ParameterInfo-based resolution
98+
public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
99+
{
100+
validatableInfo = null;
101+
return false;
102+
}
103+
}
104+
105+
{{GeneratedCodeAttribute}}
106+
file static class GeneratedServiceCollectionExtensions
107+
{
108+
{{addValidation.GetInterceptsLocationAttributeSyntax()}}
109+
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)
110+
{
111+
// Use non-extension method to avoid infinite recursion.
112+
return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options =>
113+
{
114+
options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver());
115+
if (configureOptions is not null)
116+
{
117+
configureOptions(options);
118+
}
119+
});
120+
}
121+
}
122+
123+
{{GeneratedCodeAttribute}}
124+
file static class ValidationAttributeCache
125+
{
126+
private sealed record CacheKey([property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] global::System.Type ContainingType, string PropertyName);
127+
private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<CacheKey, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> _cache = new();
128+
129+
public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes(
130+
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)]
131+
global::System.Type containingType,
132+
string propertyName)
133+
{
134+
var key = new CacheKey(containingType, propertyName);
135+
return _cache.GetOrAdd(key, static k =>
136+
{
137+
var results = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>();
138+
139+
// Get attributes from the property
140+
var property = k.ContainingType.GetProperty(k.PropertyName);
141+
if (property != null)
142+
{
143+
var propertyAttributes = global::System.Reflection.CustomAttributeExtensions
144+
.GetCustomAttributes<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(property, inherit: true);
145+
146+
results.AddRange(propertyAttributes);
147+
}
148+
149+
// Check constructors for parameters that match the property name
150+
// to handle record scenarios
151+
foreach (var constructor in k.ContainingType.GetConstructors())
152+
{
153+
// Look for parameter with matching name (case insensitive)
154+
var parameter = global::System.Linq.Enumerable.FirstOrDefault(
155+
constructor.GetParameters(),
156+
p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase));
157+
158+
if (parameter != null)
159+
{
160+
var paramAttributes = global::System.Reflection.CustomAttributeExtensions
161+
.GetCustomAttributes<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(parameter, inherit: true);
162+
163+
results.AddRange(paramAttributes);
164+
165+
break;
166+
}
167+
}
168+
169+
return results.ToArray();
170+
});
171+
}
172+
}
173+
}
174+
""";
175+
176+
private static string EmitTypeChecks(ImmutableArray<ValidatableType> validatableTypes)
177+
{
178+
var sw = new StringWriter();
179+
var cw = new CodeWriter(sw, baseIndent: 3);
180+
foreach (var validatableType in validatableTypes)
181+
{
182+
var typeName = validatableType.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
183+
cw.WriteLine($"if (type == typeof({typeName}))");
184+
cw.StartBlock();
185+
cw.WriteLine($"validatableInfo = new GeneratedValidatableTypeInfo(");
186+
cw.Indent++;
187+
cw.WriteLine($"type: typeof({typeName}),");
188+
if (validatableType.Members.IsDefaultOrEmpty)
189+
{
190+
cw.WriteLine("members: []");
191+
}
192+
else
193+
{
194+
cw.WriteLine("members: [");
195+
cw.Indent++;
196+
foreach (var member in validatableType.Members)
197+
{
198+
EmitValidatableMemberForCreate(member, cw);
199+
}
200+
cw.Indent--;
201+
cw.WriteLine("]");
202+
}
203+
cw.Indent--;
204+
cw.WriteLine(");");
205+
cw.WriteLine("return true;");
206+
cw.EndBlock();
207+
}
208+
return sw.ToString();
209+
}
210+
211+
private static void EmitValidatableMemberForCreate(ValidatableProperty member, CodeWriter cw)
212+
{
213+
cw.WriteLine("new GeneratedValidatablePropertyInfo(");
214+
cw.Indent++;
215+
cw.WriteLine($"containingType: typeof({member.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}),");
216+
cw.WriteLine($"propertyType: typeof({member.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}),");
217+
cw.WriteLine($"name: \"{member.Name}\",");
218+
cw.WriteLine($"displayName: \"{member.DisplayName}\"");
219+
cw.Indent--;
220+
cw.WriteLine("),");
221+
}
222+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Linq;
6+
using Microsoft.CodeAnalysis;
7+
8+
namespace Microsoft.Extensions.Validation.ValidationsGenerator;
9+
10+
internal static class ISymbolExtensions
11+
{
12+
public static string GetDisplayName(this ISymbol property, INamedTypeSymbol displayAttribute)
13+
{
14+
var displayNameAttribute = property.GetAttributes()
15+
.FirstOrDefault(attribute =>
16+
attribute.AttributeClass is { } attributeClass &&
17+
SymbolEqualityComparer.Default.Equals(attributeClass, displayAttribute));
18+
19+
if (displayNameAttribute is not null)
20+
{
21+
if (!displayNameAttribute.NamedArguments.IsDefaultOrEmpty)
22+
{
23+
foreach (var namedArgument in displayNameAttribute.NamedArguments)
24+
{
25+
if (string.Equals(namedArgument.Key, "Name", StringComparison.Ordinal))
26+
{
27+
return namedArgument.Value.Value?.ToString() ?? property.Name;
28+
}
29+
}
30+
}
31+
}
32+
33+
return property.Name;
34+
}
35+
}

0 commit comments

Comments
 (0)