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 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
public class DefaultApplicationModelProviderTest
|
||||
{
|
||||
private readonly TestApplicationModelProvider Provider = new TestApplicationModelProvider();
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_DerivedFromControllerClass_HasFilter()
|
||||
{
|
||||
|
|
@ -1125,6 +1127,109 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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)
|
||||
{
|
||||
return selectors
|
||||
|
|
|
|||
|
|
@ -520,5 +520,47 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var title = document.QuerySelector("title").TextContent;
|
||||
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