Skip to content

Commit e601dc5

Browse files
chrisoverzerocaptainsafia
authored andcommitted
Fully qualify type names for services when resolving in RequestDelegateGenerator (#58640)
* Fully Qualify Type Names When Resolving in RDG These two places correspond to resolution of a service from DI when attributed with `FromServicesAttribute` or with `FromKeyedServicesAttribute`. When the type name of the service is not fully qualified, the generator can generate code which fails to compile. For example, given a service `ExampleService` in namespace `Http`, the generator can generate code to resolve an instance of such a service like this: ```c# var e_local = httpContext.RequestServices.GetRequiredKeyedService<Http.ExampleService>("example"); ``` …but `Microsoft.AspNetCore.Http` is a nearer match for the reference to "Http", so the compiler will look for a type `Microsoft.AspNetCore.Http.ExampleService` (which doesn't exist – at least, not at time of writing), and compilation will fail. Fixes: #58633 * Correct Symbol Display Format and Add Tests See: #58633
1 parent c4105fa commit e601dc5

File tree

6 files changed

+78
-12
lines changed

6 files changed

+78
-12
lines changed

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,8 @@ internal static void EmitServiceParameterPreparation(this EndpointParameter endp
389389
// Unlike other scenarios, this will result in an exception being thrown
390390
// at runtime.
391391
var assigningCode = endpointParameter.IsOptional ?
392-
$"httpContext.RequestServices.GetService<{endpointParameter.Type}>();" :
393-
$"httpContext.RequestServices.GetRequiredService<{endpointParameter.Type}>()";
392+
$"httpContext.RequestServices.GetService<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>();" :
393+
$"httpContext.RequestServices.GetRequiredService<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>()";
394394
codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = {assigningCode};");
395395
}
396396

@@ -404,8 +404,8 @@ internal static void EmitKeyedServiceParameterPreparation(this EndpointParameter
404404
codeWriter.EndBlock();
405405

406406
var assigningCode = endpointParameter.IsOptional ?
407-
$"httpContext.RequestServices.GetKeyedService<{endpointParameter.Type}>({endpointParameter.KeyedServiceKey});" :
408-
$"httpContext.RequestServices.GetRequiredKeyedService<{endpointParameter.Type}>({endpointParameter.KeyedServiceKey})";
407+
$"httpContext.RequestServices.GetKeyedService<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>({endpointParameter.KeyedServiceKey});" :
408+
$"httpContext.RequestServices.GetRequiredKeyedService<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>({endpointParameter.KeyedServiceKey})";
409409
codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = {assigningCode};");
410410
}
411411

src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Http.Generated
107107
{
108108
var wasParamCheckFailure = false;
109109
// Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, IsParsable = False, IsArray = False, Source = Service)
110-
var svc_local = httpContext.RequestServices.GetRequiredService<Microsoft.AspNetCore.Http.Generators.Tests.TestService>();
110+
var svc_local = httpContext.RequestServices.GetRequiredService<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService>();
111111

112112
if (wasParamCheckFailure)
113113
{
@@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.Http.Generated
130130
{
131131
var wasParamCheckFailure = false;
132132
// Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, IsParsable = False, IsArray = False, Source = Service)
133-
var svc_local = httpContext.RequestServices.GetRequiredService<Microsoft.AspNetCore.Http.Generators.Tests.TestService>();
133+
var svc_local = httpContext.RequestServices.GetRequiredService<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService>();
134134

135135
if (wasParamCheckFailure)
136136
{
@@ -207,7 +207,7 @@ namespace Microsoft.AspNetCore.Http.Generated
207207
{
208208
var wasParamCheckFailure = false;
209209
// Endpoint Parameter: svc (Type = System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Generators.Tests.TestService>, IsOptional = False, IsParsable = False, IsArray = False, Source = Service)
210-
var svc_local = httpContext.RequestServices.GetRequiredService<System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Generators.Tests.TestService>>();
210+
var svc_local = httpContext.RequestServices.GetRequiredService<global::System.Collections.Generic.IEnumerable<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService>>();
211211

212212
if (wasParamCheckFailure)
213213
{
@@ -230,7 +230,7 @@ namespace Microsoft.AspNetCore.Http.Generated
230230
{
231231
var wasParamCheckFailure = false;
232232
// Endpoint Parameter: svc (Type = System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Generators.Tests.TestService>, IsOptional = False, IsParsable = False, IsArray = False, Source = Service)
233-
var svc_local = httpContext.RequestServices.GetRequiredService<System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Generators.Tests.TestService>>();
233+
var svc_local = httpContext.RequestServices.GetRequiredService<global::System.Collections.Generic.IEnumerable<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService>>();
234234

235235
if (wasParamCheckFailure)
236236
{
@@ -308,9 +308,9 @@ namespace Microsoft.AspNetCore.Http.Generated
308308
{
309309
var wasParamCheckFailure = false;
310310
// Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService?, IsOptional = True, IsParsable = False, IsArray = False, Source = Service)
311-
var svc_local = httpContext.RequestServices.GetService<Microsoft.AspNetCore.Http.Generators.Tests.TestService?>();;
311+
var svc_local = httpContext.RequestServices.GetService<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService?>();;
312312
// Endpoint Parameter: svcs (Type = System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Generators.Tests.TestService>, IsOptional = False, IsParsable = False, IsArray = False, Source = Service)
313-
var svcs_local = httpContext.RequestServices.GetRequiredService<System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Generators.Tests.TestService>>();
313+
var svcs_local = httpContext.RequestServices.GetRequiredService<global::System.Collections.Generic.IEnumerable<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService>>();
314314

315315
if (wasParamCheckFailure)
316316
{
@@ -333,9 +333,9 @@ namespace Microsoft.AspNetCore.Http.Generated
333333
{
334334
var wasParamCheckFailure = false;
335335
// Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService?, IsOptional = True, IsParsable = False, IsArray = False, Source = Service)
336-
var svc_local = httpContext.RequestServices.GetService<Microsoft.AspNetCore.Http.Generators.Tests.TestService?>();;
336+
var svc_local = httpContext.RequestServices.GetService<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService?>();;
337337
// Endpoint Parameter: svcs (Type = System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Generators.Tests.TestService>, IsOptional = False, IsParsable = False, IsArray = False, Source = Service)
338-
var svcs_local = httpContext.RequestServices.GetRequiredService<System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Generators.Tests.TestService>>();
338+
var svcs_local = httpContext.RequestServices.GetRequiredService<global::System.Collections.Generic.IEnumerable<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService>>();
339339

340340
if (wasParamCheckFailure)
341341
{

src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTestBase.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ internal static string GetMapActionString(string sources, string className = "Te
287287
using Microsoft.AspNetCore.Http.Generators.Tests;
288288
using Microsoft.Extensions.Primitives;
289289
using Microsoft.Extensions.DependencyInjection;
290+
using Http;
290291
291292
public static class {{className}}
292293
{

src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTests.KeyServices.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Globalization;
44
using Microsoft.AspNetCore.Http.Metadata;
55
using Microsoft.AspNetCore.Http.RequestDelegateGenerator;
6+
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel;
67
using Microsoft.Extensions.DependencyInjection;
78
using Microsoft.Extensions.Logging.Abstractions;
89

@@ -225,6 +226,32 @@ public async Task ThrowsIfDiContainerDoesNotSupportKeyedServices()
225226
Assert.Equal("Unable to resolve service referenced by FromKeyedServicesAttribute. The service provider doesn't support keyed services.", exception.Message);
226227
}
227228

229+
// See: https://github.com/dotnet/aspnetcore/issues/58633
230+
[Fact]
231+
public async Task RequestDelegateGeneratesCompilableCodeForKeyedServiceInNamespaceHttp()
232+
{
233+
var source = """
234+
app.MapGet("/hello", ([FromKeyedServices("example")] global::Http.ExampleService e) => e.Act("To be or not to be…"));
235+
""";
236+
var (results, compilation) = await RunGeneratorAsync(source);
237+
238+
// Ironically, the same error this is testing would bite us here, so we must globally qualify the type name.
239+
var serviceProvider = CreateServiceProvider((serviceCollection) => serviceCollection.AddKeyedSingleton<global::Http.ExampleService>("example"));
240+
var endpoint = GetEndpointFromCompilation(compilation, serviceProvider: serviceProvider);
241+
242+
VerifyStaticEndpointModel(results, endpointModel =>
243+
{
244+
Assert.Equal("MapGet", endpointModel.HttpMethod);
245+
var p = Assert.Single(endpointModel.Parameters);
246+
Assert.Equal(EndpointParameterSource.KeyedService, p.Source);
247+
Assert.Equal("e", p.SymbolName);
248+
});
249+
250+
var httpContext = CreateHttpContext(serviceProvider);
251+
await endpoint.RequestDelegate(httpContext);
252+
await VerifyResponseBodyAsync(httpContext, "To be or not to be…");
253+
}
254+
228255
private class MockServiceProvider : IServiceProvider, ISupportRequiredService
229256
{
230257
public object GetService(Type serviceType)

src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,4 +599,30 @@ public async Task RequestDelegateHandlesStringValuesFromExplicitQueryStringSourc
599599
Assert.Equal(new[] { 4, 5, 6 }, (int[])httpContext.Items["headers"]!);
600600
Assert.Equal(new[] { 7, 8, 9 }, (int[])httpContext.Items["form"]!);
601601
}
602+
603+
// See: https://github.com/dotnet/aspnetcore/issues/58633
604+
[Fact]
605+
public async Task RequestDelegateGeneratesCompilableCodeForServiceInNamespaceHttp()
606+
{
607+
var source = """
608+
app.MapGet("/hello", ([FromServices] ExampleService e) => e.Act("To be or not to be…"));
609+
""";
610+
var (results, compilation) = await RunGeneratorAsync(source);
611+
612+
// Ironically, the same error this is testing would bite us here, so we must globally qualify the type name.
613+
var serviceProvider = CreateServiceProvider((serviceCollection) => serviceCollection.AddSingleton<global::Http.ExampleService>());
614+
var endpoint = GetEndpointFromCompilation(compilation, serviceProvider: serviceProvider);
615+
616+
VerifyStaticEndpointModel(results, endpointModel =>
617+
{
618+
Assert.Equal("MapGet", endpointModel.HttpMethod);
619+
var p = Assert.Single(endpointModel.Parameters);
620+
Assert.Equal(EndpointParameterSource.Service, p.Source);
621+
Assert.Equal("e", p.SymbolName);
622+
});
623+
624+
var httpContext = CreateHttpContext(serviceProvider);
625+
await endpoint.RequestDelegate(httpContext);
626+
await VerifyResponseBodyAsync(httpContext, "To be or not to be…");
627+
}
602628
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
using System;
4+
5+
namespace Http;
6+
7+
#nullable enable
8+
9+
public sealed class ExampleService
10+
{
11+
public string Act(string line) => line;
12+
}

0 commit comments

Comments
 (0)