Skip to content

Commit

Permalink
[release/7.0] Infer parameter source from ElementType for IEnumerable (
Browse files Browse the repository at this point in the history
…#45256)

* Infer ElementType for IEnumerable
* Update src/Mvc/Mvc.Core/test/ApplicationModels/InferParameterBindingInfoConventionTest.cs
* Update src/Mvc/Mvc.Core/src/ApplicationModels/InferParameterBindingInfoConvention.cs
* Fix SignalR fromservices inference

Co-authored-by: Bruno Oliveira <brolivei@microsoft.com>
Co-authored-by: Bruno Oliveira <brunolins16@users.noreply.github.com>
Co-authored-by: Safia Abdalla <safia@safia.rocks>
  • Loading branch information
4 people authored Nov 30, 2022
1 parent 9275bc9 commit b9e4f66
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ internal BindingSource InferBindingSourceForParameter(ParameterModel parameter)
{
if (IsComplexTypeParameter(parameter))
{
if (_serviceProviderIsService?.IsService(parameter.ParameterType) is true)
if (IsService(parameter.ParameterType))
{
return BindingSource.Services;
}
Expand All @@ -136,6 +136,25 @@ internal BindingSource InferBindingSourceForParameter(ParameterModel parameter)
return BindingSource.Query;
}

private bool IsService(Type type)
{
if (_serviceProviderIsService == null)
{
return false;
}

// IServiceProviderIsService will special case IEnumerable<> and always return true
// so, in this case checking the element type instead
if (type.IsConstructedGenericType &&
type.GetGenericTypeDefinition() is Type genericDefinition &&
genericDefinition == typeof(IEnumerable<>))
{
type = type.GenericTypeArguments[0];
}

return _serviceProviderIsService.IsService(type);
}

private static bool ParameterExistsInAnyRoute(ActionModel action, string parameterName)
{
foreach (var selector in ActionAttributeRouteModel.FlattenSelectors(action))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,21 @@ public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfSimpleTypes
Assert.Same(BindingSource.Body, result);
}

[Fact]
public void InferBindingSourceForParameter_ReturnsBodyForIEnumerableOfSimpleTypes()
{
// Arrange
var actionName = nameof(ParameterBindingController.IEnumerableOfSimpleTypes);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();

// Act
var result = convention.InferBindingSourceForParameter(parameter);

// Assert
Assert.Same(BindingSource.Body, result);
}

[Fact]
public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfComplexTypes()
{
Expand All @@ -558,6 +573,21 @@ public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfComplexType
Assert.Same(BindingSource.Body, result);
}

[Fact]
public void InferBindingSourceForParameter_ReturnsBodyForIEnumerableOfComplexTypes()
{
// Arrange
var actionName = nameof(ParameterBindingController.IEnumerableOfComplexTypes);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();

// Act
var result = convention.InferBindingSourceForParameter(parameter);

// Assert
Assert.Same(BindingSource.Body, result);
}

[Fact]
public void InferBindingSourceForParameter_ReturnsServicesForComplexTypesRegisteredInDI()
{
Expand All @@ -576,6 +606,24 @@ public void InferBindingSourceForParameter_ReturnsServicesForComplexTypesRegiste
Assert.Same(BindingSource.Services, result);
}

[Fact]
public void InferBindingSourceForParameter_ReturnsServicesForIEnumerableOfComplexTypesRegisteredInDI()
{
// Arrange
var actionName = nameof(ParameterBindingController.IEnumerableServiceParameter);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
// Using any built-in type defined in the Test action
var serviceProvider = Mock.Of<IServiceProviderIsService>(s => s.IsService(typeof(IApplicationModelProvider)) == true);
var convention = GetConvention(serviceProviderIsService: serviceProvider);

// Act
var result = convention.InferBindingSourceForParameter(parameter);

// Assert
Assert.True(convention.IsInferForServiceParametersEnabled);
Assert.Same(BindingSource.Services, result);
}

[Fact]
public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultName()
{
Expand Down Expand Up @@ -982,9 +1030,15 @@ private class ParameterBindingController

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

public IActionResult IEnumerableOfSimpleTypes(IEnumerable<int> parameter) => null;

public IActionResult CollectionOfComplexTypes(IList<TestModel> parameter) => null;

public IActionResult IEnumerableOfComplexTypes(IEnumerable<TestModel> parameter) => null;

public IActionResult ServiceParameter(IApplicationModelProvider parameter) => null;

public IActionResult IEnumerableServiceParameter(IEnumerable<IApplicationModelProvider> parameter) => null;
}

[ApiController]
Expand Down
16 changes: 15 additions & 1 deletion src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public HubMethodDescriptor(ObjectMethodExecutor methodExecutor, IServiceProvider
return false;
}
else if (p.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)) ||
serviceProviderIsService?.IsService(p.ParameterType) == true)
serviceProviderIsService?.IsService(GetServiceType(p.ParameterType)) == true)
{
if (index >= 64)
{
Expand Down Expand Up @@ -160,4 +160,18 @@ private static Func<object, CancellationToken, IAsyncEnumerator<object>> Compile
var lambda = Expression.Lambda<Func<object, CancellationToken, IAsyncEnumerator<object>>>(methodCall, parameters);
return lambda.Compile();
}

private static Type GetServiceType(Type type)
{
// IServiceProviderIsService will special case IEnumerable<> and always return true
// so, in this case checking the element type instead
if (type.IsConstructedGenericType &&
type.GetGenericTypeDefinition() is Type genericDefinition &&
genericDefinition == typeof(IEnumerable<>))
{
return type.GenericTypeArguments[0];
}

return type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,11 @@ public int ServiceWithAndWithoutAttribute(Service1 service, [FromService] Servic
return 1;
}

public int IEnumerableOfServiceWithoutAttribute(IEnumerable<Service1> services)
{
return 1;
}

public async Task Stream(ChannelReader<int> channelReader)
{
while (await channelReader.WaitToReadAsync())
Expand Down
41 changes: 41 additions & 0 deletions src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4749,6 +4749,27 @@ public async Task ServiceResolvedWithoutAttribute()
}
}

[Fact]
public async Task ServiceResolvedForIEnumerableParameter()
{
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(provider =>
{
provider.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
});
provider.AddSingleton<Service1>();
});
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<ServicesHub>>();

using (var client = new TestClient())
{
var connectionHandlerTask = await client.ConnectAsync(connectionHandler).DefaultTimeout();
var res = await client.InvokeAsync(nameof(ServicesHub.IEnumerableOfServiceWithoutAttribute)).DefaultTimeout();
Assert.Equal(1L, res.Result);
}
}

[Fact]
public async Task ServiceResolvedWithoutAttribute_WithHubSpecificSettingEnabled()
{
Expand Down Expand Up @@ -4839,6 +4860,26 @@ public async Task ServiceNotResolvedIfNotInDI()
}
}

[Fact]
public async Task ServiceNotResolvedForIEnumerableParameterIfNotInDI()
{
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(provider =>
{
provider.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
});
});
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<ServicesHub>>();

using (var client = new TestClient())
{
var connectionHandlerTask = await client.ConnectAsync(connectionHandler).DefaultTimeout();
var res = await client.InvokeAsync(nameof(ServicesHub.IEnumerableOfServiceWithoutAttribute)).DefaultTimeout();
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);
}
}

[Fact]
public void TooManyParametersWithServiceThrows()
{
Expand Down

0 comments on commit b9e4f66

Please sign in to comment.