diff --git a/src/Http/Http.Abstractions/src/DefaultRouteHandlerInvocationContext.cs b/src/Http/Http.Abstractions/src/DefaultRouteHandlerInvocationContext.cs new file mode 100644 index 000000000000..e20f24678041 --- /dev/null +++ b/src/Http/Http.Abstractions/src/DefaultRouteHandlerInvocationContext.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Http; + +/// +/// Provides a default implementation for wrapping the and parameters +/// provided to a route handler. +/// +public sealed class DefaultRouteHandlerInvocationContext : RouteHandlerInvocationContext +{ + /// + /// Creates a new instance of the for a given request. + /// + /// The associated with the current request. + /// A list of parameters provided in the current request. + public DefaultRouteHandlerInvocationContext(HttpContext httpContext, params object[] arguments) + { + HttpContext = httpContext; + Arguments = arguments; + } + + /// + public override HttpContext HttpContext { get; } + + /// + public override IList Arguments { get; } + + /// + public override T GetArgument(int index) + { + return (T)Arguments[index]!; + } +} diff --git a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj index b8b80cd3f4a3..4c75d68b0ea1 100644 --- a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj +++ b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj @@ -30,6 +30,26 @@ Microsoft.AspNetCore.Http.HttpResponse + + + + + + TextTemplatingFileGenerator + RouteHandlerInvocationContextOfT.Generated.cs + + + + + + + + + + True + True + RouteHandlerInvocationContextOfT.Generated.tt + diff --git a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt index 20d456087930..54c1f4be51fc 100644 --- a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt @@ -3,6 +3,8 @@ *REMOVED*Microsoft.AspNetCore.Http.EndpointMetadataCollection.Enumerator.Current.get -> object? Microsoft.AspNetCore.Builder.EndpointBuilder.ServiceProvider.get -> System.IServiceProvider? Microsoft.AspNetCore.Builder.EndpointBuilder.ServiceProvider.set -> void +Microsoft.AspNetCore.Http.DefaultRouteHandlerInvocationContext +Microsoft.AspNetCore.Http.DefaultRouteHandlerInvocationContext.DefaultRouteHandlerInvocationContext(Microsoft.AspNetCore.Http.HttpContext! httpContext, params object![]! arguments) -> void Microsoft.AspNetCore.Http.EndpointMetadataCollection.Enumerator.Current.get -> object! Microsoft.AspNetCore.Http.EndpointMetadataCollection.GetRequiredMetadata() -> T! Microsoft.AspNetCore.Http.IRouteHandlerFilter.InvokeAsync(Microsoft.AspNetCore.Http.RouteHandlerInvocationContext! context, Microsoft.AspNetCore.Http.RouteHandlerFilterDelegate! next) -> System.Threading.Tasks.ValueTask @@ -14,9 +16,7 @@ Microsoft.AspNetCore.Http.RouteHandlerContext.MethodInfo.get -> System.Reflectio Microsoft.AspNetCore.Http.RouteHandlerContext.RouteHandlerContext(System.Reflection.MethodInfo! methodInfo, Microsoft.AspNetCore.Http.EndpointMetadataCollection! endpointMetadata) -> void Microsoft.AspNetCore.Http.RouteHandlerFilterDelegate Microsoft.AspNetCore.Http.RouteHandlerInvocationContext -Microsoft.AspNetCore.Http.RouteHandlerInvocationContext.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext! -Microsoft.AspNetCore.Http.RouteHandlerInvocationContext.Parameters.get -> System.Collections.Generic.IList! -Microsoft.AspNetCore.Http.RouteHandlerInvocationContext.RouteHandlerInvocationContext(Microsoft.AspNetCore.Http.HttpContext! httpContext, params object![]! parameters) -> void +Microsoft.AspNetCore.Http.RouteHandlerInvocationContext.RouteHandlerInvocationContext() -> void Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(Microsoft.AspNetCore.Routing.RouteValueDictionary? dictionary) -> void Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(System.Collections.Generic.IEnumerable>? values) -> void Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(System.Collections.Generic.IEnumerable>? values) -> void @@ -27,3 +27,9 @@ Microsoft.AspNetCore.Http.Metadata.IEndpointDescriptionMetadata Microsoft.AspNetCore.Http.Metadata.IEndpointDescriptionMetadata.Description.get -> string! Microsoft.AspNetCore.Http.Metadata.IEndpointSummaryMetadata Microsoft.AspNetCore.Http.Metadata.IEndpointSummaryMetadata.Summary.get -> string! +abstract Microsoft.AspNetCore.Http.RouteHandlerInvocationContext.Arguments.get -> System.Collections.Generic.IList! +abstract Microsoft.AspNetCore.Http.RouteHandlerInvocationContext.GetArgument(int index) -> T +abstract Microsoft.AspNetCore.Http.RouteHandlerInvocationContext.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext! +override Microsoft.AspNetCore.Http.DefaultRouteHandlerInvocationContext.Arguments.get -> System.Collections.Generic.IList! +override Microsoft.AspNetCore.Http.DefaultRouteHandlerInvocationContext.GetArgument(int index) -> T +override Microsoft.AspNetCore.Http.DefaultRouteHandlerInvocationContext.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext! diff --git a/src/Http/Http.Abstractions/src/RouteHandlerInvocationContext.cs b/src/Http/Http.Abstractions/src/RouteHandlerInvocationContext.cs index d7cfe600a760..2ed6755fdab6 100644 --- a/src/Http/Http.Abstractions/src/RouteHandlerInvocationContext.cs +++ b/src/Http/Http.Abstractions/src/RouteHandlerInvocationContext.cs @@ -4,32 +4,29 @@ namespace Microsoft.AspNetCore.Http; /// -/// Provides an abstraction for wrapping the and parameters +/// Provides an abstraction for wrapping the and arguments /// provided to a route handler. /// -public sealed class RouteHandlerInvocationContext +public abstract class RouteHandlerInvocationContext { - /// - /// Creates a new instance of the for a given request. - /// - /// The associated with the current request. - /// A list of parameters provided in the current request. - public RouteHandlerInvocationContext(HttpContext httpContext, params object[] parameters) - { - HttpContext = httpContext; - Parameters = parameters; - } - /// /// The associated with the current request being processed by the filter. /// - public HttpContext HttpContext { get; } + public abstract HttpContext HttpContext { get; } /// - /// A list of parameters provided in the current request to the filter. + /// A list of arguments provided in the current request to the filter. /// - /// This list is not read-only to permit modifying of existing parameters by filters. + /// This list is not read-only to permit modifying of existing arguments by filters. /// /// - public IList Parameters { get; } + public abstract IList Arguments { get; } + + /// + /// Retrieve the argument given its position in the argument list. + /// + /// The of the resolved argument. + /// An integer representing the position of the argument in the argument list. + /// The argument at a given . + public abstract T GetArgument(int index); } diff --git a/src/Http/Http.Abstractions/src/RouteHandlerInvocationContextOfT.Generated.cs b/src/Http/Http.Abstractions/src/RouteHandlerInvocationContextOfT.Generated.cs new file mode 100644 index 000000000000..da7dd32ea3e4 --- /dev/null +++ b/src/Http/Http.Abstractions/src/RouteHandlerInvocationContextOfT.Generated.cs @@ -0,0 +1,1385 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +#nullable enable + +using System.Collections; +using System.CodeDom.Compiler; + +namespace Microsoft.AspNetCore.Http; + +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0) + { + HttpContext = httpContext; + Arg0 = arg0; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + + public int Count => 1; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + + public int Count => 2; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + + public int Count => 3; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + + public int Count => 4; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + + public int Count => 5; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + + public int Count => 6; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + Arg6 = arg6; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + 6 => Arg6, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + case 6: + Arg6 = (T6)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + public T6 Arg6 { get; set; } + + public int Count => 7; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + 6 => (T)(object)Arg6!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + Arg6 = arg6; + Arg7 = arg7; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + 6 => Arg6, + 7 => Arg7, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + case 6: + Arg6 = (T6)(object?)value!; + break; + case 7: + Arg7 = (T7)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + public T6 Arg6 { get; set; } + public T7 Arg7 { get; set; } + + public int Count => 8; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + 6 => (T)(object)Arg6!, + 7 => (T)(object)Arg7!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + Arg6 = arg6; + Arg7 = arg7; + Arg8 = arg8; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + 6 => Arg6, + 7 => Arg7, + 8 => Arg8, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + case 6: + Arg6 = (T6)(object?)value!; + break; + case 7: + Arg7 = (T7)(object?)value!; + break; + case 8: + Arg8 = (T8)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + public T6 Arg6 { get; set; } + public T7 Arg7 { get; set; } + public T8 Arg8 { get; set; } + + public int Count => 9; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + 6 => (T)(object)Arg6!, + 7 => (T)(object)Arg7!, + 8 => (T)(object)Arg8!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + Arg6 = arg6; + Arg7 = arg7; + Arg8 = arg8; + Arg9 = arg9; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + 6 => Arg6, + 7 => Arg7, + 8 => Arg8, + 9 => Arg9, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + case 6: + Arg6 = (T6)(object?)value!; + break; + case 7: + Arg7 = (T7)(object?)value!; + break; + case 8: + Arg8 = (T8)(object?)value!; + break; + case 9: + Arg9 = (T9)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + public T6 Arg6 { get; set; } + public T7 Arg7 { get; set; } + public T8 Arg8 { get; set; } + public T9 Arg9 { get; set; } + + public int Count => 10; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + 6 => (T)(object)Arg6!, + 7 => (T)(object)Arg7!, + 8 => (T)(object)Arg8!, + 9 => (T)(object)Arg9!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} diff --git a/src/Http/Http.Abstractions/src/RouteHandlerInvocationContextOfT.Generated.tt b/src/Http/Http.Abstractions/src/RouteHandlerInvocationContextOfT.Generated.tt new file mode 100644 index 000000000000..6fcbe7075cde --- /dev/null +++ b/src/Http/Http.Abstractions/src/RouteHandlerInvocationContextOfT.Generated.tt @@ -0,0 +1,130 @@ +<#@ template language="C#" #> +<#@ output extension=".cs" #> +<#@ assembly name="System.Core.dll" #> +<#@ import namespace="System.Linq" #> +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +<# +int[] arities = Enumerable.Range(1, 10).ToArray(); +#> +#nullable enable + +using System.Collections; +using System.CodeDom.Compiler; + +namespace Microsoft.AspNetCore.Http; + +<# foreach (var arity in arities) { #> +[GeneratedCode("TextTemplatingFileGenerator", "")] +internal sealed class RouteHandlerInvocationContext<<# foreach (var argumentCount in Enumerable.Range(0, arity)) { #>T<#=argumentCount#><# if (argumentCount < arity - 1) { #>, <# } #><# } #>> : RouteHandlerInvocationContext, IList +{ + internal RouteHandlerInvocationContext(HttpContext httpContext, <# foreach (var argumentCount in Enumerable.Range(0, arity)) { #>T<#=argumentCount#> arg<#=argumentCount#><# if (argumentCount < arity - 1) { #>, <# } #><# } #>) + { + HttpContext = httpContext; +<# foreach (var argumentCount in Enumerable.Range(0, arity)) { #> Arg<#=argumentCount#> = arg<#=argumentCount#>; +<# } #> + } + + public object? this[int index] + { + get => index switch + { +<# foreach (var argumentCount in Enumerable.Range(0, arity)) { #> <#=argumentCount#> => Arg<#=argumentCount#>, +<# } #> + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { +<# foreach (var argumentCount in Enumerable.Range(0, arity)) { #> case <#=argumentCount#>: + Arg<#=argumentCount#> = (T<#=argumentCount#>)(object?)value!; + break; +<# } #> + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + <# foreach (var argumentCount in Enumerable.Range(0, arity)) { #>public T<#=argumentCount#> Arg<#=argumentCount#> { get; set; } + <# } #> + + public int Count => <#=arity#>; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { +<# foreach (var argumentCount in Enumerable.Range(0, arity)) { #> <#=argumentCount#> => (T)(object)Arg<#=argumentCount#>!, +<# } #> + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} +<# } #> diff --git a/src/Http/Http.Abstractions/test/Microsoft.AspNetCore.Http.Abstractions.Tests.csproj b/src/Http/Http.Abstractions/test/Microsoft.AspNetCore.Http.Abstractions.Tests.csproj index b4bbecb268fe..f35cfb3b4398 100644 --- a/src/Http/Http.Abstractions/test/Microsoft.AspNetCore.Http.Abstractions.Tests.csproj +++ b/src/Http/Http.Abstractions/test/Microsoft.AspNetCore.Http.Abstractions.Tests.csproj @@ -12,6 +12,13 @@ + + + + + + + diff --git a/src/Http/Http.Abstractions/test/RouteHandlerInvocationContextOfTTests.cs b/src/Http/Http.Abstractions/test/RouteHandlerInvocationContextOfTTests.cs new file mode 100644 index 000000000000..f42f8d53c580 --- /dev/null +++ b/src/Http/Http.Abstractions/test/RouteHandlerInvocationContextOfTTests.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using Mono.TextTemplating; + +namespace Microsoft.AspNetCore.Http.Abstractions.Tests; + +public class RouteHandlerInvocationContextOfTTests +{ + [Fact] + public void ProhibitsActionsThatModifyListSize() + { + var context = new RouteHandlerInvocationContext(new DefaultHttpContext(), "This is a test", 42, false); + Assert.Throws(() => context.Add("string")); + Assert.Throws(() => context.Insert(0, "string")); + Assert.Throws(() => context.RemoveAt(0)); + Assert.Throws(() => context.Remove("string")); + Assert.Throws(() => context.Clear()); + } + + [Fact] + public void ThrowsExceptionForInvalidCastOnGetArgument() + { + var context = new RouteHandlerInvocationContext(new DefaultHttpContext(), "This is a test", 42, false, new Todo()); + Assert.Throws(() => context.GetArgument(1)); + Assert.Throws(() => context.GetArgument(0)); + Assert.Throws(() => context.GetArgument(3)); + var todo = context.GetArgument(3); + Assert.IsType(todo); + } + + [Fact] + public void SetterAllowsInPlaceModificationOfParameters() + { + var context = new RouteHandlerInvocationContext(new DefaultHttpContext(), "This is a test", 42, false, new Todo()); + context[0] = "Foo"; + Assert.Equal("Foo", context.GetArgument(0)); + } + + [Fact] + public void SetterDoesNotAllowModificationOfParameterType() + { + var context = new RouteHandlerInvocationContext(new DefaultHttpContext(), "This is a test", 42, false, new Todo()); + Assert.Throws(() => context[0] = 4); + } + + [Fact] + public void AllowsEnumerationOfParameters() + { + var context = new RouteHandlerInvocationContext(new DefaultHttpContext(), "This is a test", 42, false, new Todo()); + var enumeratedCount = 0; + foreach (var parameter in context) + { + Assert.NotNull(parameter); + enumeratedCount++; + } + Assert.Equal(4, enumeratedCount); + } + + // Test for https://github.com/dotnet/aspnetcore/issues/41489 + [Fact] + public void HandlesMismatchedNullabilityOnTypeParams() + { + var context = new RouteHandlerInvocationContext(new DefaultHttpContext(), null, null, null, null); + // Mismatched reference types will resolve as null + Assert.Null(context.GetArgument(0)); + Assert.Null(context.GetArgument(3)); + // Mismatched value types will throw + Assert.Throws(() => context.GetArgument(1)); + Assert.Throws(() => context.GetArgument(2)); + } + + [Fact] + public void GeneratedCodeIsUpToDate() + { + var currentContentPath = Path.Combine(AppContext.BaseDirectory, "Shared", "GeneratedContent", "RouteHandlerInvocationContextOfT.Generated.cs"); + var templatePath = Path.Combine(AppContext.BaseDirectory, "Shared", "GeneratedContent", "RouteHandlerInvocationContextOfT.Generated.tt"); + + var generator = new TemplateGenerator(); + var compiledTemplate = generator.CompileTemplate(File.ReadAllText(templatePath)); + + var generatedContent = compiledTemplate.Process(); + var currentContent = File.ReadAllText(currentContentPath); + + Assert.Equal(currentContent, generatedContent); + } + + interface ITodo { } + class Todo : ITodo { } +} diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs index 4406099180a0..94d99f3a09e7 100644 --- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs +++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs @@ -94,9 +94,9 @@ public static partial class RequestDelegateFactory private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null)); private static readonly UnaryExpression TempSourceStringIsNotNullOrEmptyExpr = Expression.Not(Expression.Call(StringIsNullOrEmptyMethod, TempSourceStringExpr)); - private static readonly ConstructorInfo RouteHandlerInvocationContextConstructor = typeof(RouteHandlerInvocationContext).GetConstructor(new[] { typeof(HttpContext), typeof(object[]) })!; + private static readonly ConstructorInfo DefaultRouteHandlerInvocationContextConstructor = typeof(DefaultRouteHandlerInvocationContext).GetConstructor(new[] { typeof(HttpContext), typeof(object[]) })!; + private static readonly MethodInfo RouteHandlerInvocationContextGetArgument = typeof(RouteHandlerInvocationContext).GetMethod(nameof(RouteHandlerInvocationContext.GetArgument))!; private static readonly ParameterExpression FilterContextExpr = Expression.Parameter(typeof(RouteHandlerInvocationContext), "context"); - private static readonly MemberExpression FilterContextParametersExpr = Expression.Property(FilterContextExpr, typeof(RouteHandlerInvocationContext).GetProperty(nameof(RouteHandlerInvocationContext.Parameters))!); private static readonly MemberExpression FilterContextHttpContextExpr = Expression.Property(FilterContextExpr, typeof(RouteHandlerInvocationContext).GetProperty(nameof(RouteHandlerInvocationContext.HttpContext))!); private static readonly MemberExpression FilterContextHttpContextResponseExpr = Expression.Property(FilterContextHttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!); private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression.Property(FilterContextHttpContextResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!); @@ -234,14 +234,13 @@ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions var filterPipeline = CreateFilterPipeline(methodInfo, targetExpression, factoryContext, targetFactory); Expression>> invokePipeline = (context) => filterPipeline(context); returnType = typeof(ValueTask); - // var filterContext = new RouteHandlerInvocationContext(httpContext, new[] { (object)name_local, (object)int_local }); + // var filterContext = new RouteHandlerInvocationContext(httpContext, name_local, int_local); // invokePipeline.Invoke(filterContext); factoryContext.MethodCall = Expression.Block( new[] { InvokedFilterContextExpr }, Expression.Assign( InvokedFilterContextExpr, - Expression.New(RouteHandlerInvocationContextConstructor, - new Expression[] { HttpContextExpr, Expression.NewArrayInit(typeof(object), factoryContext.BoxedArgs) })), + CreateRouteHandlerInvocationContextBase(factoryContext)), Expression.Invoke(invokePipeline, InvokedFilterContextExpr) ); } @@ -274,10 +273,10 @@ private static RouteHandlerFilterDelegate CreateFilterPipeline(MethodInfo method // When `handler` returns an object, we generate the following wrapper // to convert it to `ValueTask` as expected in the filter // pipeline. - // ValueTask.FromResult(handler((string)context.Parameters[0], (int)context.Parameters[1])); + // ValueTask.FromResult(handler(RouteHandlerInvocationContext.GetArgument(0), RouteHandlerInvocationContext.GetArgument(1))); // When the `handler` is a generic Task or ValueTask we await the task and // create a `ValueTask from the resulting value. - // new ValueTask(await handler((string)context.Parameters[0], (int)context.Parameters[1])); + // new ValueTask(await handler(RouteHandlerInvocationContext.GetArgument(0), RouteHandlerInvocationContext.GetArgument(1))); // When the `handler` returns a void or a void-returning Task, then we return an EmptyHttpResult // to as a ValueTask // } @@ -380,6 +379,51 @@ private static Expression MapHandlerReturnTypeToValueTask(Expression methodCall, return ExecuteAwaited(task); } + private static Expression CreateRouteHandlerInvocationContextBase(FactoryContext factoryContext) + { + // In the event that a constructor matching the arity of the + // provided parameters is not found, we fall back to using the + // non-generic implementation of RouteHandlerInvocationContext. + var fallbackConstruction = Expression.New( + DefaultRouteHandlerInvocationContextConstructor, + new Expression[] { HttpContextExpr, Expression.NewArrayInit(typeof(object), factoryContext.BoxedArgs) }); + + var arguments = new Expression[factoryContext.ArgumentExpressions.Length + 1]; + arguments[0] = HttpContextExpr; + factoryContext.ArgumentExpressions.CopyTo(arguments, 1); + + var constructorType = factoryContext.ArgumentTypes?.Length switch + { + 1 => typeof(RouteHandlerInvocationContext<>), + 2 => typeof(RouteHandlerInvocationContext<,>), + 3 => typeof(RouteHandlerInvocationContext<,,>), + 4 => typeof(RouteHandlerInvocationContext<,,,>), + 5 => typeof(RouteHandlerInvocationContext<,,,,>), + 6 => typeof(RouteHandlerInvocationContext<,,,,,>), + 7 => typeof(RouteHandlerInvocationContext<,,,,,,>), + 8 => typeof(RouteHandlerInvocationContext<,,,,,,,>), + 9 => typeof(RouteHandlerInvocationContext<,,,,,,,,>), + 10 => typeof(RouteHandlerInvocationContext<,,,,,,,,,>), + _ => typeof(DefaultRouteHandlerInvocationContext) + }; + + if (constructorType.IsGenericType) + { + var constructor = constructorType.MakeGenericType(factoryContext.ArgumentTypes!).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).SingleOrDefault(); + if (constructor == null) + { + // new RouteHandlerInvocationContext(httpContext, (object)name_local, (object)int_local); + return fallbackConstruction; + } + + // new RouteHandlerInvocationContext(httpContext, name_local, int_local); + return Expression.New(constructor, arguments); + } + + // new RouteHandlerInvocationContext(httpContext, (object)name_local, (object)int_local); + return fallbackConstruction; + } + private static void AddTypeProvidedMetadata(MethodInfo methodInfo, List metadata, IServiceProvider? services) { object?[]? invokeArgs = null; @@ -456,19 +500,22 @@ private static Expression[] CreateArguments(ParameterInfo[]? parameters, Factory var args = new Expression[parameters.Length]; + factoryContext.ArgumentTypes = new Type[parameters.Length]; + factoryContext.ArgumentExpressions = new Expression[parameters.Length]; + factoryContext.BoxedArgs = new Expression[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) { args[i] = CreateArgument(parameters[i], factoryContext); // Register expressions containing the boxed and unboxed variants // of the route handler's arguments for use in RouteHandlerInvocationContext // construction and route handler invocation. - // (string)context.Parameters[0]; - factoryContext.ContextArgAccess.Add( - Expression.Convert( - Expression.Property(FilterContextParametersExpr, "Item", Expression.Constant(i)), - parameters[i].ParameterType)); - // (object)name_local - factoryContext.BoxedArgs.Add(Expression.Convert(args[i], typeof(object))); + // context.GetArgument(0) + factoryContext.ContextArgAccess.Add(Expression.Call(FilterContextExpr, RouteHandlerInvocationContextGetArgument.MakeGenericMethod(parameters[i].ParameterType), Expression.Constant(i))); + // (string, name_local), (int, int_local) + factoryContext.ArgumentTypes[i] = parameters[i].ParameterType; + factoryContext.ArgumentExpressions[i] = args[i]; + factoryContext.BoxedArgs[i] = Expression.Convert(args[i], typeof(object)); } if (factoryContext.HasInferredBody && factoryContext.DisableInferredFromBody) @@ -1951,7 +1998,9 @@ private class FactoryContext // Properties for constructing and managing filters public List ContextArgAccess { get; } = new(); public Expression? MethodCall { get; set; } - public List BoxedArgs { get; } = new(); + public Type[] ArgumentTypes { get; set; } = Array.Empty(); + public Expression[] ArgumentExpressions { get; set; } = Array.Empty(); + public Expression[] BoxedArgs { get; set; } = Array.Empty(); public List>? Filters { get; init; } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs index 91bbe4907cc9..bb070be027ba 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs @@ -4238,7 +4238,7 @@ string HelloName(string name) { (routeHandlerContext, next) => async (context) => { - context.Parameters[0] = context.Parameters[0] != null ? $"{((string)context.Parameters[0]!)}Prefix" : "NULL"; + context.Arguments[0] = context.Arguments[0] != null ? $"{((string)context.Arguments[0]!)}Prefix" : "NULL"; return await next(context); } } @@ -4438,12 +4438,12 @@ string HelloName(string name, int age) { (routeHandlerContext, next) => async (context) => { - context.Parameters[1] = ((int)context.Parameters[1]!) + 2; + context.Arguments[1] = ((int)context.Arguments[1]!) + 2; return await next(context); }, (routeHandlerContext, next) => async (context) => { - foreach (var parameter in context.Parameters) + foreach (var parameter in context.Arguments) { Log(parameter!.ToString() ?? "no arg"); } @@ -4492,7 +4492,7 @@ string HelloName(string name) { if (isInt) { - context.Parameters[1] = ((int)context.Parameters[1]!) + 2; + context.Arguments[1] = ((int)context.Arguments[1]!) + 2; return await next(context); } return "Is not an int."; @@ -4592,9 +4592,9 @@ string PrintTodo(Todo todo) { (routeHandlerContext, next) => async (context) => { - Todo originalTodo = (Todo)context.Parameters[0]!; + Todo originalTodo = (Todo)context.Arguments[0]!; originalTodo!.IsComplete = !originalTodo.IsComplete; - context.Parameters[0] = originalTodo; + context.Arguments[0] = originalTodo; return await next(context); } } @@ -4685,7 +4685,8 @@ string HelloName(string name) }, (RouteHandlerContext, next) => async (context) => { - context.Parameters[0] = context.Parameters[0] != null ? $"{((string)context.Parameters[0]!)}Prefix" : "NULL"; + var newValue = $"{context.GetArgument(0)}Prefix"; + context.Arguments[0] = newValue; return await next(context); } } @@ -4983,6 +4984,68 @@ public async Task CanInvokeFilter_OnHandlerReturningTasksOfStruct(Delegate @dele Assert.Equal("Test todo", deserializedResponseBody.Name); } + [Fact] + public async Task RequestDelegateFactory_CanApplyFiltersOnHandlerWithManyArguments() + { + // Arrange + string HelloName(int? one, string? two, int? three, string? four, int? five, bool? six, string? seven, string? eight, int? nine, string? ten, int? eleven) + { + return "Too many arguments!"; + }; + + var httpContext = CreateHttpContext(); + + var responseBodyStream = new MemoryStream(); + httpContext.Response.Body = responseBodyStream; + + // Act + var factoryResult = RequestDelegateFactory.Create(HelloName, new RequestDelegateFactoryOptions() + { + RouteHandlerFilterFactories = new List>() + { + (routeHandlerContext, next) => async (context) => + { + Assert.IsType(context); + Assert.Equal(11, context.Arguments.Count); + return await next(context); + } + } + }); + var requestDelegate = factoryResult.RequestDelegate; + await requestDelegate(httpContext); + } + + [Fact] + public async Task RequestDelegateFactory_CanApplyFiltersOnHandlerWithNoArguments() + { + // Arrange + string HelloName() + { + return "No arguments!"; + }; + + var httpContext = CreateHttpContext(); + + var responseBodyStream = new MemoryStream(); + httpContext.Response.Body = responseBodyStream; + + // Act + var factoryResult = RequestDelegateFactory.Create(HelloName, new RequestDelegateFactoryOptions() + { + RouteHandlerFilterFactories = new List>() + { + (routeHandlerContext, next) => async (context) => + { + Assert.IsType(context); + Assert.Equal(0, context.Arguments.Count); + return await next(context); + } + } + }); + var requestDelegate = factoryResult.RequestDelegate; + await requestDelegate(httpContext); + } + [Fact] public void Create_AddsDelegateMethodInfo_AsMetadata() { diff --git a/src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs index 09e0e7e87392..4f7db98695cf 100644 --- a/src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs @@ -892,7 +892,7 @@ public static object[][] AddFiltersByDelegateData void WithFilter(RouteHandlerBuilder builder) => builder.AddFilter(async (context, next) => { - context.Parameters[0] = ((int)context.Parameters[0]!) + 1; + context.Arguments[0] = ((int)context.Arguments[0]!) + 1; return await next(context); }); @@ -902,7 +902,7 @@ void WithFilterFactory(RouteHandlerBuilder builder) => Assert.NotNull(routeHandlerContext.MethodInfo); Assert.NotNull(routeHandlerContext.MethodInfo.DeclaringType); Assert.Equal("RouteHandlerEndpointRouteBuilderExtensionsTest", routeHandlerContext.MethodInfo.DeclaringType?.Name); - context.Parameters[0] = ((int)context.Parameters[0]!) + 1; + context.Arguments[0] = context.GetArgument(0) + 1; return await next(context); }); @@ -995,7 +995,7 @@ class IncrementArgFilter : IRouteHandlerFilter { public async ValueTask InvokeAsync(RouteHandlerInvocationContext context, RouteHandlerFilterDelegate next) { - context.Parameters[0] = ((int)context.Parameters[0]!) + 1; + context.Arguments[0] = ((int)context.Arguments[0]!) + 1; return await next(context); } }