Skip to content

Commit 9519064

Browse files
committed
Get EmptyHttpResult in RDF via reflection
1 parent 3d6504a commit 9519064

File tree

2 files changed

+54
-25
lines changed

2 files changed

+54
-25
lines changed

src/Http/Http.Extensions/src/RequestDelegateFactory.cs

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,13 @@ public static partial class RequestDelegateFactory
9292
private static readonly MemberExpression FormFilesExpr = Expression.Property(FormExpr, typeof(IFormCollection).GetProperty(nameof(IFormCollection.Files))!);
9393
private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
9494
private static readonly MemberExpression CompletedTaskExpr = Expression.Property(null, (PropertyInfo)GetMemberInfo<Func<Task>>(() => Task.CompletedTask));
95-
private static readonly NewExpression EmptyHttpResultValueTaskExpr = Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(EmptyHttpResult) })!, Expression.Property(null, typeof(EmptyHttpResult), nameof(EmptyHttpResult.Instance)));
96-
95+
// Due to https://github.com/dotnet/aspnetcore/issues/41330 we cannot reference the EmptyHttpResult type
96+
// but users still need to assert on it as in https://github.com/dotnet/aspnetcore/issues/45063
97+
// so we temporarily work around this here by using reflection to get the actual type.
98+
private static readonly NewExpression EmptyHttpResultValueTaskExpr = Type.GetType("Microsoft.AspNetCore.Http.Results, Microsoft.AspNetCore.Http.Results") is {} resultsType
99+
? Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(IResult) })!, Expression.Property(null, resultsType.GetProperty("Empty")!))
100+
: Expression.New(typeof(ValueTask<object>));
101+
private static readonly object? EmptyHttpResultInstance = Type.GetType("Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult, Microsoft.AspNetCore.Http.Results")?.GetProperty("Instance")?.GetValue(null, null);
97102
private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache.TempSourceStringExpr;
98103
private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression.NotEqual(TempSourceStringExpr, Expression.Constant(null));
99104
private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null));
@@ -2154,32 +2159,34 @@ static async Task ExecuteAwaited(ValueTask task)
21542159

21552160
private static ValueTask<object?> ExecuteTaskWithEmptyResult(Task task)
21562161
{
2162+
Debug.Assert(EmptyHttpResultInstance is not null);
21572163
static async ValueTask<object?> ExecuteAwaited(Task task)
21582164
{
21592165
await task;
2160-
return EmptyHttpResult.Instance;
2166+
return EmptyHttpResultInstance;
21612167
}
21622168

21632169
if (task.IsCompletedSuccessfully)
21642170
{
2165-
return new ValueTask<object?>(EmptyHttpResult.Instance);
2171+
return new ValueTask<object?>(EmptyHttpResultInstance);
21662172
}
21672173

21682174
return ExecuteAwaited(task);
21692175
}
21702176

21712177
private static ValueTask<object?> ExecuteValueTaskWithEmptyResult(ValueTask valueTask)
21722178
{
2179+
Debug.Assert(EmptyHttpResultInstance is not null);
21732180
static async ValueTask<object?> ExecuteAwaited(ValueTask task)
21742181
{
21752182
await task;
2176-
return EmptyHttpResult.Instance;
2183+
return EmptyHttpResultInstance;
21772184
}
21782185

21792186
if (valueTask.IsCompletedSuccessfully)
21802187
{
21812188
valueTask.GetAwaiter().GetResult();
2182-
return new ValueTask<object?>(EmptyHttpResult.Instance);
2189+
return new ValueTask<object?>(EmptyHttpResultInstance);
21832190
}
21842191

21852192
return ExecuteAwaited(valueTask);
@@ -2507,24 +2514,6 @@ private static void FormatTrackedParameters(RequestDelegateFactoryContext factor
25072514
}
25082515
}
25092516

2510-
// Due to cyclic references between Http.Extensions and
2511-
// Http.Results, we define our own instance of the `EmptyHttpResult`
2512-
// type here.
2513-
private sealed class EmptyHttpResult : IResult
2514-
{
2515-
private EmptyHttpResult()
2516-
{
2517-
}
2518-
2519-
public static EmptyHttpResult Instance { get; } = new();
2520-
2521-
/// <inheritdoc/>
2522-
public Task ExecuteAsync(HttpContext httpContext)
2523-
{
2524-
return Task.CompletedTask;
2525-
}
2526-
}
2527-
25282517
private sealed class RdfEndpointBuilder : EndpointBuilder
25292518
{
25302519
public RdfEndpointBuilder(IServiceProvider applicationServices)

src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using Microsoft.AspNetCore.Builder;
2222
using Microsoft.AspNetCore.Http;
2323
using Microsoft.AspNetCore.Http.Features;
24+
using Microsoft.AspNetCore.Http.HttpResults;
2425
using Microsoft.AspNetCore.Http.Json;
2526
using Microsoft.AspNetCore.Http.Metadata;
2627
using Microsoft.AspNetCore.Routing.Patterns;
@@ -97,7 +98,18 @@ public async Task RequestDelegateInvokesAction(Delegate @delegate)
9798
{
9899
var httpContext = CreateHttpContext();
99100

100-
var factoryResult = RequestDelegateFactory.Create(@delegate);
101+
var factoryResult = RequestDelegateFactory.Create(@delegate, new RequestDelegateFactoryOptions()
102+
{
103+
EndpointBuilder = CreateEndpointBuilderFromFilterFactories(new List<Func<EndpointFilterFactoryContext, EndpointFilterDelegate, EndpointFilterDelegate>>()
104+
{
105+
(routeHandlerContext, next) => async (context) =>
106+
{
107+
var response = await next(context);
108+
Assert.IsType<EmptyHttpResult>(response);
109+
return response;
110+
}
111+
}),
112+
});
101113
var requestDelegate = factoryResult.RequestDelegate;
102114

103115
await requestDelegate(httpContext);
@@ -7075,6 +7087,34 @@ public void Create_Populates_EndpointBuilderWithRequestDelegateAndMetadata()
70757087
Assert.Same(options.EndpointBuilder.Metadata, result.EndpointMetadata);
70767088
}
70777089

7090+
[Fact]
7091+
public async Task RDF_CanAssertOnEmptyResult()
7092+
{
7093+
var @delegate = (string name, HttpContext context) => context.Items.Add("param", name);
7094+
7095+
var result = RequestDelegateFactory.Create(@delegate, new RequestDelegateFactoryOptions()
7096+
{
7097+
EndpointBuilder = CreateEndpointBuilderFromFilterFactories(new List<Func<EndpointFilterFactoryContext, EndpointFilterDelegate, EndpointFilterDelegate>>()
7098+
{
7099+
(routeHandlerContext, next) => async (context) =>
7100+
{
7101+
var response = await next(context);
7102+
Assert.IsType<EmptyHttpResult>(response);
7103+
Assert.Same(Results.Empty, response);
7104+
return response;
7105+
}
7106+
}),
7107+
});
7108+
7109+
var httpContext = CreateHttpContext();
7110+
httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
7111+
{
7112+
["name"] = "Tester"
7113+
});
7114+
7115+
await result.RequestDelegate(httpContext);
7116+
}
7117+
70787118
private class ParameterListRequiredStringFromDifferentSources
70797119
{
70807120
public HttpContext? HttpContext { get; set; }

0 commit comments

Comments
 (0)