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);
}
}