Skip to content

Commit

Permalink
Merge in 'release/7.0' changes
Browse files Browse the repository at this point in the history
  • Loading branch information
dotnet-bot committed Jan 12, 2023
2 parents 1256325 + 65330ff commit 215879c
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 25 deletions.
43 changes: 19 additions & 24 deletions src/Http/Http.Extensions/src/RequestDelegateFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,17 @@ 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)));

// 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 object? EmptyHttpResultInstance = Type.GetType("Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult, Microsoft.AspNetCore.Http.Results")?.GetProperty("Instance")?.GetValue(null, null);
#if DEBUG
private static readonly NewExpression EmptyHttpResultValueTaskExpr = EmptyHttpResultInstance is not null
? Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(IResult) })!, Expression.Constant(EmptyHttpResultInstance))
: throw new UnreachableException("The EmptyHttpResult type could not be found.");
#else
private static readonly NewExpression EmptyHttpResultValueTaskExpr = Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(IResult) })!, Expression.Constant(EmptyHttpResultInstance));
#endif
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 @@ -389,6 +398,7 @@ private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInf
private static EndpointFilterDelegate? CreateFilterPipeline(MethodInfo methodInfo, Expression? targetExpression, RequestDelegateFactoryContext factoryContext, Expression<Func<HttpContext, object?>>? targetFactory)
{
Debug.Assert(factoryContext.EndpointBuilder.FilterFactories.Count > 0);
Debug.Assert(EmptyHttpResultInstance is not null, "The EmptyHttpResult type could not be found.");
// httpContext.Response.StatusCode >= 400
// ? Task.CompletedTask
// : {
Expand Down Expand Up @@ -453,6 +463,7 @@ private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInf

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

private static ValueTask<object?> ExecuteTaskWithEmptyResult(Task task)
{
Debug.Assert(EmptyHttpResultInstance is not null, "The EmptyHttpResult type could not be found.");
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, "The EmptyHttpResult type could not be found.");
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 @@ -2442,24 +2455,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 @@ -23,6 +23,7 @@
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
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 @@ -6604,6 +6616,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);
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 DefaultHttpContext CreateHttpContext()
{
var responseFeature = new TestHttpResponseFeature();
Expand Down

0 comments on commit 215879c

Please sign in to comment.