Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add infrastructure for RequestDelegateGenerator #45924

Merged
merged 12 commits into from
Jan 9, 2023
19 changes: 19 additions & 0 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1762,6 +1762,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
EndProject
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Generators", "src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.Generators.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -10571,6 +10573,22 @@ Global
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x64.Build.0 = Release|Any CPU
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x86.ActiveCfg = Release|Any CPU
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x86.Build.0 = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|arm64.ActiveCfg = Debug|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|arm64.Build.0 = Debug|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|x64.ActiveCfg = Debug|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|x64.Build.0 = Debug|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|x86.ActiveCfg = Debug|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|x86.Build.0 = Debug|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|Any CPU.Build.0 = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|arm64.ActiveCfg = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|arm64.Build.0 = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x64.ActiveCfg = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x64.Build.0 = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x86.ActiveCfg = Release|Any CPU
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -11441,6 +11459,7 @@ Global
{F057512B-55BF-4A8B-A027-A0505F8BA10C} = {4FDDC525-4E60-4CAF-83A3-261C5B43721F}
{10173568-A65E-44E5-8C6F-4AA49D0577A1} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
{97C7D2A4-87E5-4A4A-A170-D736427D5C21} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
{4730F56D-24EF-4BB2-AA75-862E31205F3A} = {225AEDCF-7162-4A86-AC74-06B84660B379}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
6 changes: 5 additions & 1 deletion src/Framework/App.Ref/src/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Suppressions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>PKV004</DiagnosticId>
<Target>net7.0</Target>
</Suppression>
<Suppression>
<DiagnosticId>PKV004</DiagnosticId>
<Target>net8.0</Target>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my learning - what are these files for? And why do we need both net7.0 and net8.0 in them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure of the reasons but I ran this delta past @wtgodbe since it was automatically introduced when I ran the GenerateProjectList task after adding the new source generator project to the repo.

From my understanding, it's a requirement of the package validation SDK we use for validating changes across versions.

As to why we have both versions, I'm thinking we might be able to get away with just having net8 in the main branch but I'll let @wtgodbe share.

For now, I just settled with whatever the build produced here 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think net7.0 can be removed now that the source is targeting .NET 8.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think net7.0 can be removed now that the source is targeting .NET 8.

Roger that. It probably makes sense to do this in a separate PR. I assume there's no harm to having net7.0 here since we've had it in the main branch for a while.

We might also want to add removing the older framework target as part of our updating branding instructions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice that these files keep changing because the order of the namespaces emitted into the file keep changing. Anyone know the reason for this non-determinism?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ViktorHofer and @smasher164 own the Package Validation SDK - any idea on why there's a net7.0 & and a net8.0 section in the generated file, or why there may nondeterminism in the ordering of the file? It's been somewhat noisy lately

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smasher164 can you please take a look? We might have caused a non-deterministic behavior by ordering the compatibility differences in ApiCompat. Or maybe not and something else is wrong.

</Suppression>
</Suppressions>
5 changes: 5 additions & 0 deletions src/Framework/App.Ref/src/Microsoft.AspNetCore.App.Ref.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ This package is an internal implementation of the .NET Core SDK and is not meant
Private="false"
ReferenceOutputAssembly="false" />

<ProjectReference Include="$(RepoRoot)src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.Generators.csproj"
Private="false"
ReferenceOutputAssembly="false" />

<!-- Enforce build order. Targeting pack needs to bundle analyzers and information about the runtime. -->
<ProjectReference Include="..\..\App.Runtime\src\Microsoft.AspNetCore.App.Runtime.csproj"
Private="false"
Expand Down Expand Up @@ -176,6 +180,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.App.Analyzers\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.App.Analyzers.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.Components.Analyzers\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Components.Analyzers.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.App.CodeFixes\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.App.CodeFixes.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.Http.Generators\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Http.Generators.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />

<_InitialRefPackContent Include="@(AspNetCoreReferenceAssemblyPath)" PackagePath="$(RefAssemblyPackagePath)" />
<_InitialRefPackContent Include="@(AspNetCoreReferenceDocXml)" PackagePath="$(RefAssemblyPackagePath)" />
Expand Down
6 changes: 5 additions & 1 deletion src/Framework/App.Runtime/src/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Suppressions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>PKV0001</DiagnosticId>
<Target>net7.0</Target>
</Suppression>
<Suppression>
<DiagnosticId>PKV0001</DiagnosticId>
<Target>net8.0</Target>
</Suppression>
</Suppressions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsPackable>false</IsPackable>
<IsAnalyzersProject>true</IsAnalyzersProject>
<AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<Reference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" />
Comment on lines +12 to +13
Copy link
Member

@Youssef1313 Youssef1313 Jan 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a reference to Microsoft.CodeAnalysis.Analyzers. It currently comes as a transitive package from Microsoft.CodeAnalysis.Common. The direct dependency will ensure dotnet/roslyn-analyzers#6229 doesn't happen in future (I'm not sure why it doesn't happen currently).

</ItemGroup>

<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.AspNetCore.Http.Extensions.Tests" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(SharedSourceRoot)IsExternalInit.cs" LinkBase="Shared" />
</ItemGroup>

</Project>
117 changes: 117 additions & 0 deletions src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel;

namespace Microsoft.AspNetCore.Http.Generators;

[Generator]
public sealed class RequestDelegateGenerator : IIncrementalGenerator
{
private static readonly string[] _knownMethods =
{
"MapGet",
"MapPost",
"MapPut",
"MapDelete",
"MapPatch",
"Map",
};

public void Initialize(IncrementalGeneratorInitializationContext context)
{
var endpoints = context.SyntaxProvider.CreateSyntaxProvider(
predicate: (node, _) => node is InvocationExpressionSyntax
{
Expression: MemberAccessExpressionSyntax
{
Name: IdentifierNameSyntax
{
Identifier: { ValueText: var method }
}
},
ArgumentList: { Arguments: { Count: 2 } args }
} && _knownMethods.Contains(method),
transform: (context, token) =>
{
var operation = context.SemanticModel.GetOperation(context.Node, token) as IInvocationOperation;
return StaticRouteHandlerModelParser.GetEndpointFromOperation(operation);
})
.Where(endpoint => endpoint.Response.ResponseType == "string")
.WithTrackingName("EndpointModel");

var thunks = endpoints.Select((endpoint, _) => $$"""
[{{StaticRouteHandlerModelEmitter.EmitSourceKey(endpoint)}}] = (
(del, builder) =>
{
builder.Metadata.Add(new SourceKey{{StaticRouteHandlerModelEmitter.EmitSourceKey(endpoint)}});
},
(del, builder) =>
{
var handler = ({{StaticRouteHandlerModelEmitter.EmitHandlerDelegateType(endpoint)}})del;
EndpointFilterDelegate? filteredInvocation = null;

if (builder.FilterFactories.Count > 0)
{
filteredInvocation = BuildFilterDelegate(ic =>
{
if (ic.HttpContext.Response.StatusCode == 400)
{
return System.Threading.Tasks.ValueTask.FromResult<object?>(Results.Empty);
}
{{StaticRouteHandlerModelEmitter.EmitFilteredInvocation()}}
},
builder,
handler.Method);
}

{{StaticRouteHandlerModelEmitter.EmitRequestHandler()}}
{{StaticRouteHandlerModelEmitter.EmitFilteredRequestHandler()}}

return filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
}),
""");

var stronglyTypedEndpointDefinitions = endpoints.Select((endpoint, _) => $$"""
{{RequestDelegateGeneratorSources.GeneratedCodeAttribute}}
internal static Microsoft.AspNetCore.Builder.RouteHandlerBuilder {{endpoint.HttpMethod}}(
this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
[System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
{{StaticRouteHandlerModelEmitter.EmitHandlerDelegateType(endpoint)}} handler,
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
[System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
{
return MapCore(endpoints, pattern, handler, GetVerb, filePath, lineNumber);
}
""");

var thunksAndEndpoints = thunks.Collect().Combine(stronglyTypedEndpointDefinitions.Collect());

context.RegisterSourceOutput(thunksAndEndpoints, (context, sources) =>
{
var (thunks, endpoints) = sources;

var endpointsCode = new StringBuilder();
var thunksCode = new StringBuilder();
foreach (var endpoint in endpoints)
{
endpointsCode.AppendLine(endpoint);
}
foreach (var thunk in thunks)
{
thunksCode.AppendLine(thunk);
}

var code = RequestDelegateGeneratorSources.GetGeneratedRouteBuilderExtensionsSource(
genericThunks: string.Empty,
captainsafia marked this conversation as resolved.
Show resolved Hide resolved
thunks: thunksCode.ToString(),
endpoints: endpointsCode.ToString());
context.AddSource("GeneratedRouteBuilderExtensions.g.cs", code);
});
}
}
Loading