Skip to content

Commit 22f7b3d

Browse files
committed
Add initial validations generator for minimal APIs
1 parent f7ff82f commit 22f7b3d

File tree

77 files changed

+4796
-31
lines changed

Some content is hidden

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

77 files changed

+4796
-31
lines changed

AspNetCore.sln

+38-19
Original file line numberDiff line numberDiff line change
@@ -1732,7 +1732,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
17321732
EndProject
17331733
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests", "src\Servers\Kestrel\Transport.NamedPipes\test\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests.csproj", "{97C7D2A4-87E5-4A4A-A170-D736427D5C21}"
17341734
EndProject
1735-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.RequestDelegateGenerator", "src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
1735+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.RequestDelegateGenerator", "src\Http\Http.Extensions\gen\RequestDelegateGenerator\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
17361736
EndProject
17371737
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "QuickGrid", "QuickGrid", "{C406D9E0-1585-43F9-AA8F-D468AF84A996}"
17381738
EndProject
@@ -1812,6 +1812,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Assets", "Assets", "{2B858B
18121812
EndProject
18131813
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.App.Internal.Assets", "src\Assets\Microsoft.AspNetCore.App.Internal.Assets.csproj", "{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}"
18141814
EndProject
1815+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.ValidationsGenerator", "src\Http\Http.Extensions\gen\ValidationsGenerator\Microsoft.AspNetCore.Http.ValidationsGenerator.csproj", "{185D74AB-76CE-44C1-86EB-16E93C84033F}"
1816+
EndProject
18151817
Global
18161818
GlobalSection(SolutionConfigurationPlatforms) = preSolution
18171819
Debug|Any CPU = Debug|Any CPU
@@ -10943,22 +10945,38 @@ Global
1094310945
{C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x64.Build.0 = Release|Any CPU
1094410946
{C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x86.ActiveCfg = Release|Any CPU
1094510947
{C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x86.Build.0 = Release|Any CPU
10946-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10947-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|Any CPU.Build.0 = Debug|Any CPU
10948-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|arm64.ActiveCfg = Debug|Any CPU
10949-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|arm64.Build.0 = Debug|Any CPU
10950-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|x64.ActiveCfg = Debug|Any CPU
10951-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|x64.Build.0 = Debug|Any CPU
10952-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|x86.ActiveCfg = Debug|Any CPU
10953-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|x86.Build.0 = Debug|Any CPU
10954-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|Any CPU.ActiveCfg = Release|Any CPU
10955-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|Any CPU.Build.0 = Release|Any CPU
10956-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|arm64.ActiveCfg = Release|Any CPU
10957-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|arm64.Build.0 = Release|Any CPU
10958-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|x64.ActiveCfg = Release|Any CPU
10959-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|x64.Build.0 = Release|Any CPU
10960-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|x86.ActiveCfg = Release|Any CPU
10961-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|x86.Build.0 = Release|Any CPU
10948+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10949+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|Any CPU.Build.0 = Debug|Any CPU
10950+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|arm64.ActiveCfg = Debug|Any CPU
10951+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|arm64.Build.0 = Debug|Any CPU
10952+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|x64.ActiveCfg = Debug|Any CPU
10953+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|x64.Build.0 = Debug|Any CPU
10954+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|x86.ActiveCfg = Debug|Any CPU
10955+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|x86.Build.0 = Debug|Any CPU
10956+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|Any CPU.ActiveCfg = Release|Any CPU
10957+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|Any CPU.Build.0 = Release|Any CPU
10958+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|arm64.ActiveCfg = Release|Any CPU
10959+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|arm64.Build.0 = Release|Any CPU
10960+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|x64.ActiveCfg = Release|Any CPU
10961+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|x64.Build.0 = Release|Any CPU
10962+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|x86.ActiveCfg = Release|Any CPU
10963+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|x86.Build.0 = Release|Any CPU
10964+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10965+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|Any CPU.Build.0 = Debug|Any CPU
10966+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|arm64.ActiveCfg = Debug|Any CPU
10967+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|arm64.Build.0 = Debug|Any CPU
10968+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|x64.ActiveCfg = Debug|Any CPU
10969+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|x64.Build.0 = Debug|Any CPU
10970+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|x86.ActiveCfg = Debug|Any CPU
10971+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|x86.Build.0 = Debug|Any CPU
10972+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|Any CPU.ActiveCfg = Release|Any CPU
10973+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|Any CPU.Build.0 = Release|Any CPU
10974+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|arm64.ActiveCfg = Release|Any CPU
10975+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|arm64.Build.0 = Release|Any CPU
10976+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|x64.ActiveCfg = Release|Any CPU
10977+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|x64.Build.0 = Release|Any CPU
10978+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|x86.ActiveCfg = Release|Any CPU
10979+
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|x86.Build.0 = Release|Any CPU
1096210980
EndGlobalSection
1096310981
GlobalSection(SolutionProperties) = preSolution
1096410982
HideSolutionNode = FALSE
@@ -11853,8 +11871,9 @@ Global
1185311871
{B32FF7A7-9CB3-4DCD-AE97-3B2594DB9DAC} = {2299CCD8-8F9C-4F2B-A633-9BF4DA81022B}
1185411872
{B9BBC1A8-7F58-4F43-94C3-5F3CB125CEF7} = {B32FF7A7-9CB3-4DCD-AE97-3B2594DB9DAC}
1185511873
{C3928C15-1836-46DB-A09D-9EFBCCA33E08} = {B5D98AEB-9409-4280-8225-9C1EC6A791B2}
11856-
{2B858B82-5F0B-4A24-B3C0-5E99149F70D6} = {017429CC-C5FB-48B4-9C46-034E29EE2F06}
11857-
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62} = {2B858B82-5F0B-4A24-B3C0-5E99149F70D6}
11874+
{18A3AF88-D633-44E6-9407-3B2C708F7F64} = {225AEDCF-7162-4A86-AC74-06B84660B379}
11875+
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11} = {18A3AF88-D633-44E6-9407-3B2C708F7F64}
11876+
{185D74AB-76CE-44C1-86EB-16E93C84033F} = {18A3AF88-D633-44E6-9407-3B2C708F7F64}
1185811877
EndGlobalSection
1185911878
GlobalSection(ExtensibilityGlobals) = postSolution
1186011879
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

eng/Dependencies.props

+1
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ and are generated based on the last package release.
224224
<LatestPackageReference Include="Swashbuckle.AspNetCore" />
225225
<LatestPackageReference Include="System.Reactive.Linq" />
226226
<LatestPackageReference Include="Verify.Xunit" />
227+
<LatestPackageReference Include="Verify.SourceGenerators" />
227228
<LatestPackageReference Include="xunit.abstractions" />
228229
<LatestPackageReference Include="xunit.analyzers" />
229230
<LatestPackageReference Include="xunit.assert" />

eng/Versions.props

+1
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@
327327
<SystemReactiveLinqVersion>5.0.0</SystemReactiveLinqVersion>
328328
<SwashbuckleAspNetCoreVersion>6.6.2</SwashbuckleAspNetCoreVersion>
329329
<VerifyXunitVersion>19.14.0</VerifyXunitVersion>
330+
<VerifySourceGeneratorsVersion>2.2.0</VerifySourceGeneratorsVersion>
330331
<XunitAbstractionsVersion>2.0.3</XunitAbstractionsVersion>
331332
<XunitAnalyzersVersion>1.15.0</XunitAnalyzersVersion>
332333
<XunitVersion>2.9.2</XunitVersion>

eng/testing/linker/SupportFiles/Directory.Build.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
<ProjectReference Include="$(RepoRoot)src\SignalR\clients\csharp\Client\src\Microsoft.AspNetCore.SignalR.Client.csproj" />
7777
<ProjectReference Include="$(RepoRoot)src\SignalR\server\SignalR\src\Microsoft.AspNetCore.SignalR.csproj" />
7878
<ProjectReference Include="$(RepoRoot)src\OpenApi\src\Microsoft.AspNetCore.OpenApi.csproj" />
79-
<ProjectReference Include="$(RepoRoot)src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
79+
<ProjectReference Include="$(RepoRoot)src\Http\Http.Extensions\gen\RequestDelegateGenerator\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
8080
</ItemGroup>
8181

8282
</Project>
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Builder.WebApplication.Conventions.get -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!

src/DefaultBuilder/src/WebApplication.cs

+20-3
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,18 @@ public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteB
2727
internal const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";
2828

2929
private readonly IHost _host;
30-
private readonly List<EndpointDataSource> _dataSources = new();
30+
private readonly GlobalEndpointRouteBuilder _innerBuilder;
31+
private readonly RouteGroupBuilder _globalRouteGroup;
3132

3233
internal WebApplication(IHost host)
3334
{
3435
_host = host;
36+
_innerBuilder = new(this);
37+
_globalRouteGroup = _innerBuilder.MapGroup("");
3538
ApplicationBuilder = new ApplicationBuilder(host.Services, ServerFeatures);
3639
Logger = host.Services.GetRequiredService<ILoggerFactory>().CreateLogger(Environment.ApplicationName ?? nameof(WebApplication));
3740

38-
Properties[GlobalEndpointRouteBuilderKey] = this;
41+
Properties[GlobalEndpointRouteBuilderKey] = _innerBuilder;
3942
}
4043

4144
/// <summary>
@@ -80,9 +83,14 @@ IServiceProvider IApplicationBuilder.ApplicationServices
8083
internal IDictionary<string, object?> Properties => ApplicationBuilder.Properties;
8184
IDictionary<string, object?> IApplicationBuilder.Properties => Properties;
8285

83-
internal ICollection<EndpointDataSource> DataSources => _dataSources;
86+
internal ICollection<EndpointDataSource> DataSources => ((IEndpointRouteBuilder)_globalRouteGroup).DataSources;
8487
ICollection<EndpointDataSource> IEndpointRouteBuilder.DataSources => DataSources;
8588

89+
/// <summary>
90+
/// Gets the <see cref="IEndpointConventionBuilder"/> for the application.
91+
/// </summary>
92+
public IEndpointConventionBuilder Conventions => _globalRouteGroup;
93+
8694
internal ApplicationBuilder ApplicationBuilder { get; }
8795

8896
IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services;
@@ -307,4 +315,13 @@ public IList<string>? Middleware
307315
}
308316
}
309317
}
318+
319+
private class GlobalEndpointRouteBuilder(WebApplication application) : IEndpointRouteBuilder
320+
{
321+
public IServiceProvider ServiceProvider => application.Services;
322+
323+
public ICollection<EndpointDataSource> DataSources { get; } = [];
324+
325+
public IApplicationBuilder CreateApplicationBuilder() => application;
326+
}
310327
}

src/DefaultBuilder/src/WebApplicationBuilder.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
408408
// destination.UseEndpoints()
409409

410410
// Set the route builder so that UseRouting will use the WebApplication as the IEndpointRouteBuilder for route matching
411-
app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication);
411+
app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication.Properties[WebApplication.GlobalEndpointRouteBuilderKey]);
412412

413413
// Only call UseRouting() if there are endpoints configured and UseRouting() wasn't called on the global route builder already
414414
if (_builtApplication.DataSources.Count > 0)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 System.IO;
6+
using Microsoft.CodeAnalysis;
7+
8+
namespace Microsoft.AspNetCore.Http.ValidationsGenerator;
9+
10+
public sealed partial class ValidationsGenerator
11+
{
12+
internal static void EmitValidationsFile(SourceProductionContext context, ((string Left, string Right) Left, ImmutableArray<string> Right) source)
13+
{
14+
var withValidations = source.Left.Left;
15+
var typeValidations = source.Left.Right;
16+
var validationsFilters = source.Right;
17+
var writer = new StringWriter();
18+
var output = new CodeWriter(writer, baseIndent: 0);
19+
output.WriteLine("// <auto-generated/>");
20+
output.WriteLine("#nullable enable");
21+
output.WriteLine("namespace System.Runtime.CompilerServices");
22+
output.StartBlock();
23+
output.WriteLine("[AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true)]");
24+
output.WriteLine("file sealed class InterceptsLocationAttribute : Attribute");
25+
output.StartBlock();
26+
output.WriteLine("public InterceptsLocationAttribute(int version, string data) { }");
27+
output.EndBlock();
28+
output.EndBlock();
29+
output.WriteLine();
30+
output.WriteLine("namespace Microsoft.AspNetCore.Http.Validations.Generated");
31+
output.StartBlock();
32+
output.WriteLine("using System;");
33+
output.WriteLine("using System.Linq;");
34+
output.WriteLine("using System.Diagnostics;");
35+
output.WriteLine("using System.ComponentModel.DataAnnotations;");
36+
output.WriteLine();
37+
output.Indent--;
38+
output.WriteLine(EmitEndpointKey());
39+
output.WriteLine(EmitValidationProblemBuilder());
40+
output.WriteLine(withValidations);
41+
output.WriteLine();
42+
output.WriteLine(typeValidations);
43+
output.WriteLine();
44+
output.Write(EmitEndpointValidationFilters(validationsFilters));
45+
output.WriteLine("}");
46+
output.WriteLine("#nullable restore");
47+
context.AddSource("RouteHandlerValidations.g.cs", writer.ToString());
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.IO;
5+
6+
namespace Microsoft.AspNetCore.Http.ValidationsGenerator;
7+
8+
public sealed partial class ValidationsGenerator
9+
{
10+
internal static string EmitEndpointKey()
11+
{
12+
var writer = new StringWriter();
13+
var code = new CodeWriter(writer, baseIndent: 1);
14+
code.WriteLine("file class EndpointKey(string route, global::System.Collections.Generic.IEnumerable<string> methods)");
15+
code.StartBlock();
16+
code.WriteLine("public string Route { get; } = route;");
17+
code.WriteLine("public global::System.Collections.Generic.IEnumerable<string> Methods { get; } = methods;");
18+
code.WriteLine();
19+
code.WriteLine("public override bool Equals(object? obj)");
20+
code.StartBlock();
21+
code.WriteLine("if (obj is EndpointKey other)");
22+
code.StartBlock();
23+
code.WriteLine("return string.Equals(Route, other.Route, global::System.StringComparison.OrdinalIgnoreCase) &&");
24+
code.WriteLine("Methods.SequenceEqual(other.Methods, global::System.StringComparer.OrdinalIgnoreCase);");
25+
code.EndBlock();
26+
code.WriteLine("return false;");
27+
code.EndBlock();
28+
code.WriteLine();
29+
code.WriteLine("public override int GetHashCode()");
30+
code.StartBlock();
31+
code.WriteLine("int hash = 17;");
32+
code.WriteLine("hash = hash * 23 + (Route?.GetHashCode(global::System.StringComparison.OrdinalIgnoreCase) ?? 0);");
33+
code.WriteLine("hash = hash * 23 + GetMethodsHashCode(Methods);");
34+
code.WriteLine("return hash;");
35+
code.EndBlock();
36+
code.WriteLine();
37+
code.WriteLine("private static int GetMethodsHashCode(global::System.Collections.Generic.IEnumerable<string> methods)");
38+
code.StartBlock();
39+
code.WriteLine("if (methods == null)");
40+
code.StartBlock();
41+
code.WriteLine("return 0;");
42+
code.EndBlock();
43+
code.WriteLine("int hash = 17;");
44+
code.WriteLine("foreach (var method in methods)");
45+
code.StartBlock();
46+
code.WriteLine("hash = hash * 23 + (method?.GetHashCode(global::System.StringComparison.OrdinalIgnoreCase) ?? 0);");
47+
code.EndBlock();
48+
code.WriteLine("return hash;");
49+
code.EndBlock();
50+
code.EndBlock();
51+
return writer.ToString();
52+
}
53+
}

0 commit comments

Comments
 (0)