|
// 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.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Http.Extensions.Tests;
public class ParameterBindingMethodCacheTests
{
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(double))]
[InlineData(typeof(float))]
[InlineData(typeof(Half))]
[InlineData(typeof(short))]
[InlineData(typeof(long))]
[InlineData(typeof(IntPtr))]
[InlineData(typeof(sbyte))]
[InlineData(typeof(ushort))]
[InlineData(typeof(uint))]
[InlineData(typeof(ulong))]
public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCulture(Type type)
{
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
Assert.NotNull(methodFound);
var call = methodFound!(Expression.Variable(type, "parsedValue"), Expression.Constant(CultureInfo.InvariantCulture)) as MethodCallExpression;
Assert.NotNull(call);
var parameters = call!.Method.GetParameters();
Assert.Equal(4, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
Assert.Equal(typeof(NumberStyles), parameters[1].ParameterType);
Assert.Equal(typeof(IFormatProvider), parameters[2].ParameterType);
Assert.True(parameters[3].IsOut);
}
[Fact]
public void FindUriTryCreateStringMethod_ReturnsTheExpectedUriTryCreateMethod()
{
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(typeof(Uri));
Assert.NotNull(methodFound);
var call = methodFound!(Expression.Variable(typeof(Uri), "parsedValue"), Expression.Constant(UriKind.RelativeOrAbsolute)) as MethodCallExpression;
Assert.NotNull(call);
var parameters = call!.Method.GetParameters();
Assert.Equal(3, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
Assert.Equal(typeof(UriKind), parameters[1].ParameterType);
Assert.True(parameters[2].IsOut);
}
[Theory]
[InlineData(typeof(DateTime))]
[InlineData(typeof(DateOnly))]
[InlineData(typeof(DateTimeOffset))]
[InlineData(typeof(TimeOnly))]
[InlineData(typeof(TimeSpan))]
public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCultureDateType(Type type)
{
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
Assert.NotNull(methodFound);
var call = methodFound!(Expression.Variable(type, "parsedValue"), Expression.Constant(CultureInfo.InvariantCulture)) as MethodCallExpression;
Assert.NotNull(call);
var parameters = call!.Method.GetParameters();
if (@type == typeof(TimeSpan))
{
Assert.Equal(3, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
Assert.Equal(typeof(IFormatProvider), parameters[1].ParameterType);
Assert.True(parameters[2].IsOut);
}
else
{
Assert.Equal(4, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
Assert.Equal(typeof(IFormatProvider), parameters[1].ParameterType);
Assert.Equal(typeof(DateTimeStyles), parameters[2].ParameterType);
Assert.True(parameters[3].IsOut);
}
}
[Theory]
[InlineData(typeof(TryParseStringRecord))]
[InlineData(typeof(TryParseStringStruct))]
[InlineData(typeof(TryParseInheritClassWithFormatProvider))]
[InlineData(typeof(TryParseFromInterfaceWithFormatProvider))]
public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCultureCustomType(Type type)
{
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
Assert.NotNull(methodFound);
var call = methodFound!(Expression.Variable(type, "parsedValue"), Expression.Constant(CultureInfo.InvariantCulture)) as MethodCallExpression;
Assert.NotNull(call);
var parameters = call!.Method.GetParameters();
Assert.Equal(3, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
Assert.Equal(typeof(IFormatProvider), parameters[1].ParameterType);
Assert.True(parameters[2].IsOut);
Assert.True(((call.Arguments[1] as ConstantExpression)!.Value as CultureInfo)!.Equals(CultureInfo.InvariantCulture));
}
[Theory]
[InlineData(typeof(TryParseNoFormatProviderRecord))]
[InlineData(typeof(TryParseNoFormatProviderStruct))]
[InlineData(typeof(TryParseInheritClass))]
[InlineData(typeof(TryParseFromInterface))]
[InlineData(typeof(TryParseFromGrandparentInterface))]
[InlineData(typeof(TryParseDirectlyAndFromInterface))]
[InlineData(typeof(TryParseFromClassAndInterface))]
public void FindTryParseMethod_WithNoFormatProvider(Type type)
{
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
Assert.NotNull(methodFound);
var call = methodFound!(Expression.Variable(type, "parsedValue"), Expression.Constant(CultureInfo.InvariantCulture)) as MethodCallExpression;
Assert.NotNull(call);
var parameters = call!.Method.GetParameters();
Assert.Equal(2, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
Assert.True(parameters[1].IsOut);
}
public static IEnumerable<object[]> TryParseStringParameterInfoData
{
get
{
return new[]
{
new[]
{
GetFirstParameter((TryParseStringRecord arg) => TryParseStringRecordMethod(arg)),
},
new[]
{
GetFirstParameter((TryParseStringStruct arg) => TryParseStringStructMethod(arg)),
},
new[]
{
GetFirstParameter((TryParseStringStruct? arg) => TryParseStringNullableStructMethod(arg)),
},
};
}
}
[Theory]
[MemberData(nameof(TryParseStringParameterInfoData))]
public void HasTryParseStringMethod_ReturnsTrueWhenMethodExists(ParameterInfo parameterInfo)
{
Assert.True(new ParameterBindingMethodCache().HasTryParseMethod(parameterInfo.ParameterType));
}
[Fact]
public void FindTryParseStringMethod_FindsExplicitlyImplementedIParsable()
{
var type = typeof(TodoWithExplicitIParsable);
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(type);
Assert.NotNull(methodFound);
}
[Fact]
public void FindTryParseStringMethod_WorksForEnums()
{
var type = typeof(Choice);
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(type);
Assert.NotNull(methodFound);
var call = methodFound!(Expression.Variable(type, "parsedValue"), Expression.Constant(CultureInfo.InvariantCulture)) as MethodCallExpression;
Assert.NotNull(call);
var method = call!.Method;
var parameters = method.GetParameters();
// By default, we use Enum.TryParse<T>
Assert.True(method.IsGenericMethod);
Assert.Equal(2, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
Assert.True(parameters[1].IsOut);
}
[Fact]
public void FindTryParseStringMethod_WorksForEnumsWhenNonGenericEnumParseIsUsed()
{
var type = typeof(Choice);
var cache = new ParameterBindingMethodCache(preferNonGenericEnumParseOverload: true);
var methodFound = cache.FindTryParseMethod(type);
Assert.NotNull(methodFound);
var parsedValue = Expression.Variable(type, "parsedValue");
var block = methodFound!(parsedValue, Expression.Constant(CultureInfo.InvariantCulture)) as BlockExpression;
Assert.NotNull(block);
Assert.Equal(typeof(bool), block!.Type);
var parseEnum = Expression.Lambda<Func<string, Choice>>(Expression.Block(new[] { parsedValue },
block,
parsedValue), ParameterBindingMethodCache.SharedExpressions.TempSourceStringExpr).Compile();
Assert.Equal(Choice.One, parseEnum("One"));
Assert.Equal(Choice.Two, parseEnum("Two"));
Assert.Equal(Choice.Three, parseEnum("Three"));
}
[Fact]
public async Task FindBindAsyncMethod_FindsCorrectMethodOnClass()
{
var type = typeof(BindAsyncRecord);
var cache = new ParameterBindingMethodCache();
var parameter = new MockParameterInfo(type, "bindAsyncRecord");
var methodFound = cache.FindBindAsyncMethod(parameter);
Assert.NotNull(methodFound.Expression);
Assert.Equal(2, methodFound.ParamCount);
var parsedValue = Expression.Variable(type, "parsedValue");
var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(
Expression.Block(new[] { parsedValue }, methodFound.Expression!),
ParameterBindingMethodCache.SharedExpressions.HttpContextExpr).Compile();
var httpContext = new DefaultHttpContext
{
Request =
{
Headers =
{
["ETag"] = "42",
},
},
};
Assert.Equal(new BindAsyncRecord(42), await parseHttpContext(httpContext));
}
[Fact]
public async Task FindBindAsyncMethod_FindsSingleArgBindAsync()
{
var type = typeof(BindAsyncSingleArgStruct);
var cache = new ParameterBindingMethodCache();
var parameter = new MockParameterInfo(type, "bindAsyncSingleArgStruct");
var methodFound = cache.FindBindAsyncMethod(parameter);
Assert.NotNull(methodFound.Expression);
Assert.Equal(1, methodFound.ParamCount);
var parsedValue = Expression.Variable(type, "parsedValue");
var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(
Expression.Block(new[] { parsedValue }, methodFound.Expression!),
ParameterBindingMethodCache.SharedExpressions.HttpContextExpr).Compile();
var httpContext = new DefaultHttpContext
{
Request =
{
Headers =
{
["ETag"] = "42",
},
},
};
Assert.Equal(new BindAsyncSingleArgStruct(42), await parseHttpContext(httpContext));
}
public static IEnumerable<object[]> BindAsyncParameterInfoData
{
get
{
return new[]
{
new[]
{
GetFirstParameter((BindAsyncRecord arg) => BindAsyncRecordMethod(arg)),
},
new[]
{
GetFirstParameter((BindAsyncStruct arg) => BindAsyncStructMethod(arg)),
},
new[]
{
GetFirstParameter((BindAsyncSingleArgRecord arg) => BindAsyncSingleArgRecordMethod(arg)),
},
new[]
{
GetFirstParameter((BindAsyncSingleArgStruct arg) => BindAsyncSingleArgStructMethod(arg)),
},
new[]
{
GetFirstParameter((InheritBindAsync arg) => InheritBindAsyncMethod(arg))
},
new[]
{
GetFirstParameter((InheritBindAsyncWithParameterInfo arg) => InheritBindAsyncWithParameterInfoMethod(arg))
},
new[]
{
GetFirstParameter((BindAsyncFromInterface arg) => BindAsyncFromInterfaceMethod(arg))
},
new[]
{
GetFirstParameter((BindAsyncFromGrandparentInterface arg) => BindAsyncFromGrandparentInterfaceMethod(arg))
},
new[]
{
GetFirstParameter((BindAsyncDirectlyAndFromInterface arg) => BindAsyncDirectlyAndFromInterfaceMethod(arg))
},
new[]
{
GetFirstParameter((BindAsyncFromClassAndInterface arg) => BindAsyncFromClassAndInterfaceMethod(arg))
},
new[]
{
GetFirstParameter((BindAsyncFromInterfaceWithParameterInfo arg) => BindAsyncFromInterfaceWithParameterInfoMethod(arg))
},
new[]
{
GetFirstParameter((BindAsyncFromStaticAbstractInterfaceAndBindAsync arg) => BindAsyncFromImplicitStaticAbstractInterfaceMethodInsteadOfReflectionMatchedMethod(arg))
},
};
}
}
[Theory]
[MemberData(nameof(BindAsyncParameterInfoData))]
public void HasBindAsyncMethod_ReturnsTrueWhenMethodExists(ParameterInfo parameterInfo)
{
Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
}
[Fact]
public void HasBindAsyncMethod_ReturnsTrueForNullableReturningBindAsyncStructMethod()
{
var parameterInfo = GetFirstParameter((NullableReturningBindAsyncStruct arg) => NullableReturningBindAsyncStructMethod(arg));
Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
}
[Fact]
public void HasBindAsyncMethod_ReturnsTrueForClassImplicitlyImplementingIBindableFromHttpContext()
{
var parameterInfo = GetFirstParameter((BindAsyncFromImplicitStaticAbstractInterface arg) => BindAsyncFromImplicitStaticAbstractInterfaceMethod(arg));
Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
}
[Fact]
public void HasBindAsyncMethod_ReturnsTrueForClassExplicitlyImplementingIBindableFromHttpContext()
{
var parameterInfo = GetFirstParameter((BindAsyncFromExplicitStaticAbstractInterface arg) => BindAsyncFromExplicitStaticAbstractInterfaceMethod(arg));
Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
}
[Fact]
public void HasBindAsyncMethod_ReturnsTrueForClassImplementingIBindableFromHttpContextAndNonInterfaceBindAsyncMethod()
{
var parameterInfo = GetFirstParameter((BindAsyncFromStaticAbstractInterfaceAndBindAsync arg) => BindAsyncFromImplicitStaticAbstractInterfaceMethodInsteadOfReflectionMatchedMethod(arg));
Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
}
[Fact]
public void FindBindAsyncMethod_FindsNonNullableReturningBindAsyncMethodGivenNullableType()
{
var parameterInfo = GetFirstParameter((BindAsyncStruct? arg) => BindAsyncNullableStructMethod(arg));
Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
}
[Fact]
public async Task FindBindAsyncMethod_FindsForClassImplicitlyImplementingIBindableFromHttpContext()
{
var parameterInfo = GetFirstParameter((BindAsyncFromImplicitStaticAbstractInterface arg) => BindAsyncFromImplicitStaticAbstractInterfaceMethod(arg));
var cache = new ParameterBindingMethodCache();
Assert.True(cache.HasBindAsyncMethod(parameterInfo));
var methodFound = cache.FindBindAsyncMethod(parameterInfo);
var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object?>>>(methodFound.Expression!,
ParameterBindingMethodCache.SharedExpressions.HttpContextExpr).Compile();
var httpContext = new DefaultHttpContext();
var result = await parseHttpContext(httpContext);
Assert.NotNull(result);
Assert.IsType<BindAsyncFromImplicitStaticAbstractInterface>(result);
}
[Fact]
public async Task FindBindAsyncMethod_FindsForClassExplicitlyImplementingIBindableFromHttpContext()
{
var parameterInfo = GetFirstParameter((BindAsyncFromExplicitStaticAbstractInterface arg) => BindAsyncFromExplicitStaticAbstractInterfaceMethod(arg));
var cache = new ParameterBindingMethodCache();
Assert.True(cache.HasBindAsyncMethod(parameterInfo));
var methodFound = cache.FindBindAsyncMethod(parameterInfo);
var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object?>>>(methodFound.Expression!,
ParameterBindingMethodCache.SharedExpressions.HttpContextExpr).Compile();
var httpContext = new DefaultHttpContext();
var result = await parseHttpContext(httpContext);
Assert.NotNull(result);
Assert.IsType<BindAsyncFromExplicitStaticAbstractInterface>(result);
}
[Fact]
public async Task FindBindAsyncMethod_FindsFallbackMethodWhenPreferredMethodsReturnTypeIsWrong()
{
var parameterInfo = GetFirstParameter((BindAsyncFallsBack? arg) => BindAsyncFallbackMethod(arg));
var cache = new ParameterBindingMethodCache();
Assert.True(cache.HasBindAsyncMethod(parameterInfo));
var methodFound = cache.FindBindAsyncMethod(parameterInfo);
var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(methodFound.Expression!,
ParameterBindingMethodCache.SharedExpressions.HttpContextExpr).Compile();
var httpContext = new DefaultHttpContext();
Assert.Null(await parseHttpContext(httpContext));
}
[Fact]
public async Task FindBindAsyncMethod_FindsFallbackMethodFromInheritedWhenPreferredMethodIsInvalid()
{
var parameterInfo = GetFirstParameter((BindAsyncBadMethod? arg) => BindAsyncBadMethodMethod(arg));
var cache = new ParameterBindingMethodCache();
Assert.True(cache.HasBindAsyncMethod(parameterInfo));
var methodFound = cache.FindBindAsyncMethod(parameterInfo);
var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(methodFound.Expression!,
ParameterBindingMethodCache.SharedExpressions.HttpContextExpr).Compile();
var httpContext = new DefaultHttpContext();
Assert.Null(await parseHttpContext(httpContext));
}
[Fact]
public async Task FindBindAsyncMethod_FindsMethodFromStaticAbstractInterfaceWhenValidNonInterfaceMethodAlsoExists()
{
var parameterInfo = GetFirstParameter((BindAsyncFromStaticAbstractInterfaceAndBindAsync arg) => BindAsyncFromImplicitStaticAbstractInterfaceMethodInsteadOfReflectionMatchedMethod(arg));
var cache = new ParameterBindingMethodCache();
Assert.True(cache.HasBindAsyncMethod(parameterInfo));
var methodFound = cache.FindBindAsyncMethod(parameterInfo);
var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(methodFound.Expression!,
ParameterBindingMethodCache.SharedExpressions.HttpContextExpr).Compile();
var httpContext = new DefaultHttpContext();
var result = await parseHttpContext(httpContext);
Assert.NotNull(result);
Assert.IsType<BindAsyncFromStaticAbstractInterfaceAndBindAsync>(result);
Assert.Equal(BindAsyncSource.InterfaceStaticAbstractImplicit, ((BindAsyncFromStaticAbstractInterfaceAndBindAsync)result).BoundFrom);
}
[Theory]
[InlineData(typeof(ClassWithParameterlessConstructor))]
[InlineData(typeof(RecordClassParameterlessConstructor))]
[InlineData(typeof(StructWithParameterlessConstructor))]
[InlineData(typeof(RecordStructWithParameterlessConstructor))]
public void FindConstructor_FindsParameterlessConstructor_WhenExplicitlyDeclared(Type type)
{
var cache = new ParameterBindingMethodCache();
var (constructor, parameters) = cache.FindConstructor(type);
Assert.NotNull(constructor);
Assert.True(parameters.Length == 0);
}
[Theory]
[InlineData(typeof(ClassWithDefaultConstructor))]
[InlineData(typeof(RecordClassWithDefaultConstructor))]
public void FindConstructor_FindsDefaultConstructor_WhenNotExplictlyDeclared(Type type)
{
var cache = new ParameterBindingMethodCache();
var (constructor, parameters) = cache.FindConstructor(type);
Assert.NotNull(constructor);
Assert.True(parameters.Length == 0);
}
[Theory]
[InlineData(typeof(ClassWithParameterizedConstructor))]
[InlineData(typeof(RecordClassParameterizedConstructor))]
[InlineData(typeof(StructWithParameterizedConstructor))]
[InlineData(typeof(RecordStructParameterizedConstructor))]
public void FindConstructor_FindsParameterizedConstructor_WhenExplictlyDeclared(Type type)
{
var cache = new ParameterBindingMethodCache();
var (constructor, parameters) = cache.FindConstructor(type);
Assert.NotNull(constructor);
Assert.True(parameters.Length == 1);
}
[Theory]
[InlineData(typeof(StructWithDefaultConstructor))]
[InlineData(typeof(RecordStructWithDefaultConstructor))]
public void FindConstructor_ReturnNullForStruct_WhenNotExplictlyDeclared(Type type)
{
var cache = new ParameterBindingMethodCache();
var (constructor, parameters) = cache.FindConstructor(type);
Assert.Null(constructor);
Assert.True(parameters.Length == 0);
}
[Theory]
[InlineData(typeof(StructWithMultipleConstructors))]
[InlineData(typeof(RecordStructWithMultipleConstructors))]
public void FindConstructor_ReturnNullForStruct_WhenMultipleParameterizedConstructorsDeclared(Type type)
{
var cache = new ParameterBindingMethodCache();
var (constructor, parameters) = cache.FindConstructor(type);
Assert.Null(constructor);
Assert.True(parameters.Length == 0);
}
public static TheoryData<Type> InvalidTryParseStringTypesData
{
get
{
return new TheoryData<Type>
{
typeof(InvalidVoidReturnTryParseStruct),
typeof(InvalidVoidReturnTryParseClass),
typeof(InvalidWrongTypeTryParseStruct),
typeof(InvalidWrongTypeTryParseClass),
typeof(InvalidTryParseNullableStruct),
typeof(InvalidTooFewArgsTryParseStruct),
typeof(InvalidTooFewArgsTryParseClass),
typeof(InvalidNonStaticTryParseStruct),
typeof(InvalidNonStaticTryParseClass),
typeof(TryParseWrongTypeInheritClass),
typeof(TryParseWrongTypeFromInterface),
};
}
}
[Theory]
[MemberData(nameof(InvalidTryParseStringTypesData))]
public void FindTryParseMethod_ThrowsIfInvalidTryParseOnType(Type type)
{
var ex = Assert.Throws<InvalidOperationException>(
() => new ParameterBindingMethodCache().FindTryParseMethod(type));
Assert.StartsWith($"TryParse method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
Assert.Contains($"bool TryParse(string, IFormatProvider, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
Assert.Contains($"bool TryParse(string, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
}
[Theory]
[MemberData(nameof(InvalidTryParseStringTypesData))]
public void FindTryParseMethod_DoesNotThrowIfInvalidTryParseOnType_WhenThrowOnInvalidFalse(Type type)
{
Assert.Null(new ParameterBindingMethodCache(throwOnInvalidMethod: false).FindTryParseMethod(type));
}
[Fact]
public void FindTryParseMethod_ThrowsIfMultipleInterfacesMatch()
{
var ex = Assert.Throws<InvalidOperationException>(
() => new ParameterBindingMethodCache().FindTryParseMethod(typeof(TryParseFromMultipleInterfaces)));
Assert.Equal("TryParseFromMultipleInterfaces implements multiple interfaces defining a static Boolean TryParse(System.String, TryParseFromMultipleInterfaces ByRef) method causing ambiguity.", ex.Message);
}
[Fact]
public void FindTryParseMethod_DoesNotThrowIfMultipleInterfacesMatch_WhenThrowOnInvalidFalse()
{
Assert.Null(new ParameterBindingMethodCache(throwOnInvalidMethod: false).FindTryParseMethod(typeof(TryParseFromMultipleInterfaces)));
}
[Theory]
[InlineData(typeof(TryParseClassWithGoodAndBad))]
[InlineData(typeof(TryParseStructWithGoodAndBad))]
public void FindTryParseMethod_IgnoresInvalidTryParseIfGoodOneFound(Type type)
{
var method = new ParameterBindingMethodCache().FindTryParseMethod(type);
Assert.NotNull(method);
}
public static TheoryData<Type> InvalidBindAsyncTypesData
{
get
{
return new TheoryData<Type>
{
typeof(InvalidWrongReturnBindAsyncStruct),
typeof(InvalidWrongReturnBindAsyncClass),
typeof(InvalidWrongParamBindAsyncStruct),
typeof(InvalidWrongParamBindAsyncClass),
typeof(BindAsyncWrongTypeInherit),
typeof(BindAsyncWithParameterInfoWrongTypeInherit),
typeof(BindAsyncWrongTypeFromInterface),
typeof(BindAsyncBothBadMethods),
typeof(BindAsyncFromStaticAbstractInterfaceWrongType)
};
}
}
[Theory]
[MemberData(nameof(InvalidBindAsyncTypesData))]
public void FindBindAsyncMethod_ThrowsIfInvalidBindAsyncOnType(Type type)
{
var cache = new ParameterBindingMethodCache();
var parameter = new MockParameterInfo(type, "anything");
var ex = Assert.Throws<InvalidOperationException>(
() => cache.FindBindAsyncMethod(parameter));
Assert.StartsWith($"BindAsync method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context)", ex.Message);
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context)", ex.Message);
}
[Theory]
[MemberData(nameof(InvalidBindAsyncTypesData))]
public void FindBindAsyncMethod_DoesNotThrowIfInvalidBindAsyncOnType_WhenThrowOnInvalidFalse(Type type)
{
var cache = new ParameterBindingMethodCache(throwOnInvalidMethod: false);
var parameter = new MockParameterInfo(type, "anything");
var (expression, _) = cache.FindBindAsyncMethod(parameter);
Assert.Null(expression);
}
[Fact]
public void FindBindAsyncMethod_ThrowsIfMultipleInterfacesMatch()
{
var cache = new ParameterBindingMethodCache();
var parameter = new MockParameterInfo(typeof(BindAsyncFromMultipleInterfaces), "anything");
var ex = Assert.Throws<InvalidOperationException>(() => cache.FindBindAsyncMethod(parameter));
Assert.Equal("BindAsyncFromMultipleInterfaces implements multiple interfaces defining a static System.Threading.Tasks.ValueTask`1[Microsoft.AspNetCore.Http.Extensions.Tests.ParameterBindingMethodCacheTests+BindAsyncFromMultipleInterfaces] BindAsync(Microsoft.AspNetCore.Http.HttpContext) method causing ambiguity.", ex.Message);
}
[Fact]
public void FindBindAsyncMethod_DoesNotThrowIfMultipleInterfacesMatch_WhenThrowOnInvalidFalse()
{
var cache = new ParameterBindingMethodCache(throwOnInvalidMethod: false);
var parameter = new MockParameterInfo(typeof(BindAsyncFromMultipleInterfaces), "anything");
var (expression, _) = cache.FindBindAsyncMethod(parameter);
Assert.Null(expression);
}
[Theory]
[InlineData(typeof(BindAsyncStructWithGoodAndBad))]
[InlineData(typeof(BindAsyncClassWithGoodAndBad))]
public void FindBindAsyncMethod_IgnoresInvalidBindAsyncIfGoodOneFound(Type type)
{
var cache = new ParameterBindingMethodCache();
var parameter = new MockParameterInfo(type, "anything");
var (expression, _) = cache.FindBindAsyncMethod(parameter);
Assert.NotNull(expression);
}
private class ClassWithInternalConstructor
{
internal ClassWithInternalConstructor()
{ }
}
private record RecordWithInternalConstructor
{
internal RecordWithInternalConstructor()
{ }
}
[Theory]
[InlineData(typeof(ClassWithInternalConstructor))]
[InlineData(typeof(RecordWithInternalConstructor))]
public void FindConstructor_ThrowsIfNoPublicConstructors(Type type)
{
var cache = new ParameterBindingMethodCache();
var ex = Assert.Throws<InvalidOperationException>(() => cache.FindConstructor(type));
Assert.Equal($"No public parameterless constructor found for type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}'.", ex.Message);
}
[Theory]
[InlineData(typeof(AbstractClass))]
[InlineData(typeof(AbstractRecord))]
public void FindConstructor_ThrowsIfAbstract(Type type)
{
var cache = new ParameterBindingMethodCache();
var ex = Assert.Throws<InvalidOperationException>(() => cache.FindConstructor(type));
Assert.Equal($"The abstract type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}' is not supported.", ex.Message);
}
[Theory]
[InlineData(typeof(ClassWithMultipleConstructors))]
[InlineData(typeof(RecordWithMultipleConstructors))]
public void FindConstructor_ThrowsIfMultipleParameterizedConstructors(Type type)
{
var cache = new ParameterBindingMethodCache();
var ex = Assert.Throws<InvalidOperationException>(() => cache.FindConstructor(type));
Assert.Equal($"Only a single public parameterized constructor is allowed for type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}'.", ex.Message);
}
[Theory]
[InlineData(typeof(ClassWithInvalidConstructors))]
[InlineData(typeof(RecordClassWithInvalidConstructors))]
[InlineData(typeof(RecordStructWithInvalidConstructors))]
[InlineData(typeof(StructWithInvalidConstructors))]
public void FindConstructor_ThrowsIfParameterizedConstructorIncludeNoMatchingArguments(Type type)
{
var cache = new ParameterBindingMethodCache();
var ex = Assert.Throws<InvalidOperationException>(() => cache.FindConstructor(type));
Assert.Equal(
$"The public parameterized constructor must contain only parameters that match the declared public properties for type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}'.",
ex.Message);
}
enum Choice
{
One,
Two,
Three
}
private static void TryParseStringRecordMethod(TryParseStringRecord arg) { }
private static void TryParseStringStructMethod(TryParseStringStruct arg) { }
private static void TryParseStringNullableStructMethod(TryParseStringStruct? arg) { }
private static void BindAsyncRecordMethod(BindAsyncRecord arg) { }
private static void BindAsyncStructMethod(BindAsyncStruct arg) { }
private static void BindAsyncNullableStructMethod(BindAsyncStruct? arg) { }
private static void NullableReturningBindAsyncStructMethod(NullableReturningBindAsyncStruct arg) { }
private static void BindAsyncSingleArgRecordMethod(BindAsyncSingleArgRecord arg) { }
private static void BindAsyncSingleArgStructMethod(BindAsyncSingleArgStruct arg) { }
private static void InheritBindAsyncMethod(InheritBindAsync arg) { }
private static void InheritBindAsyncWithParameterInfoMethod(InheritBindAsyncWithParameterInfo args) { }
private static void BindAsyncFromInterfaceMethod(BindAsyncFromInterface arg) { }
private static void BindAsyncFromGrandparentInterfaceMethod(BindAsyncFromGrandparentInterface arg) { }
private static void BindAsyncDirectlyAndFromInterfaceMethod(BindAsyncDirectlyAndFromInterface arg) { }
private static void BindAsyncFromClassAndInterfaceMethod(BindAsyncFromClassAndInterface arg) { }
private static void BindAsyncFromInterfaceWithParameterInfoMethod(BindAsyncFromInterfaceWithParameterInfo args) { }
private static void BindAsyncFallbackMethod(BindAsyncFallsBack? arg) { }
private static void BindAsyncBadMethodMethod(BindAsyncBadMethod? arg) { }
private static void BindAsyncFromImplicitStaticAbstractInterfaceMethod(BindAsyncFromImplicitStaticAbstractInterface arg) { }
private static void BindAsyncFromExplicitStaticAbstractInterfaceMethod(BindAsyncFromExplicitStaticAbstractInterface arg) { }
private static void BindAsyncFromImplicitStaticAbstractInterfaceMethodInsteadOfReflectionMatchedMethod(BindAsyncFromStaticAbstractInterfaceAndBindAsync arg) { }
private static void BindAsyncFromStaticAbstractInterfaceWrongTypeMethod(BindAsyncFromStaticAbstractInterfaceWrongType arg) { }
private static ParameterInfo GetFirstParameter<T>(Expression<Action<T>> expr)
{
var mc = (MethodCallExpression)expr.Body;
return mc.Method.GetParameters()[0];
}
private static ParameterInfo GetParameterAtIndex<T>(Expression<Action<T>> expr, int paramIndex)
{
var mc = (MethodCallExpression)expr.Body;
return mc.Method.GetParameters()[paramIndex];
}
private record TryParseStringRecord(int Value)
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out TryParseStringRecord? result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = null;
return false;
}
result = new TryParseStringRecord(val);
return true;
}
}
private record struct TryParseStringStruct(int Value)
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out TryParseStringStruct result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return false;
}
result = new TryParseStringStruct(val);
return true;
}
}
private record struct InvalidVoidReturnTryParseStruct(int Value)
{
public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return;
}
result = new InvalidVoidReturnTryParseStruct(val);
return;
}
}
private record struct InvalidWrongTypeTryParseStruct(int Value)
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return false;
}
result = new InvalidVoidReturnTryParseStruct(val);
return true;
}
}
private record struct InvalidTryParseNullableStruct(int Value)
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidTryParseNullableStruct? result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return false;
}
result = new InvalidTryParseNullableStruct(val);
return true;
}
}
private record struct InvalidTooFewArgsTryParseStruct(int Value)
{
public static bool TryParse(out InvalidTooFewArgsTryParseStruct result)
{
result = default;
return false;
}
}
private struct TryParseStructWithGoodAndBad
{
public static bool TryParse(string? value, out TryParseStructWithGoodAndBad result)
{
result = new();
return false;
}
public static void TryParse(out TryParseStructWithGoodAndBad result)
{
result = new();
}
}
private record struct InvalidNonStaticTryParseStruct(int Value)
{
public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return false;
}
result = new InvalidVoidReturnTryParseStruct(val);
return true;
}
}
private class InvalidVoidReturnTryParseClass
{
public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = new();
return;
}
result = new();
}
}
private class InvalidWrongTypeTryParseClass
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = new();
return false;
}
result = new();
return true;
}
}
private class InvalidTooFewArgsTryParseClass
{
public static bool TryParse(out InvalidTooFewArgsTryParseClass result)
{
result = new();
return false;
}
}
private class TryParseClassWithGoodAndBad
{
public static bool TryParse(string? value, out TryParseClassWithGoodAndBad result)
{
result = new();
return false;
}
public static bool TryParse(out TryParseClassWithGoodAndBad result)
{
result = new();
return false;
}
}
private class InvalidNonStaticTryParseClass
{
public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidNonStaticTryParseClass result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = new();
return false;
}
result = new();
return true;
}
}
private record TryParseNoFormatProviderRecord(int Value)
{
public static bool TryParse(string? value, out TryParseNoFormatProviderRecord? result)
{
if (!int.TryParse(value, out var val))
{
result = null;
return false;
}
result = new TryParseNoFormatProviderRecord(val);
return true;
}
}
private record struct TryParseNoFormatProviderStruct(int Value)
{
public static bool TryParse(string? value, out TryParseNoFormatProviderStruct result)
{
if (!int.TryParse(value, out var val))
{
result = default;
return false;
}
result = new TryParseNoFormatProviderStruct(val);
return true;
}
}
private class BaseTryParseClass<T>
{
public static bool TryParse(string? value, out T? result)
{
result = default(T);
return false;
}
}
private class TryParseInheritClass : BaseTryParseClass<TryParseInheritClass>
{
}
// using wrong T on purpose
private class TryParseWrongTypeInheritClass : BaseTryParseClass<TryParseInheritClass>
{
}
private class BaseTryParseClassWithFormatProvider<T>
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out T? result)
{
result = default(T);
return false;
}
}
private class TryParseInheritClassWithFormatProvider : BaseTryParseClassWithFormatProvider<TryParseInheritClassWithFormatProvider>
{
}
private interface ITryParse<T>
{
static bool TryParse(string? value, out T? result)
{
result = default(T);
return false;
}
}
private interface ITryParse2<T>
{
static bool TryParse(string? value, out T? result)
{
result = default(T);
return false;
}
}
private interface IImplementITryParse<T> : ITryParse<T>
{
}
private class TryParseFromInterface : ITryParse<TryParseFromInterface>
{
}
private class TryParseFromGrandparentInterface : IImplementITryParse<TryParseFromGrandparentInterface>
{
}
private class TryParseDirectlyAndFromInterface : ITryParse<TryParseDirectlyAndFromInterface>
{
static bool TryParse(string? value, out TryParseDirectlyAndFromInterface? result)
{
result = null;
return false;
}
}
private class TryParseFromClassAndInterface
: BaseTryParseClass<TryParseFromClassAndInterface>,
ITryParse<TryParseFromClassAndInterface>
{
}
private class TryParseFromMultipleInterfaces
: ITryParse<TryParseFromMultipleInterfaces>,
ITryParse2<TryParseFromMultipleInterfaces>
{
}
// using wrong T on purpose
private class TryParseWrongTypeFromInterface : ITryParse<TryParseFromInterface>
{
}
private interface ITryParseWithFormatProvider<T>
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out T? result)
{
result = default(T);
return false;
}
}
private class TryParseFromInterfaceWithFormatProvider : ITryParseWithFormatProvider<TryParseFromInterfaceWithFormatProvider>
{
}
private record BindAsyncRecord(int Value)
{
public static ValueTask<BindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
{
Assert.Equal(typeof(BindAsyncRecord), parameter.ParameterType);
Assert.Equal("bindAsyncRecord", parameter.Name);
if (!int.TryParse(context.Request.Headers.ETag, out var val))
{
return new(result: null);
}
return new(result: new(val));
}
}
private record struct BindAsyncStruct(int Value)
{
public static ValueTask<BindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
{
Assert.Equal(typeof(BindAsyncStruct), parameter.ParameterType);
Assert.Equal("bindAsyncStruct", parameter.Name);
if (!int.TryParse(context.Request.Headers.ETag, out var val))
{
throw new BadHttpRequestException("The request is missing the required ETag header.");
}
return new(result: new(val));
}
}
private record struct NullableReturningBindAsyncStruct(int Value)
{
public static ValueTask<NullableReturningBindAsyncStruct?> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
}
private record BindAsyncSingleArgRecord(int Value)
{
public static ValueTask<BindAsyncSingleArgRecord?> BindAsync(HttpContext context)
{
if (!int.TryParse(context.Request.Headers.ETag, out var val))
{
return new(result: null);
}
return new(result: new(val));
}
}
private record struct BindAsyncSingleArgStruct(int Value)
{
public static ValueTask<BindAsyncSingleArgStruct> BindAsync(HttpContext context)
{
if (!int.TryParse(context.Request.Headers.ETag, out var val))
{
throw new BadHttpRequestException("The request is missing the required ETag header.");
}
return new(result: new(val));
}
}
private record struct InvalidWrongReturnBindAsyncStruct(int Value)
{
public static Task<InvalidWrongReturnBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
}
private class InvalidWrongReturnBindAsyncClass
{
public static Task<InvalidWrongReturnBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
}
private record struct InvalidWrongParamBindAsyncStruct(int Value)
{
public static ValueTask<InvalidWrongParamBindAsyncStruct> BindAsync(ParameterInfo parameter) =>
throw new NotImplementedException();
}
private class InvalidWrongParamBindAsyncClass
{
public static Task<InvalidWrongParamBindAsyncClass> BindAsync(ParameterInfo parameter) =>
throw new NotImplementedException();
}
private record struct BindAsyncStructWithGoodAndBad(int Value)
{
public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
throw new NotImplementedException();
}
private class BindAsyncClassWithGoodAndBad
{
public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
throw new NotImplementedException();
}
private class BaseBindAsync<T>
{
public static ValueTask<T?> BindAsync(HttpContext context)
{
return new(default(T));
}
}
private class InheritBindAsync : BaseBindAsync<InheritBindAsync>
{
}
// Using wrong T on purpose
private class BindAsyncWrongTypeInherit : BaseBindAsync<InheritBindAsync>
{
}
private class BaseBindAsyncWithParameterInfo<T>
{
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter)
{
return new(default(T));
}
}
private class InheritBindAsyncWithParameterInfo : BaseBindAsyncWithParameterInfo<InheritBindAsyncWithParameterInfo>
{
}
// Using wrong T on purpose
private class BindAsyncWithParameterInfoWrongTypeInherit : BaseBindAsyncWithParameterInfo<InheritBindAsync>
{
}
private interface IBindAsync<T>
{
static ValueTask<T?> BindAsync(HttpContext context)
{
return new(default(T));
}
}
private interface IBindAsync2<T>
{
static ValueTask<T?> BindAsync(HttpContext context)
{
return new(default(T));
}
}
private interface IImeplmentIBindAsync<T> : IBindAsync<T>
{
}
private class BindAsyncFromInterface : IBindAsync<BindAsyncFromInterface>
{
}
private class BindAsyncFromGrandparentInterface : IImeplmentIBindAsync<BindAsyncFromGrandparentInterface>
{
}
private class BindAsyncDirectlyAndFromInterface : IBindAsync<BindAsyncDirectlyAndFromInterface>
{
static ValueTask<BindAsyncFromInterface?> BindAsync(HttpContext context)
{
return new(result: null);
}
}
private class BindAsyncFromClassAndInterface
: BaseBindAsync<BindAsyncFromClassAndInterface>,
IBindAsync<BindAsyncFromClassAndInterface>
{
}
private class BindAsyncFromMultipleInterfaces
: IBindAsync<BindAsyncFromMultipleInterfaces>,
IBindAsync2<BindAsyncFromMultipleInterfaces>
{
}
// using wrong T on purpose
private class BindAsyncWrongTypeFromInterface : IBindAsync<BindAsyncFromInterface>
{
}
private interface IBindAsyncWithParameterInfo<T>
{
static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter)
{
return new(default(T));
}
}
private class BindAsyncFromInterfaceWithParameterInfo : IBindAsync<BindAsyncFromInterfaceWithParameterInfo>
{
}
private class BindAsyncFallsBack
{
public static void BindAsync(HttpContext context, ParameterInfo parameter)
=> throw new NotImplementedException();
public static ValueTask<BindAsyncFallsBack?> BindAsync(HttpContext context)
{
return new(result: null);
}
}
private class BindAsyncBadMethod : IBindAsyncWithParameterInfo<BindAsyncBadMethod>
{
public static void BindAsync(HttpContext context, ParameterInfo parameter)
=> throw new NotImplementedException();
}
private class BindAsyncBothBadMethods
{
public static void BindAsync(HttpContext context, ParameterInfo parameter)
=> throw new NotImplementedException();
public static void BindAsync(HttpContext context)
=> throw new NotImplementedException();
}
public class ClassWithParameterizedConstructor
{
public int Foo { get; set; }
public ClassWithParameterizedConstructor(int foo)
{
}
}
public record RecordClassParameterizedConstructor(int Foo);
public record struct RecordStructParameterizedConstructor(int Foo);
public struct StructWithParameterizedConstructor
{
public int Foo { get; set; }
public StructWithParameterizedConstructor(int foo)
{
Foo = foo;
}
}
public class ClassWithParameterlessConstructor
{
public ClassWithParameterlessConstructor()
{
}
public ClassWithParameterlessConstructor(int foo)
{
}
}
public record RecordClassParameterlessConstructor
{
public RecordClassParameterlessConstructor()
{
}
public RecordClassParameterlessConstructor(int foo)
{
}
}
public struct StructWithParameterlessConstructor
{
public StructWithParameterlessConstructor()
{
}
public StructWithParameterlessConstructor(int foo)
{
}
}
public record struct RecordStructWithParameterlessConstructor
{
public RecordStructWithParameterlessConstructor()
{
}
public RecordStructWithParameterlessConstructor(int foo)
{
}
}
public class ClassWithDefaultConstructor
{ }
public record RecordClassWithDefaultConstructor
{ }
public struct StructWithDefaultConstructor
{ }
public record struct RecordStructWithDefaultConstructor
{ }
public struct StructWithMultipleConstructors
{
public StructWithMultipleConstructors(int foo)
{
}
public StructWithMultipleConstructors(int foo, int bar)
{
}
}
public record struct RecordStructWithMultipleConstructors(int Foo)
{
public RecordStructWithMultipleConstructors(int foo, int bar)
: this(foo)
{
}
}
private abstract class AbstractClass { }
private abstract record AbstractRecord();
private class ClassWithMultipleConstructors
{
public ClassWithMultipleConstructors(int foo)
{ }
public ClassWithMultipleConstructors(int foo, int bar)
{ }
}
private record RecordWithMultipleConstructors
{
public RecordWithMultipleConstructors(int foo)
{ }
public RecordWithMultipleConstructors(int foo, int bar)
{ }
}
private class ClassWithInvalidConstructors
{
public int Foo { get; set; }
public ClassWithInvalidConstructors(int foo, int bar)
{ }
}
private record RecordClassWithInvalidConstructors
{
public int Foo { get; set; }
public RecordClassWithInvalidConstructors(int foo, int bar)
{ }
}
private struct StructWithInvalidConstructors
{
public int Foo { get; set; }
public StructWithInvalidConstructors(int foo, int bar)
{
Foo = foo;
}
}
private record struct RecordStructWithInvalidConstructors
{
public int Foo { get; set; }
public RecordStructWithInvalidConstructors(int foo, int bar)
{
Foo = foo;
}
}
private class BindAsyncFromImplicitStaticAbstractInterface : IBindableFromHttpContext<BindAsyncFromImplicitStaticAbstractInterface>
{
public static ValueTask<BindAsyncFromImplicitStaticAbstractInterface?> BindAsync(HttpContext context, ParameterInfo parameter)
{
return ValueTask.FromResult<BindAsyncFromImplicitStaticAbstractInterface?>(new());
}
}
private class BindAsyncFromExplicitStaticAbstractInterface : IBindableFromHttpContext<BindAsyncFromExplicitStaticAbstractInterface>
{
static ValueTask<BindAsyncFromExplicitStaticAbstractInterface?> IBindableFromHttpContext<BindAsyncFromExplicitStaticAbstractInterface>.BindAsync(HttpContext context, ParameterInfo parameter)
{
return ValueTask.FromResult<BindAsyncFromExplicitStaticAbstractInterface?>(new());
}
}
private class BindAsyncFromStaticAbstractInterfaceAndBindAsync : IBindableFromHttpContext<BindAsyncFromStaticAbstractInterfaceAndBindAsync>
{
public BindAsyncFromStaticAbstractInterfaceAndBindAsync(BindAsyncSource boundFrom)
{
BoundFrom = boundFrom;
}
public BindAsyncSource BoundFrom { get; }
// Implicit interface implementation
public static ValueTask<BindAsyncFromStaticAbstractInterfaceAndBindAsync?> BindAsync(HttpContext context, ParameterInfo parameter)
{
return ValueTask.FromResult<BindAsyncFromStaticAbstractInterfaceAndBindAsync?>(new(BindAsyncSource.InterfaceStaticAbstractImplicit));
}
// Late-bound pattern based match in RequestDelegateFactory
public static ValueTask<BindAsyncFromStaticAbstractInterfaceAndBindAsync?> BindAsync(HttpContext context)
{
return ValueTask.FromResult<BindAsyncFromStaticAbstractInterfaceAndBindAsync?>(new(BindAsyncSource.Reflection));
}
}
private class BindAsyncFromStaticAbstractInterfaceWrongType : IBindableFromHttpContext<BindAsyncFromImplicitStaticAbstractInterface>
{
public static ValueTask<BindAsyncFromImplicitStaticAbstractInterface?> BindAsync(HttpContext context, ParameterInfo parameter)
{
return ValueTask.FromResult<BindAsyncFromImplicitStaticAbstractInterface?>(new());
}
}
private enum BindAsyncSource
{
Reflection,
InterfaceStaticAbstractImplicit,
InterfaceStaticAbstractExplicit
}
private class MockParameterInfo : ParameterInfo
{
public MockParameterInfo(Type type, string name)
{
ClassImpl = type;
NameImpl = name;
}
}
public class TodoWithExplicitIParsable : IParsable<TodoWithExplicitIParsable>
{
static TodoWithExplicitIParsable IParsable<TodoWithExplicitIParsable>.Parse(string s, IFormatProvider? provider)
{
throw new NotImplementedException();
}
static bool IParsable<TodoWithExplicitIParsable>.TryParse(string? s, IFormatProvider? provider, out TodoWithExplicitIParsable result)
{
throw new NotImplementedException();
}
}
}
|