Allow BindPropertyAttribute to be specified on controller classes

Fixes #7350
This commit is contained in:
Pranav K 2018-04-06 09:35:39 -07:00
parent c35030267c
commit d995b0418a
4 changed files with 191 additions and 0 deletions

View File

@ -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)
{

View File

@ -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

View File

@ -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; }
}
}
}

View File

@ -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;
}
}
}
}