Skip to content

Commit 971df4c

Browse files
Infer parameter source from ElementType for IEnumerable (dotnet#45173)
* Infer ElementType for IEnumerable * Update src/Mvc/Mvc.Core/test/ApplicationModels/InferParameterBindingInfoConventionTest.cs * Update src/Mvc/Mvc.Core/src/ApplicationModels/InferParameterBindingInfoConvention.cs Co-authored-by: Safia Abdalla <[email protected]> * Fix SignalR fromservices inference Co-authored-by: Safia Abdalla <[email protected]>
1 parent c7c3ccb commit 971df4c

File tree

5 files changed

+135
-2
lines changed

5 files changed

+135
-2
lines changed

src/Mvc/Mvc.Core/src/ApplicationModels/InferParameterBindingInfoConvention.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ internal BindingSource InferBindingSourceForParameter(ParameterModel parameter)
120120
{
121121
if (IsComplexTypeParameter(parameter))
122122
{
123-
if (_serviceProviderIsService?.IsService(parameter.ParameterType) is true)
123+
if (IsService(parameter.ParameterType))
124124
{
125125
return BindingSource.Services;
126126
}
@@ -136,6 +136,25 @@ internal BindingSource InferBindingSourceForParameter(ParameterModel parameter)
136136
return BindingSource.Query;
137137
}
138138

139+
private bool IsService(Type type)
140+
{
141+
if (_serviceProviderIsService == null)
142+
{
143+
return false;
144+
}
145+
146+
// IServiceProviderIsService will special case IEnumerable<> and always return true
147+
// so, in this case checking the element type instead
148+
if (type.IsConstructedGenericType &&
149+
type.GetGenericTypeDefinition() is Type genericDefinition &&
150+
genericDefinition == typeof(IEnumerable<>))
151+
{
152+
type = type.GenericTypeArguments[0];
153+
}
154+
155+
return _serviceProviderIsService.IsService(type);
156+
}
157+
139158
private static bool ParameterExistsInAnyRoute(ActionModel action, string parameterName)
140159
{
141160
foreach (var selector in ActionAttributeRouteModel.FlattenSelectors(action))

src/Mvc/Mvc.Core/test/ApplicationModels/InferParameterBindingInfoConventionTest.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,21 @@ public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfSimpleTypes
543543
Assert.Same(BindingSource.Body, result);
544544
}
545545

546+
[Fact]
547+
public void InferBindingSourceForParameter_ReturnsBodyForIEnumerableOfSimpleTypes()
548+
{
549+
// Arrange
550+
var actionName = nameof(ParameterBindingController.IEnumerableOfSimpleTypes);
551+
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
552+
var convention = GetConvention();
553+
554+
// Act
555+
var result = convention.InferBindingSourceForParameter(parameter);
556+
557+
// Assert
558+
Assert.Same(BindingSource.Body, result);
559+
}
560+
546561
[Fact]
547562
public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfComplexTypes()
548563
{
@@ -558,6 +573,21 @@ public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfComplexType
558573
Assert.Same(BindingSource.Body, result);
559574
}
560575

576+
[Fact]
577+
public void InferBindingSourceForParameter_ReturnsBodyForIEnumerableOfComplexTypes()
578+
{
579+
// Arrange
580+
var actionName = nameof(ParameterBindingController.IEnumerableOfComplexTypes);
581+
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
582+
var convention = GetConvention();
583+
584+
// Act
585+
var result = convention.InferBindingSourceForParameter(parameter);
586+
587+
// Assert
588+
Assert.Same(BindingSource.Body, result);
589+
}
590+
561591
[Fact]
562592
public void InferBindingSourceForParameter_ReturnsServicesForComplexTypesRegisteredInDI()
563593
{
@@ -576,6 +606,24 @@ public void InferBindingSourceForParameter_ReturnsServicesForComplexTypesRegiste
576606
Assert.Same(BindingSource.Services, result);
577607
}
578608

609+
[Fact]
610+
public void InferBindingSourceForParameter_ReturnsServicesForIEnumerableOfComplexTypesRegisteredInDI()
611+
{
612+
// Arrange
613+
var actionName = nameof(ParameterBindingController.IEnumerableServiceParameter);
614+
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
615+
// Using any built-in type defined in the Test action
616+
var serviceProvider = Mock.Of<IServiceProviderIsService>(s => s.IsService(typeof(IApplicationModelProvider)) == true);
617+
var convention = GetConvention(serviceProviderIsService: serviceProvider);
618+
619+
// Act
620+
var result = convention.InferBindingSourceForParameter(parameter);
621+
622+
// Assert
623+
Assert.True(convention.IsInferForServiceParametersEnabled);
624+
Assert.Same(BindingSource.Services, result);
625+
}
626+
579627
[Fact]
580628
public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultName()
581629
{
@@ -982,9 +1030,15 @@ private class ParameterBindingController
9821030

9831031
public IActionResult CollectionOfSimpleTypes(IList<int> parameter) => null;
9841032

1033+
public IActionResult IEnumerableOfSimpleTypes(IEnumerable<int> parameter) => null;
1034+
9851035
public IActionResult CollectionOfComplexTypes(IList<TestModel> parameter) => null;
9861036

1037+
public IActionResult IEnumerableOfComplexTypes(IEnumerable<TestModel> parameter) => null;
1038+
9871039
public IActionResult ServiceParameter(IApplicationModelProvider parameter) => null;
1040+
1041+
public IActionResult IEnumerableServiceParameter(IEnumerable<IApplicationModelProvider> parameter) => null;
9881042
}
9891043

9901044
[ApiController]

src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public HubMethodDescriptor(ObjectMethodExecutor methodExecutor, IServiceProvider
8080
return false;
8181
}
8282
else if (p.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)) ||
83-
serviceProviderIsService?.IsService(p.ParameterType) == true)
83+
serviceProviderIsService?.IsService(GetServiceType(p.ParameterType)) == true)
8484
{
8585
if (index >= 64)
8686
{
@@ -160,4 +160,18 @@ private static Func<object, CancellationToken, IAsyncEnumerator<object>> Compile
160160
var lambda = Expression.Lambda<Func<object, CancellationToken, IAsyncEnumerator<object>>>(methodCall, parameters);
161161
return lambda.Compile();
162162
}
163+
164+
private static Type GetServiceType(Type type)
165+
{
166+
// IServiceProviderIsService will special case IEnumerable<> and always return true
167+
// so, in this case checking the element type instead
168+
if (type.IsConstructedGenericType &&
169+
type.GetGenericTypeDefinition() is Type genericDefinition &&
170+
genericDefinition == typeof(IEnumerable<>))
171+
{
172+
return type.GenericTypeArguments[0];
173+
}
174+
175+
return type;
176+
}
163177
}

src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,11 @@ public int ServiceWithAndWithoutAttribute(Service1 service, [FromService] Servic
13721372
return 1;
13731373
}
13741374

1375+
public int IEnumerableOfServiceWithoutAttribute(IEnumerable<Service1> services)
1376+
{
1377+
return 1;
1378+
}
1379+
13751380
public async Task Stream(ChannelReader<int> channelReader)
13761381
{
13771382
while (await channelReader.WaitToReadAsync())

src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4749,6 +4749,27 @@ public async Task ServiceResolvedWithoutAttribute()
47494749
}
47504750
}
47514751

4752+
[Fact]
4753+
public async Task ServiceResolvedForIEnumerableParameter()
4754+
{
4755+
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(provider =>
4756+
{
4757+
provider.AddSignalR(options =>
4758+
{
4759+
options.EnableDetailedErrors = true;
4760+
});
4761+
provider.AddSingleton<Service1>();
4762+
});
4763+
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<ServicesHub>>();
4764+
4765+
using (var client = new TestClient())
4766+
{
4767+
var connectionHandlerTask = await client.ConnectAsync(connectionHandler).DefaultTimeout();
4768+
var res = await client.InvokeAsync(nameof(ServicesHub.IEnumerableOfServiceWithoutAttribute)).DefaultTimeout();
4769+
Assert.Equal(1L, res.Result);
4770+
}
4771+
}
4772+
47524773
[Fact]
47534774
public async Task ServiceResolvedWithoutAttribute_WithHubSpecificSettingEnabled()
47544775
{
@@ -4839,6 +4860,26 @@ public async Task ServiceNotResolvedIfNotInDI()
48394860
}
48404861
}
48414862

4863+
[Fact]
4864+
public async Task ServiceNotResolvedForIEnumerableParameterIfNotInDI()
4865+
{
4866+
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(provider =>
4867+
{
4868+
provider.AddSignalR(options =>
4869+
{
4870+
options.EnableDetailedErrors = true;
4871+
});
4872+
});
4873+
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<ServicesHub>>();
4874+
4875+
using (var client = new TestClient())
4876+
{
4877+
var connectionHandlerTask = await client.ConnectAsync(connectionHandler).DefaultTimeout();
4878+
var res = await client.InvokeAsync(nameof(ServicesHub.IEnumerableOfServiceWithoutAttribute)).DefaultTimeout();
4879+
Assert.Equal("Failed to invoke 'IEnumerableOfServiceWithoutAttribute' due to an error on the server. InvalidDataException: Invocation provides 0 argument(s) but target expects 1.", res.Error);
4880+
}
4881+
}
4882+
48424883
[Fact]
48434884
public void TooManyParametersWithServiceThrows()
48444885
{

0 commit comments

Comments
 (0)