Allow BindPropertyAttribute to be specified on controller classes
Fixes #7350
This commit is contained in:
parent
c35030267c
commit
d995b0418a
|
|
@ -221,6 +221,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
var modelMetadata = _modelMetadataProvider.GetMetadataForProperty(propertyInfo.DeclaringType, propertyInfo.Name);
|
var modelMetadata = _modelMetadataProvider.GetMetadataForProperty(propertyInfo.DeclaringType, propertyInfo.Name);
|
||||||
var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);
|
var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);
|
||||||
|
if (bindingInfo == null)
|
||||||
|
{
|
||||||
|
var declaringType = propertyInfo.DeclaringType;
|
||||||
|
if (declaringType.IsDefined(typeof(BindPropertyAttribute), inherit: true))
|
||||||
|
{
|
||||||
|
// Specify a BindingInfo so that the property is now considered for model binding.
|
||||||
|
bindingInfo = new BindingInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var propertyModel = new PropertyModel(propertyInfo, attributes)
|
var propertyModel = new PropertyModel(propertyInfo, attributes)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class DefaultApplicationModelProviderTest
|
public class DefaultApplicationModelProviderTest
|
||||||
{
|
{
|
||||||
|
private readonly TestApplicationModelProvider Provider = new TestApplicationModelProvider();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateControllerModel_DerivedFromControllerClass_HasFilter()
|
public void CreateControllerModel_DerivedFromControllerClass_HasFilter()
|
||||||
{
|
{
|
||||||
|
|
@ -1125,6 +1127,109 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Equal(typeInfo, action.ActionMethod.DeclaringType.GetTypeInfo());
|
Assert.Equal(typeInfo, action.ActionMethod.DeclaringType.GetTypeInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BindProperty]
|
||||||
|
public class BindPropertyController
|
||||||
|
{
|
||||||
|
public string Property { get; set; }
|
||||||
|
|
||||||
|
[ModelBinder(typeof(object))]
|
||||||
|
public string BinderType { get; set; }
|
||||||
|
|
||||||
|
[FromRoute]
|
||||||
|
public string BinderSource { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreatePropertyModel_AddsBindingInfoToProperty_IfDeclaringTypeHasBindPropertyAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyInfo = typeof(BindPropertyController).GetProperty(nameof(BindPropertyController.Property));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var property = Provider.CreatePropertyModel(propertyInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var bindingInfo = property.BindingInfo;
|
||||||
|
Assert.NotNull(bindingInfo);
|
||||||
|
Assert.Null(bindingInfo.BinderModelName);
|
||||||
|
Assert.Null(bindingInfo.BinderType);
|
||||||
|
Assert.Null(bindingInfo.BindingSource);
|
||||||
|
Assert.Null(bindingInfo.PropertyFilterProvider);
|
||||||
|
Assert.Null(bindingInfo.RequestPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreatePropertyModel_DoesNotSetBindingInfo_IfPropertySpecifiesBinderType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyInfo = typeof(BindPropertyController).GetProperty(nameof(BindPropertyController.BinderType));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var property = Provider.CreatePropertyModel(propertyInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var bindingInfo = property.BindingInfo;
|
||||||
|
Assert.Same(typeof(object), bindingInfo.BinderType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreatePropertyModel_DoesNotSetBindingInfo_IfPropertySpecifiesBinderSource()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyInfo = typeof(BindPropertyController).GetProperty(nameof(BindPropertyController.BinderSource));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var property = Provider.CreatePropertyModel(propertyInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var bindingInfo = property.BindingInfo;
|
||||||
|
Assert.Null(bindingInfo.BinderType);
|
||||||
|
Assert.Same(BindingSource.Path, property.BindingInfo.BindingSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class DerivedFromBindPropertyController : BindPropertyController
|
||||||
|
{
|
||||||
|
public string DerivedProperty { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreatePropertyModel_AppliesBindPropertyAttributeDeclaredOnBaseType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var propertyInfo = typeof(DerivedFromBindPropertyController).GetProperty(nameof(DerivedFromBindPropertyController.DerivedProperty));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var property = Provider.CreatePropertyModel(propertyInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(property.BindingInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BindProperty]
|
||||||
|
public class UserController : ControllerBase
|
||||||
|
{
|
||||||
|
public string DerivedProperty { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreatePropertyModel_DoesNotApplyBindingInfoToPropertiesOnBaseType()
|
||||||
|
{
|
||||||
|
// This test ensures that applying BindPropertyAttribute on a user defined type does not cause properties on
|
||||||
|
// Controller \ ControllerBase to be treated as model bound.
|
||||||
|
// Arrange
|
||||||
|
var derivedPropertyInfo = typeof(UserController).GetProperty(nameof(UserController.DerivedProperty));
|
||||||
|
var basePropertyInfo = typeof(UserController).GetProperty(nameof(ControllerBase.ControllerContext));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var derivedProperty = Provider.CreatePropertyModel(derivedPropertyInfo);
|
||||||
|
var baseProperty = Provider.CreatePropertyModel(basePropertyInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(derivedProperty.BindingInfo);
|
||||||
|
Assert.Null(baseProperty.BindingInfo);
|
||||||
|
}
|
||||||
|
|
||||||
private IList<AttributeRouteModel> GetAttributeRoutes(IList<SelectorModel> selectors)
|
private IList<AttributeRouteModel> GetAttributeRoutes(IList<SelectorModel> selectors)
|
||||||
{
|
{
|
||||||
return selectors
|
return selectors
|
||||||
|
|
|
||||||
|
|
@ -520,5 +520,47 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
var title = document.QuerySelector("title").TextContent;
|
var title = document.QuerySelector("title").TextContent;
|
||||||
Assert.Equal("View Data Property Sample", title);
|
Assert.Equal("View Data Property Sample", title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindPropertyCanBeAppliedToControllers()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("BindProperty/Action?Name=TestName&Id=10");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var data = JsonConvert.DeserializeObject<BindPropertyControllerData>(content);
|
||||||
|
|
||||||
|
Assert.Equal("TestName", data.Name);
|
||||||
|
Assert.Equal(10, data.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindProperty_DoesNotApplyToPropertiesWithBindingInfo()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("BindProperty/Action?Id=10&IdFromRoute=12&CustomBound=Test");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var data = JsonConvert.DeserializeObject<BindPropertyControllerData>(content);
|
||||||
|
|
||||||
|
Assert.Equal(10, data.Id);
|
||||||
|
Assert.Null(data.IdFromRoute);
|
||||||
|
Assert.Equal("CustomBoundValue", data.CustomBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BindPropertyControllerData
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
public int? IdFromRoute { get; set; }
|
||||||
|
|
||||||
|
public string CustomBound { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace BasicWebSite
|
||||||
|
{
|
||||||
|
[BindProperty]
|
||||||
|
public class BindPropertyController : Controller
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
[FromRoute]
|
||||||
|
public int? IdFromRoute { get; set; }
|
||||||
|
|
||||||
|
[ModelBinder(typeof(CustomBoundModelBinder))]
|
||||||
|
public string CustomBound { get; set; }
|
||||||
|
|
||||||
|
public object Action() => new { Name, Id, IdFromRoute, CustomBound };
|
||||||
|
|
||||||
|
private class CustomBoundModelBinder : IModelBinder
|
||||||
|
{
|
||||||
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
bindingContext.Result = ModelBindingResult.Success("CustomBoundValue");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue