parent
63397653fa
commit
de38922601
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class BindPropertyAttribute : Attribute, IModelNameProvider, IBinderTypeProviderMetadata, IRequestPredicateProvider
|
public class BindPropertyAttribute : Attribute, IModelNameProvider, IBinderTypeProviderMetadata, IRequestPredicateProvider
|
||||||
{
|
{
|
||||||
private static readonly Func<ActionContext, bool> _supportsAllRequests = (c) => true;
|
private static readonly Func<ActionContext, bool> _supportsAllRequests = (c) => true;
|
||||||
|
|
@ -31,22 +31,14 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
return _bindingSource;
|
return _bindingSource;
|
||||||
}
|
}
|
||||||
protected set
|
protected set => _bindingSource = value;
|
||||||
{
|
|
||||||
_bindingSource = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
Func<ActionContext, bool> IRequestPredicateProvider.RequestPredicate
|
Func<ActionContext, bool> IRequestPredicateProvider.RequestPredicate
|
||||||
{
|
=> SupportsGet ? _supportsAllRequests : _supportsNonGetRequests;
|
||||||
get
|
|
||||||
{
|
|
||||||
return SupportsGet ? _supportsAllRequests : _supportsNonGetRequests;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsNonGetRequest(ActionContext context)
|
private static bool IsNonGetRequest(ActionContext context)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return propertyModel;
|
return propertyModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the <see cref="ActionModel"/> instance for the given action <see cref="MethodInfo"/>.
|
/// Creates the <see cref="ActionModel"/> instance for the given action <see cref="MethodInfo"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -220,10 +220,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
throw new ArgumentNullException(nameof(property));
|
throw new ArgumentNullException(nameof(property));
|
||||||
}
|
}
|
||||||
|
|
||||||
var attributes = property.GetCustomAttributes(inherit: true);
|
var propertyAttributes = property.GetCustomAttributes(inherit: true);
|
||||||
var bindingInfo = BindingInfo.GetBindingInfo(attributes);
|
var handlerAttributes = property.DeclaringType.GetCustomAttributes(inherit: true);
|
||||||
|
|
||||||
var model = new PagePropertyModel(property, property.GetCustomAttributes(inherit: true))
|
// Look for binding info on the handler if nothing is specified on the property.
|
||||||
|
// This allows BindProperty attributes on handlers to apply to properties.
|
||||||
|
var bindingInfo = BindingInfo.GetBindingInfo(propertyAttributes) ??
|
||||||
|
BindingInfo.GetBindingInfo(handlerAttributes);
|
||||||
|
|
||||||
|
var model = new PagePropertyModel(property, propertyAttributes)
|
||||||
{
|
{
|
||||||
PropertyName = property.Name,
|
PropertyName = property.Name,
|
||||||
BindingInfo = bindingInfo,
|
BindingInfo = bindingInfo,
|
||||||
|
|
|
||||||
|
|
@ -1136,9 +1136,59 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.GetStringAsync("/Pages/Localized/PageWithModel?culture=fr-FR");
|
var response = await Client.GetStringAsync("/Pages/Localized/PageWithModel?culture=fr-FR");
|
||||||
|
|
||||||
|
// Assert
|
||||||
Assert.Equal(expected, response.Trim());
|
Assert.Equal(expected, response.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindPropertyAttribute_CanBeAppliedToModelType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Property1 = 123, Property2 = 25,";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "/Pages/PropertyBinding/BindPropertyOnModel?Property1=123")
|
||||||
|
{
|
||||||
|
Content = new FormUrlEncodedContent(new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("Property2", "25"),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
await AddAntiforgeryHeaders(request);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.StartsWith(expected, responseContent.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindingInfoOnPropertiesIsPreferredToBindingInfoOnType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Property1 = 123, Property2 = 25,";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "/Pages/PropertyBinding/BindPropertyOnModel?Property1=123")
|
||||||
|
{
|
||||||
|
Content = new FormUrlEncodedContent(new[]
|
||||||
|
{
|
||||||
|
// FormValueProvider appears before QueryStringValueProvider. However, the FromQuery explicitly listed
|
||||||
|
// on the property should cause it to use the latter.
|
||||||
|
new KeyValuePair<string, string>("Property1", "345"),
|
||||||
|
new KeyValuePair<string, string>("Property2", "25"),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
await AddAntiforgeryHeaders(request);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.StartsWith(expected, responseContent.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
|
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
var getResponse = await Client.GetAsync(request.RequestUri);
|
var getResponse = await Client.GetAsync(request.RequestUri);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
@ -229,6 +228,60 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
public string Property { get; set; }
|
public string Property { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnProvidersExecuting_DiscoversProperties_FromAllSubTypesThatDeclaresBindProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new DefaultPageApplicationModelProvider();
|
||||||
|
var typeInfo = typeof(BindPropertyAttributeOnBaseModelPage).GetTypeInfo();
|
||||||
|
var descriptor = new PageActionDescriptor();
|
||||||
|
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(context.PageApplicationModel);
|
||||||
|
Assert.Collection(
|
||||||
|
context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName).Where(p => p.BindingInfo != null),
|
||||||
|
property =>
|
||||||
|
{
|
||||||
|
var name = nameof(ModelLevel3.Property2);
|
||||||
|
Assert.Equal(typeof(ModelLevel3).GetProperty(name), property.PropertyInfo);
|
||||||
|
Assert.Equal(name, property.PropertyName);
|
||||||
|
Assert.NotNull(property.BindingInfo);
|
||||||
|
},
|
||||||
|
property =>
|
||||||
|
{
|
||||||
|
var name = nameof(ModelLevel3.Property3);
|
||||||
|
Assert.Equal(typeof(ModelLevel3).GetProperty(name), property.PropertyInfo);
|
||||||
|
Assert.Equal(name, property.PropertyName);
|
||||||
|
Assert.NotNull(property.BindingInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BindPropertyAttributeOnBaseModelPage : Page
|
||||||
|
{
|
||||||
|
public ModelLevel3 Model => null;
|
||||||
|
public override Task ExecuteAsync() => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModelLevel1 : PageModel
|
||||||
|
{
|
||||||
|
public string Property1 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[BindProperty]
|
||||||
|
private class ModelLevel2 : ModelLevel1
|
||||||
|
{
|
||||||
|
public string Property2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModelLevel3 : ModelLevel2
|
||||||
|
{
|
||||||
|
public string Property3 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OnProvidersExecuting_DiscoversHandlersFromPage()
|
public void OnProvidersExecuting_DiscoversHandlersFromPage()
|
||||||
{
|
{
|
||||||
|
|
@ -305,6 +358,53 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnProvidersExecuting_DiscoversBindingInfoFromHandler()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new DefaultPageApplicationModelProvider();
|
||||||
|
var typeInfo = typeof(PageWithBindPropertyModel).GetTypeInfo();
|
||||||
|
var modelType = typeof(ModelWithBindProperty);
|
||||||
|
var descriptor = new PageActionDescriptor();
|
||||||
|
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(context.PageApplicationModel);
|
||||||
|
Assert.Collection(
|
||||||
|
context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName),
|
||||||
|
property =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(ModelWithBindProperty.Property1), property.PropertyName);
|
||||||
|
Assert.NotNull(property.BindingInfo);
|
||||||
|
},
|
||||||
|
property =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(ModelWithBindProperty.Property2), property.PropertyName);
|
||||||
|
Assert.NotNull(property.BindingInfo);
|
||||||
|
Assert.Equal(BindingSource.Path, property.BindingInfo.BindingSource);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PageWithBindPropertyModel : PageBase
|
||||||
|
{
|
||||||
|
public ModelWithBindProperty Model => null;
|
||||||
|
|
||||||
|
public override Task ExecuteAsync() => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BindProperty]
|
||||||
|
[PageModel]
|
||||||
|
private class ModelWithBindProperty
|
||||||
|
{
|
||||||
|
public string Property1 { get; set; }
|
||||||
|
|
||||||
|
[FromRoute]
|
||||||
|
public string Property2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OnProvidersExecuting_DiscoversHandlersFromModel()
|
public void OnProvidersExecuting_DiscoversHandlersFromModel()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace RazorPagesWebSite
|
||||||
|
{
|
||||||
|
[BindProperty]
|
||||||
|
public class BindPropertyOnModel : PageModel
|
||||||
|
{
|
||||||
|
[FromQuery]
|
||||||
|
public string Property1 { get; set; }
|
||||||
|
|
||||||
|
public string Property2 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
@page
|
||||||
|
@model BindPropertyOnModel
|
||||||
|
|
||||||
|
Property1 = @Model.Property1, Property2 = @Model.Property2,
|
||||||
|
|
||||||
|
<form action="">
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
</form>
|
||||||
Loading…
Reference in New Issue