Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 11 additions & 24 deletions src/Http/Http.Extensions/src/RequestDelegateFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,11 @@ public static partial class RequestDelegateFactory
private static readonly MemberExpression FormFilesExpr = Expression.Property(FormExpr, typeof(IFormCollection).GetProperty(nameof(IFormCollection.Files))!);
private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
private static readonly MemberExpression CompletedTaskExpr = Expression.Property(null, (PropertyInfo)GetMemberInfo<Func<Task>>(() => Task.CompletedTask));
private static readonly NewExpression EmptyHttpResultValueTaskExpr = Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(EmptyHttpResult) })!, Expression.Property(null, typeof(EmptyHttpResult), nameof(EmptyHttpResult.Instance)));
Comment thread
captainsafia marked this conversation as resolved.

// Due to https://github.com/dotnet/aspnetcore/issues/41330 we cannot reference the EmptyHttpResult type
// but users still need to assert on it as in https://github.com/dotnet/aspnetcore/issues/45063
// so we temporarily work around this here by using reflection to get the actual type.
private static readonly NewExpression EmptyHttpResultValueTaskExpr = Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(IResult) })!, Expression.Property(null, Type.GetType("Microsoft.AspNetCore.Http.Results, Microsoft.AspNetCore.Http.Results")!.GetProperty("Empty")!));
private static readonly object? EmptyHttpResultInstance = Type.GetType("Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult, Microsoft.AspNetCore.Http.Results")!.GetProperty("Instance")!.GetValue(null, null);
Comment thread
mitchdenny marked this conversation as resolved.
private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache.TempSourceStringExpr;
private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression.NotEqual(TempSourceStringExpr, Expression.Constant(null));
private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null));
Expand Down Expand Up @@ -2154,32 +2157,34 @@ static async Task ExecuteAwaited(ValueTask task)

private static ValueTask<object?> ExecuteTaskWithEmptyResult(Task task)
{
Debug.Assert(EmptyHttpResultInstance is not null);
static async ValueTask<object?> ExecuteAwaited(Task task)
{
await task;
return EmptyHttpResult.Instance;
return EmptyHttpResultInstance;
}

if (task.IsCompletedSuccessfully)
{
return new ValueTask<object?>(EmptyHttpResult.Instance);
return new ValueTask<object?>(EmptyHttpResultInstance);
}

return ExecuteAwaited(task);
}

private static ValueTask<object?> ExecuteValueTaskWithEmptyResult(ValueTask valueTask)
{
Debug.Assert(EmptyHttpResultInstance is not null);
static async ValueTask<object?> ExecuteAwaited(ValueTask task)
{
await task;
return EmptyHttpResult.Instance;
return EmptyHttpResultInstance;
}

if (valueTask.IsCompletedSuccessfully)
{
valueTask.GetAwaiter().GetResult();
return new ValueTask<object?>(EmptyHttpResult.Instance);
return new ValueTask<object?>(EmptyHttpResultInstance);
}

return ExecuteAwaited(valueTask);
Expand Down Expand Up @@ -2507,24 +2512,6 @@ private static void FormatTrackedParameters(RequestDelegateFactoryContext factor
}
}

// Due to cyclic references between Http.Extensions and
// Http.Results, we define our own instance of the `EmptyHttpResult`
// type here.
private sealed class EmptyHttpResult : IResult
{
private EmptyHttpResult()
{
}

public static EmptyHttpResult Instance { get; } = new();

/// <inheritdoc/>
public Task ExecuteAsync(HttpContext httpContext)
{
return Task.CompletedTask;
}
}

private sealed class RdfEndpointBuilder : EndpointBuilder
{
public RdfEndpointBuilder(IServiceProvider applicationServices)
Expand Down
42 changes: 41 additions & 1 deletion src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Routing.Patterns;
Expand Down Expand Up @@ -97,7 +98,18 @@ public async Task RequestDelegateInvokesAction(Delegate @delegate)
{
var httpContext = CreateHttpContext();

var factoryResult = RequestDelegateFactory.Create(@delegate);
var factoryResult = RequestDelegateFactory.Create(@delegate, new RequestDelegateFactoryOptions()
{
EndpointBuilder = CreateEndpointBuilderFromFilterFactories(new List<Func<EndpointFilterFactoryContext, EndpointFilterDelegate, EndpointFilterDelegate>>()
{
(routeHandlerContext, next) => async (context) =>
{
var response = await next(context);
Assert.IsType<EmptyHttpResult>(response);
return response;
}
}),
});
var requestDelegate = factoryResult.RequestDelegate;

await requestDelegate(httpContext);
Expand Down Expand Up @@ -7075,6 +7087,34 @@ public void Create_Populates_EndpointBuilderWithRequestDelegateAndMetadata()
Assert.Same(options.EndpointBuilder.Metadata, result.EndpointMetadata);
}

[Fact]
public async Task RDF_CanAssertOnEmptyResult()
{
var @delegate = (string name, HttpContext context) => context.Items.Add("param", name);

var result = RequestDelegateFactory.Create(@delegate, new RequestDelegateFactoryOptions()
{
EndpointBuilder = CreateEndpointBuilderFromFilterFactories(new List<Func<EndpointFilterFactoryContext, EndpointFilterDelegate, EndpointFilterDelegate>>()
{
(routeHandlerContext, next) => async (context) =>
{
var response = await next(context);
Assert.IsType<EmptyHttpResult>(response);
Comment thread
captainsafia marked this conversation as resolved.
Assert.Same(Results.Empty, response);
return response;
}
}),
});

var httpContext = CreateHttpContext();
httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
["name"] = "Tester"
});

await result.RequestDelegate(httpContext);
}

private class ParameterListRequiredStringFromDifferentSources
{
public HttpContext? HttpContext { get; set; }
Expand Down