Skip to content

Commit 9ffffa4

Browse files
Evaluate BoundProperties when inferring BindindSource (#46228)
* Evaluate BoundProperties when inferring BindindSource * Update src/Mvc/test/WebSites/BasicWebSite/Models/Contact.cs Co-authored-by: Stephen Halter <[email protected]> Co-authored-by: Stephen Halter <[email protected]>
1 parent 3bacfee commit 9ffffa4

File tree

5 files changed

+74
-9
lines changed

5 files changed

+74
-9
lines changed

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,8 @@ internal void InferParameterBindingSources(ActionModel action)
8484
var bindingSource = parameter.BindingInfo?.BindingSource;
8585
if (bindingSource == null)
8686
{
87-
bindingSource = InferBindingSourceForParameter(parameter);
88-
89-
parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo();
90-
parameter.BindingInfo.BindingSource = bindingSource;
87+
parameter.BindingInfo ??= new BindingInfo();
88+
parameter.BindingInfo.BindingSource = InferBindingSourceForParameter(parameter);
9189
}
9290
}
9391

@@ -113,16 +111,16 @@ internal void InferParameterBindingSources(ActionModel action)
113111
}
114112

115113
// Internal for unit testing.
116-
internal BindingSource InferBindingSourceForParameter(ParameterModel parameter)
114+
internal BindingSource? InferBindingSourceForParameter(ParameterModel parameter)
117115
{
118-
if (IsComplexTypeParameter(parameter))
116+
if (IsComplexTypeParameter(parameter, out var metadata))
119117
{
120118
if (IsService(parameter.ParameterType))
121119
{
122120
return BindingSource.Services;
123121
}
124122

125-
return BindingSource.Body;
123+
return metadata.BoundProperties.Any(prop => prop.BindingSource is not null) ? null : BindingSource.Body;
126124
}
127125

128126
if (ParameterExistsInAnyRoute(parameter.Action, parameter.ParameterName))
@@ -171,10 +169,10 @@ private static bool ParameterExistsInAnyRoute(ActionModel action, string paramet
171169
return false;
172170
}
173171

174-
private bool IsComplexTypeParameter(ParameterModel parameter)
172+
private bool IsComplexTypeParameter(ParameterModel parameter, out ModelMetadata metadata)
175173
{
176174
// No need for information from attributes on the parameter. Just use its type.
177-
var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterInfo.ParameterType);
175+
metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterInfo.ParameterType);
178176

179177
return metadata.IsComplexType;
180178
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,24 @@ public void Apply_DoesNotInferBindingSourceForParametersWithBindingInfo()
3131
Assert.Same(BindingSource.Custom, parameterModel.BindingInfo.BindingSource);
3232
}
3333

34+
[Fact]
35+
public void Apply_DoesNotInferBindingSourceFor_ComplexType_WithPropertiesWithBindingSource()
36+
{
37+
// Arrange
38+
var actionName = nameof(ParameterBindingController.CompositeComplexTypeModel);
39+
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
40+
var convention = GetConvention(modelMetadataProvider);
41+
var action = GetActionModel(typeof(ParameterBindingController), actionName);
42+
43+
// Act
44+
convention.Apply(action);
45+
46+
// Assert
47+
var parameterModel = Assert.Single(action.Parameters);
48+
Assert.NotNull(parameterModel.BindingInfo);
49+
Assert.Null(parameterModel.BindingInfo.BindingSource);
50+
}
51+
3452
[Fact]
3553
public void InferParameterBindingSources_Throws_IfMultipleParametersAreInferredAsBodyBound()
3654
{
@@ -952,6 +970,9 @@ private class ParameterBindingController
952970
[HttpPut("put-action/{id}")]
953971
public IActionResult ComplexTypeModel(TestModel model) => null;
954972

973+
[HttpPut("put-action/{id}")]
974+
public IActionResult CompositeComplexTypeModel(CompositeTestModel model) => null;
975+
955976
[HttpPut("put-action/{id}")]
956977
public IActionResult SimpleTypeModel(ConvertibleFromString model) => null;
957978

@@ -1094,6 +1115,14 @@ private class ParameterBindingNoRoutesOnController
10941115

10951116
private class TestModel { }
10961117

1118+
private class CompositeTestModel
1119+
{
1120+
[FromQuery]
1121+
public int Id { get; set; }
1122+
1123+
public TestModel TestModel { get; set; }
1124+
}
1125+
10971126
[TypeConverter(typeof(ConvertibleFromStringConverter))]
10981127
private class ConvertibleFromString { }
10991128

src/Mvc/test/Mvc.FunctionalTests/ApiBehaviorTest.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Net;
56
using System.Net.Http;
67
using System.Text;
78
using BasicWebSite.Models;
89
using Microsoft.AspNetCore.Hosting;
910
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
11+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
1012
using Newtonsoft.Json;
1113
using Newtonsoft.Json.Linq;
1214

@@ -145,6 +147,28 @@ private async Task ActionsWithApiBehaviorInferFromBodyParameters(string action)
145147
Assert.Equal(input.Name, result.Name);
146148
}
147149

150+
[Fact]
151+
public async Task ActionsWithApiBehavior_DoesNotInferFromBodyForCompositeComplexTypesParameters()
152+
{
153+
// Arrange
154+
var input = new Contact
155+
{
156+
ContactId = 13,
157+
Name = "Test123",
158+
};
159+
var requestId = 1;
160+
161+
// Act
162+
var response = await Client.PostAsJsonAsync($"/contact/ActionWithCompositeComplexTypeParameter/{requestId}", input);
163+
164+
// Assert
165+
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
166+
var result = JsonConvert.DeserializeObject<ContactRequest>(await response.Content.ReadAsStringAsync());
167+
Assert.Equal(input.ContactId, result.ContactInfo.ContactId);
168+
Assert.Equal(input.Name, result.ContactInfo.Name);
169+
Assert.Equal(requestId, result.Id);
170+
}
171+
148172
[Fact]
149173
public async Task ActionsWithApiBehavior_InferFromServicesParameters()
150174
{

src/Mvc/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ public ActionResult<string> ActionWithInferredModelBinderTypeWithExplicitModelNa
8787
public ActionResult<Contact> ActionWithInferredFromServicesParameter(int id, ContactsRepository repository)
8888
=> repository.GetContact(id) ?? new Contact() { ContactId = id };
8989

90+
[HttpPost("[action]/{id}")]
91+
public ActionResult<ContactRequest> ActionWithCompositeComplexTypeParameter(ContactRequest request, ContactsRepository repository)
92+
=> Ok(request);
93+
9094
[HttpGet("[action]")]
9195
public ActionResult<int> ActionReturningStatusCodeResult()
9296
{

src/Mvc/test/WebSites/BasicWebSite/Models/Contact.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.ComponentModel.DataAnnotations;
5+
using Microsoft.AspNetCore.Mvc;
56

67
namespace BasicWebSite.Models;
78

@@ -29,3 +30,12 @@ public class Contact
2930

3031
public string Self { get; set; }
3132
}
33+
34+
public class ContactRequest
35+
{
36+
[FromRoute]
37+
public int Id { get; set; }
38+
39+
[FromBody]
40+
public Contact ContactInfo { get; set; }
41+
}

0 commit comments

Comments
 (0)