Skip to content

Commit 215879c

Browse files
committed
Merge in 'release/7.0' changes
2 parents 1256325 + 65330ff commit 215879c

File tree

2 files changed

+60
-25
lines changed

2 files changed

+60
-25
lines changed

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

+19-24
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,17 @@ public static partial class RequestDelegateFactory
9191
private static readonly MemberExpression FormFilesExpr = Expression.Property(FormExpr, typeof(IFormCollection).GetProperty(nameof(IFormCollection.Files))!);
9292
private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
9393
private static readonly MemberExpression CompletedTaskExpr = Expression.Property(null, (PropertyInfo)GetMemberInfo<Func<Task>>(() => Task.CompletedTask));
94-
private static readonly NewExpression EmptyHttpResultValueTaskExpr = Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(EmptyHttpResult) })!, Expression.Property(null, typeof(EmptyHttpResult), nameof(EmptyHttpResult.Instance)));
95-
94+
// Due to https://github.com/dotnet/aspnetcore/issues/41330 we cannot reference the EmptyHttpResult type
95+
// but users still need to assert on it as in https://github.com/dotnet/aspnetcore/issues/45063
96+
// so we temporarily work around this here by using reflection to get the actual type.
97+
private static readonly object? EmptyHttpResultInstance = Type.GetType("Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult, Microsoft.AspNetCore.Http.Results")?.GetProperty("Instance")?.GetValue(null, null);
98+
#if DEBUG
99+
private static readonly NewExpression EmptyHttpResultValueTaskExpr = EmptyHttpResultInstance is not null
100+
? Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(IResult) })!, Expression.Constant(EmptyHttpResultInstance))
101+
: throw new UnreachableException("The EmptyHttpResult type could not be found.");
102+
#else
103+
private static readonly NewExpression EmptyHttpResultValueTaskExpr = Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(IResult) })!, Expression.Constant(EmptyHttpResultInstance));
104+
#endif
96105
private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache.TempSourceStringExpr;
97106
private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression.NotEqual(TempSourceStringExpr, Expression.Constant(null));
98107
private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null));
@@ -389,6 +398,7 @@ private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInf
389398
private static EndpointFilterDelegate? CreateFilterPipeline(MethodInfo methodInfo, Expression? targetExpression, RequestDelegateFactoryContext factoryContext, Expression<Func<HttpContext, object?>>? targetFactory)
390399
{
391400
Debug.Assert(factoryContext.EndpointBuilder.FilterFactories.Count > 0);
401+
Debug.Assert(EmptyHttpResultInstance is not null, "The EmptyHttpResult type could not be found.");
392402
// httpContext.Response.StatusCode >= 400
393403
// ? Task.CompletedTask
394404
// : {
@@ -453,6 +463,7 @@ targetExpression is null
453463

454464
private static Expression MapHandlerReturnTypeToValueTask(Expression methodCall, Type returnType)
455465
{
466+
Debug.Assert(EmptyHttpResultInstance is not null, "The EmptyHttpResult type could not be found.");
456467
if (returnType == typeof(void))
457468
{
458469
return Expression.Block(methodCall, EmptyHttpResultValueTaskExpr);
@@ -2097,32 +2108,34 @@ static async Task ExecuteAwaited(ValueTask task)
20972108

20982109
private static ValueTask<object?> ExecuteTaskWithEmptyResult(Task task)
20992110
{
2111+
Debug.Assert(EmptyHttpResultInstance is not null, "The EmptyHttpResult type could not be found.");
21002112
static async ValueTask<object?> ExecuteAwaited(Task task)
21012113
{
21022114
await task;
2103-
return EmptyHttpResult.Instance;
2115+
return EmptyHttpResultInstance;
21042116
}
21052117

21062118
if (task.IsCompletedSuccessfully)
21072119
{
2108-
return new ValueTask<object?>(EmptyHttpResult.Instance);
2120+
return new ValueTask<object?>(EmptyHttpResultInstance);
21092121
}
21102122

21112123
return ExecuteAwaited(task);
21122124
}
21132125

21142126
private static ValueTask<object?> ExecuteValueTaskWithEmptyResult(ValueTask valueTask)
21152127
{
2128+
Debug.Assert(EmptyHttpResultInstance is not null, "The EmptyHttpResult type could not be found.");
21162129
static async ValueTask<object?> ExecuteAwaited(ValueTask task)
21172130
{
21182131
await task;
2119-
return EmptyHttpResult.Instance;
2132+
return EmptyHttpResultInstance;
21202133
}
21212134

21222135
if (valueTask.IsCompletedSuccessfully)
21232136
{
21242137
valueTask.GetAwaiter().GetResult();
2125-
return new ValueTask<object?>(EmptyHttpResult.Instance);
2138+
return new ValueTask<object?>(EmptyHttpResultInstance);
21262139
}
21272140

21282141
return ExecuteAwaited(valueTask);
@@ -2442,24 +2455,6 @@ private static void FormatTrackedParameters(RequestDelegateFactoryContext factor
24422455
}
24432456
}
24442457

2445-
// Due to cyclic references between Http.Extensions and
2446-
// Http.Results, we define our own instance of the `EmptyHttpResult`
2447-
// type here.
2448-
private sealed class EmptyHttpResult : IResult
2449-
{
2450-
private EmptyHttpResult()
2451-
{
2452-
}
2453-
2454-
public static EmptyHttpResult Instance { get; } = new();
2455-
2456-
/// <inheritdoc/>
2457-
public Task ExecuteAsync(HttpContext httpContext)
2458-
{
2459-
return Task.CompletedTask;
2460-
}
2461-
}
2462-
24632458
private sealed class RDFEndpointBuilder : EndpointBuilder
24642459
{
24652460
public RDFEndpointBuilder(IServiceProvider applicationServices)

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

+41-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using Microsoft.AspNetCore.Http.Features;
2424
using Microsoft.AspNetCore.Http.Json;
2525
using Microsoft.AspNetCore.Http.Metadata;
26+
using Microsoft.AspNetCore.Http.HttpResults;
2627
using Microsoft.AspNetCore.Routing.Patterns;
2728
using Microsoft.AspNetCore.Testing;
2829
using Microsoft.Extensions.DependencyInjection;
@@ -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);
@@ -6604,6 +6616,34 @@ public void Create_Populates_EndpointBuilderWithRequestDelegateAndMetadata()
66046616
Assert.Same(options.EndpointBuilder.Metadata, result.EndpointMetadata);
66056617
}
66066618

6619+
[Fact]
6620+
public async Task RDF_CanAssertOnEmptyResult()
6621+
{
6622+
var @delegate = (string name, HttpContext context) => context.Items.Add("param", name);
6623+
6624+
var result = RequestDelegateFactory.Create(@delegate, new RequestDelegateFactoryOptions()
6625+
{
6626+
EndpointBuilder = CreateEndpointBuilderFromFilterFactories(new List<Func<EndpointFilterFactoryContext, EndpointFilterDelegate, EndpointFilterDelegate>>()
6627+
{
6628+
(routeHandlerContext, next) => async (context) =>
6629+
{
6630+
var response = await next(context);
6631+
Assert.IsType<EmptyHttpResult>(response);
6632+
Assert.Same(Results.Empty, response);
6633+
return response;
6634+
}
6635+
}),
6636+
});
6637+
6638+
var httpContext = CreateHttpContext();
6639+
httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
6640+
{
6641+
["name"] = "Tester"
6642+
});
6643+
6644+
await result.RequestDelegate(httpContext);
6645+
}
6646+
66076647
private DefaultHttpContext CreateHttpContext()
66086648
{
66096649
var responseFeature = new TestHttpResponseFeature();

0 commit comments

Comments
 (0)