Skip to content

[release/6.0-rc2] Find inherited TryParse and BindAsync #36694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvaria
[Theory]
[InlineData(typeof(TryParseStringRecord))]
[InlineData(typeof(TryParseStringStruct))]
[InlineData(typeof(TryParseInheritClassWithFormatProvider))]
public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCultureCustomType(Type type)
{
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
Expand All @@ -94,6 +95,24 @@ public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvaria
Assert.True(((call.Arguments[1] as ConstantExpression)!.Value as CultureInfo)!.Equals(CultureInfo.InvariantCulture));
}

[Theory]
[InlineData(typeof(TryParseNoFormatProviderRecord))]
[InlineData(typeof(TryParseNoFormatProviderStruct))]
[InlineData(typeof(TryParseInheritClass))]
public void FindTryParseMethod_WithNoFormatProvider(Type type)
{
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
Assert.NotNull(methodFound);

var call = methodFound!(Expression.Variable(type, "parsedValue")) 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
Expand Down Expand Up @@ -249,6 +268,14 @@ public static IEnumerable<object[]> BindAsyncParameterInfoData
new[]
{
GetFirstParameter((BindAsyncSingleArgStruct arg) => BindAsyncSingleArgStructMethod(arg)),
},
new[]
{
GetFirstParameter((InheritBindAsync arg) => InheritBindAsyncMethod(arg))
},
new[]
{
GetFirstParameter((InheritBindAsyncWithParameterInfo arg) => InheritBindAsyncWithParameterInfoMethod(arg))
}
};
}
Expand Down Expand Up @@ -285,6 +312,7 @@ public void FindBindAsyncMethod_FindsNonNullableReturningBindAsyncMethodGivenNul
[InlineData(typeof(InvalidTooFewArgsTryParseClass))]
[InlineData(typeof(InvalidNonStaticTryParseStruct))]
[InlineData(typeof(InvalidNonStaticTryParseClass))]
[InlineData(typeof(TryParseWrongTypeInheritClass))]
public void FindTryParseMethod_ThrowsIfInvalidTryParseOnType(Type type)
{
var ex = Assert.Throws<InvalidOperationException>(
Expand All @@ -308,6 +336,8 @@ public void FindTryParseMethod_IgnoresInvalidTryParseIfGoodOneFound(Type type)
[InlineData(typeof(InvalidWrongReturnBindAsyncClass))]
[InlineData(typeof(InvalidWrongParamBindAsyncStruct))]
[InlineData(typeof(InvalidWrongParamBindAsyncClass))]
[InlineData(typeof(BindAsyncWrongTypeInherit))]
[InlineData(typeof(BindAsyncWithParameterInfoWrongTypeInherit))]
public void FindBindAsyncMethod_ThrowsIfInvalidBindAsyncOnType(Type type)
{
var cache = new ParameterBindingMethodCache();
Expand Down Expand Up @@ -350,6 +380,8 @@ private static void NullableReturningBindAsyncStructMethod(NullableReturningBind

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 ParameterInfo GetFirstParameter<T>(Expression<Action<T>> expr)
{
Expand Down Expand Up @@ -538,6 +570,67 @@ public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidN
}
}

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 record BindAsyncRecord(int Value)
{
public static ValueTask<BindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
Expand Down Expand Up @@ -644,6 +737,40 @@ public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(ParameterInfo pa
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 class MockParameterInfo : ParameterInfo
{
public MockParameterInfo(Type type, string name)
Expand Down
12 changes: 6 additions & 6 deletions src/Shared/ParameterBindingMethodCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public bool HasBindAsyncMethod(ParameterInfo parameter) =>
expression);
}

methodInfo = type.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static, new[] { typeof(string), typeof(IFormatProvider), type.MakeByRefType() });
methodInfo = type.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy, new[] { typeof(string), typeof(IFormatProvider), type.MakeByRefType() });

if (methodInfo is not null && methodInfo.ReturnType == typeof(bool))
{
Expand All @@ -117,14 +117,14 @@ public bool HasBindAsyncMethod(ParameterInfo parameter) =>
expression);
}

methodInfo = type.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static, new[] { typeof(string), type.MakeByRefType() });
methodInfo = type.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy, new[] { typeof(string), type.MakeByRefType() });

if (methodInfo is not null && methodInfo.ReturnType == typeof(bool))
{
return (expression) => Expression.Call(methodInfo, TempSourceStringExpr, expression);
}

if (type.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance) is MethodInfo invalidMethod)
if (type.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy) is MethodInfo invalidMethod)
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine(CultureInfo.InvariantCulture, $"TryParse method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format");
Expand All @@ -149,11 +149,11 @@ public bool HasBindAsyncMethod(ParameterInfo parameter) =>
{
var hasParameterInfo = true;
// There should only be one BindAsync method with these parameters since C# does not allow overloading on return type.
var methodInfo = nonNullableParameterType.GetMethod("BindAsync", BindingFlags.Public | BindingFlags.Static, new[] { typeof(HttpContext), typeof(ParameterInfo) });
var methodInfo = nonNullableParameterType.GetMethod("BindAsync", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy, new[] { typeof(HttpContext), typeof(ParameterInfo) });
if (methodInfo is null)
{
hasParameterInfo = false;
methodInfo = nonNullableParameterType.GetMethod("BindAsync", BindingFlags.Public | BindingFlags.Static, new[] { typeof(HttpContext) });
methodInfo = nonNullableParameterType.GetMethod("BindAsync", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy, new[] { typeof(HttpContext) });
}

// We're looking for a method with the following signatures:
Expand Down Expand Up @@ -207,7 +207,7 @@ public bool HasBindAsyncMethod(ParameterInfo parameter) =>
}
}

if (nonNullableParameterType.GetMethod("BindAsync", BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance) is MethodInfo invalidBindMethod)
if (nonNullableParameterType.GetMethod("BindAsync", BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy) is MethodInfo invalidBindMethod)
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine(CultureInfo.InvariantCulture, $"BindAsync method found on {TypeNameHelper.GetTypeDisplayName(nonNullableParameterType, fullName: false)} with incorrect format. Must be a static method with format");
Expand Down