Skip to content

Commit 5699c0e

Browse files
Use TryParse and BindAsync methods from interfaces (#36959)
Co-authored-by: Stephen Halter <[email protected]>
1 parent ac78d54 commit 5699c0e

File tree

3 files changed

+288
-10
lines changed

3 files changed

+288
-10
lines changed

src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs

Lines changed: 184 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvaria
7878
[InlineData(typeof(TryParseStringRecord))]
7979
[InlineData(typeof(TryParseStringStruct))]
8080
[InlineData(typeof(TryParseInheritClassWithFormatProvider))]
81+
[InlineData(typeof(TryParseFromInterfaceWithFormatProvider))]
8182
public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCultureCustomType(Type type)
8283
{
8384
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
@@ -99,6 +100,10 @@ public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvaria
99100
[InlineData(typeof(TryParseNoFormatProviderRecord))]
100101
[InlineData(typeof(TryParseNoFormatProviderStruct))]
101102
[InlineData(typeof(TryParseInheritClass))]
103+
[InlineData(typeof(TryParseFromInterface))]
104+
[InlineData(typeof(TryParseFromGrandparentInterface))]
105+
[InlineData(typeof(TryParseDirectlyAndFromInterface))]
106+
[InlineData(typeof(TryParseFromClassAndInterface))]
102107
public void FindTryParseMethod_WithNoFormatProvider(Type type)
103108
{
104109
var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
@@ -276,7 +281,27 @@ public static IEnumerable<object[]> BindAsyncParameterInfoData
276281
new[]
277282
{
278283
GetFirstParameter((InheritBindAsyncWithParameterInfo arg) => InheritBindAsyncWithParameterInfoMethod(arg))
279-
}
284+
},
285+
new[]
286+
{
287+
GetFirstParameter((BindAsyncFromInterface arg) => BindAsyncFromInterfaceMethod(arg))
288+
},
289+
new[]
290+
{
291+
GetFirstParameter((BindAsyncFromGrandparentInterface arg) => BindAsyncFromGrandparentInterfaceMethod(arg))
292+
},
293+
new[]
294+
{
295+
GetFirstParameter((BindAsyncDirectlyAndFromInterface arg) => BindAsyncDirectlyAndFromInterfaceMethod(arg))
296+
},
297+
new[]
298+
{
299+
GetFirstParameter((BindAsyncFromClassAndInterface arg) => BindAsyncFromClassAndInterfaceMethod(arg))
300+
},
301+
new[]
302+
{
303+
GetFirstParameter((BindAsyncFromInterfaceWithParameterInfo arg) => BindAsyncFromInterfaceWithParameterInfoMethod(arg))
304+
},
280305
};
281306
}
282307
}
@@ -313,6 +338,7 @@ public void FindBindAsyncMethod_FindsNonNullableReturningBindAsyncMethodGivenNul
313338
[InlineData(typeof(InvalidNonStaticTryParseStruct))]
314339
[InlineData(typeof(InvalidNonStaticTryParseClass))]
315340
[InlineData(typeof(TryParseWrongTypeInheritClass))]
341+
[InlineData(typeof(TryParseWrongTypeFromInterface))]
316342
public void FindTryParseMethod_ThrowsIfInvalidTryParseOnType(Type type)
317343
{
318344
var ex = Assert.Throws<InvalidOperationException>(
@@ -322,6 +348,14 @@ public void FindTryParseMethod_ThrowsIfInvalidTryParseOnType(Type type)
322348
Assert.Contains($"bool TryParse(string, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
323349
}
324350

351+
[Fact]
352+
public void FindTryParseMethod_ThrowsIfMultipleInterfacesMatch()
353+
{
354+
var ex = Assert.Throws<InvalidOperationException>(
355+
() => new ParameterBindingMethodCache().FindTryParseMethod(typeof(TryParseFromMultipleInterfaces)));
356+
Assert.Equal("TryParseFromMultipleInterfaces implements multiple interfaces defining a static Boolean TryParse(System.String, TryParseFromMultipleInterfaces ByRef) method causing ambiguity.", ex.Message);
357+
}
358+
325359
[Theory]
326360
[InlineData(typeof(TryParseClassWithGoodAndBad))]
327361
[InlineData(typeof(TryParseStructWithGoodAndBad))]
@@ -338,6 +372,7 @@ public void FindTryParseMethod_IgnoresInvalidTryParseIfGoodOneFound(Type type)
338372
[InlineData(typeof(InvalidWrongParamBindAsyncClass))]
339373
[InlineData(typeof(BindAsyncWrongTypeInherit))]
340374
[InlineData(typeof(BindAsyncWithParameterInfoWrongTypeInherit))]
375+
[InlineData(typeof(BindAsyncWrongTypeFromInterface))]
341376
public void FindBindAsyncMethod_ThrowsIfInvalidBindAsyncOnType(Type type)
342377
{
343378
var cache = new ParameterBindingMethodCache();
@@ -351,6 +386,15 @@ public void FindBindAsyncMethod_ThrowsIfInvalidBindAsyncOnType(Type type)
351386
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context)", ex.Message);
352387
}
353388

389+
[Fact]
390+
public void FindBindAsyncMethod_ThrowsIfMultipleInterfacesMatch()
391+
{
392+
var cache = new ParameterBindingMethodCache();
393+
var parameter = new MockParameterInfo(typeof(BindAsyncFromMultipleInterfaces), "anything");
394+
var ex = Assert.Throws<InvalidOperationException>(() => cache.FindBindAsyncMethod(parameter));
395+
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);
396+
}
397+
354398
[Theory]
355399
[InlineData(typeof(BindAsyncStructWithGoodAndBad))]
356400
[InlineData(typeof(BindAsyncClassWithGoodAndBad))]
@@ -382,6 +426,11 @@ private static void BindAsyncSingleArgRecordMethod(BindAsyncSingleArgRecord arg)
382426
private static void BindAsyncSingleArgStructMethod(BindAsyncSingleArgStruct arg) { }
383427
private static void InheritBindAsyncMethod(InheritBindAsync arg) { }
384428
private static void InheritBindAsyncWithParameterInfoMethod(InheritBindAsyncWithParameterInfo args) { }
429+
private static void BindAsyncFromInterfaceMethod(BindAsyncFromInterface arg) { }
430+
private static void BindAsyncFromGrandparentInterfaceMethod(BindAsyncFromGrandparentInterface arg) { }
431+
private static void BindAsyncDirectlyAndFromInterfaceMethod(BindAsyncDirectlyAndFromInterface arg) { }
432+
private static void BindAsyncFromClassAndInterfaceMethod(BindAsyncFromClassAndInterface arg) { }
433+
private static void BindAsyncFromInterfaceWithParameterInfoMethod(BindAsyncFromInterfaceWithParameterInfo args) { }
385434

386435
private static ParameterInfo GetFirstParameter<T>(Expression<Action<T>> expr)
387436
{
@@ -631,6 +680,75 @@ private class TryParseInheritClassWithFormatProvider : BaseTryParseClassWithForm
631680
{
632681
}
633682

683+
private interface ITryParse<T>
684+
{
685+
static bool TryParse(string? value, out T? result)
686+
{
687+
result = default(T);
688+
return false;
689+
}
690+
}
691+
692+
private interface ITryParse2<T>
693+
{
694+
static bool TryParse(string? value, out T? result)
695+
{
696+
result = default(T);
697+
return false;
698+
}
699+
}
700+
701+
private interface IImplementITryParse<T> : ITryParse<T>
702+
{
703+
}
704+
705+
private class TryParseFromInterface : ITryParse<TryParseFromInterface>
706+
{
707+
}
708+
709+
private class TryParseFromGrandparentInterface : IImplementITryParse<TryParseFromGrandparentInterface>
710+
{
711+
}
712+
713+
private class TryParseDirectlyAndFromInterface : ITryParse<TryParseDirectlyAndFromInterface>
714+
{
715+
static bool TryParse(string? value, out TryParseDirectlyAndFromInterface? result)
716+
{
717+
result = null;
718+
return false;
719+
}
720+
}
721+
722+
private class TryParseFromClassAndInterface
723+
: BaseTryParseClass<TryParseFromClassAndInterface>,
724+
ITryParse<TryParseFromClassAndInterface>
725+
{
726+
}
727+
728+
private class TryParseFromMultipleInterfaces
729+
: ITryParse<TryParseFromMultipleInterfaces>,
730+
ITryParse2<TryParseFromMultipleInterfaces>
731+
{
732+
}
733+
734+
// using wrong T on purpose
735+
private class TryParseWrongTypeFromInterface : ITryParse<TryParseFromInterface>
736+
{
737+
}
738+
739+
private interface ITryParseWithFormatProvider<T>
740+
{
741+
public static bool TryParse(string? value, IFormatProvider formatProvider, out T? result)
742+
{
743+
result = default(T);
744+
return false;
745+
}
746+
}
747+
748+
private class TryParseFromInterfaceWithFormatProvider : ITryParseWithFormatProvider<TryParseFromInterfaceWithFormatProvider>
749+
{
750+
}
751+
634752
private record BindAsyncRecord(int Value)
635753
{
636754
public static ValueTask<BindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
@@ -771,6 +889,71 @@ private class BindAsyncWithParameterInfoWrongTypeInherit : BaseBindAsyncWithPara
771889
{
772890
}
773891

892+
private interface IBindAsync<T>
893+
{
894+
static ValueTask<T?> BindAsync(HttpContext context)
895+
{
896+
return new(default(T));
897+
}
898+
}
899+
900+
private interface IBindAsync2<T>
901+
{
902+
static ValueTask<T?> BindAsync(HttpContext context)
903+
{
904+
return new(default(T));
905+
}
906+
}
907+
908+
private interface IImeplmentIBindAsync<T> : IBindAsync<T>
909+
{
910+
}
911+
912+
private class BindAsyncFromInterface : IBindAsync<BindAsyncFromInterface>
913+
{
914+
}
915+
916+
private class BindAsyncFromGrandparentInterface : IImeplmentIBindAsync<BindAsyncFromGrandparentInterface>
917+
{
918+
}
919+
920+
private class BindAsyncDirectlyAndFromInterface : IBindAsync<BindAsyncDirectlyAndFromInterface>
921+
{
922+
static ValueTask<BindAsyncFromInterface?> BindAsync(HttpContext context)
923+
{
924+
return new(result: null);
925+
}
926+
}
927+
928+
private class BindAsyncFromClassAndInterface
929+
: BaseBindAsync<BindAsyncFromClassAndInterface>,
930+
IBindAsync<BindAsyncFromClassAndInterface>
931+
{
932+
}
933+
934+
private class BindAsyncFromMultipleInterfaces
935+
: IBindAsync<BindAsyncFromMultipleInterfaces>,
936+
IBindAsync2<BindAsyncFromMultipleInterfaces>
937+
{
938+
}
939+
940+
// using wrong T on purpose
941+
private class BindAsyncWrongTypeFromInterface : IBindAsync<BindAsyncFromInterface>
942+
{
943+
}
944+
945+
private interface IBindAsyncWithParameterInfo<T>
946+
{
947+
static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter)
948+
{
949+
return new(default(T));
950+
}
951+
}
952+
953+
private class BindAsyncFromInterfaceWithParameterInfo : IBindAsync<BindAsyncFromInterfaceWithParameterInfo>
954+
{
955+
}
956+
774957
private class MockParameterInfo : ParameterInfo
775958
{
776959
public MockParameterInfo(Type type, string name)

src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,28 @@ private record MySimpleBindAsyncRecord(Uri Uri)
660660
}
661661
}
662662

663+
private interface IBindAsync<T>
664+
{
665+
static ValueTask<T?> BindAsync(HttpContext context)
666+
{
667+
if (typeof(T) != typeof(MyBindAsyncFromInterfaceRecord))
668+
{
669+
throw new InvalidOperationException();
670+
}
671+
672+
if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
673+
{
674+
return new(default(T));
675+
}
676+
677+
return new(result: (T)(object)new MyBindAsyncFromInterfaceRecord(uri));
678+
}
679+
}
680+
681+
private record MyBindAsyncFromInterfaceRecord(Uri uri) : IBindAsync<MyBindAsyncFromInterfaceRecord>
682+
{
683+
}
684+
663685
[Theory]
664686
[MemberData(nameof(TryParsableParameters))]
665687
public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromRouteValue(Delegate action, string? routeValue, object? expectedParameterValue)
@@ -832,6 +854,25 @@ public async Task RequestDelegateCanAwaitValueTasksThatAreNotImmediatelyComplete
832854
Assert.Equal(new MyAwaitedBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myAwaitedBindAsyncStruct"]);
833855
}
834856

857+
[Fact]
858+
public async Task RequestDelegateUsesBindAsyncFromImplementedInterface()
859+
{
860+
var httpContext = CreateHttpContext();
861+
862+
httpContext.Request.Headers.Referer = "https://example.org";
863+
864+
var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncFromInterfaceRecord myBindAsyncRecord) =>
865+
{
866+
httpContext.Items["myBindAsyncFromInterfaceRecord"] = myBindAsyncRecord;
867+
});
868+
869+
var requestDelegate = resultFactory.RequestDelegate;
870+
871+
await requestDelegate(httpContext);
872+
873+
Assert.Equal(new MyBindAsyncFromInterfaceRecord(new Uri("https://example.org")), httpContext.Items["myBindAsyncFromInterfaceRecord"]);
874+
}
875+
835876
public static object[][] DelegatesWithAttributesOnNotTryParsableParameters
836877
{
837878
get

0 commit comments

Comments
 (0)