Fix for #563 - FromForm, FromQuery and FromValue allow providing a Name which is used as a prefix. Also the name is used for reporting model state errors.
This commit is contained in:
parent
1cb170ce02
commit
85b6382c69
|
|
@ -10,9 +10,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// Specifies that a parameter or property should be bound using form-data in the request body.
|
/// Specifies that a parameter or property should be bound using form-data in the request body.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class FromFormAttribute : Attribute, IBindingSourceMetadata
|
public class FromFormAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public BindingSource BindingSource { get { return BindingSource.Form; } }
|
public BindingSource BindingSource { get { return BindingSource.Form; } }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// Specifies that a parameter or property should be bound using the request query string.
|
/// Specifies that a parameter or property should be bound using the request query string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class FromQueryAttribute : Attribute, IBindingSourceMetadata
|
public class FromQueryAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public BindingSource BindingSource { get { return BindingSource.Query; } }
|
public BindingSource BindingSource { get { return BindingSource.Query; } }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// Specifies that a parameter or property should be bound using route-data from the current request.
|
/// Specifies that a parameter or property should be bound using route-data from the current request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class FromRouteAttribute : Attribute, IBindingSourceMetadata
|
public class FromRouteAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public BindingSource BindingSource { get { return BindingSource.Path; } }
|
public BindingSource BindingSource { get { return BindingSource.Path; } }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var dto = (ComplexModelDto)bindingContext.ModelMetadata.Model;
|
var dto = (ComplexModelDto)bindingContext.ModelMetadata.Model;
|
||||||
foreach (var propertyMetadata in dto.PropertyMetadata)
|
foreach (var propertyMetadata in dto.PropertyMetadata)
|
||||||
{
|
{
|
||||||
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
|
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
|
||||||
propertyMetadata.PropertyName);
|
bindingContext.ModelName,
|
||||||
|
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
|
||||||
|
|
||||||
var propertyBindingContext = new ModelBindingContext(bindingContext,
|
var propertyBindingContext = new ModelBindingContext(bindingContext,
|
||||||
propertyModelName,
|
propertyModelName,
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// to just those that match. We can skip filtering when IsGreedy == true, because that can't use
|
// to just those that match. We can skip filtering when IsGreedy == true, because that can't use
|
||||||
// value providers.
|
// value providers.
|
||||||
//
|
//
|
||||||
// We also want to base this filtering on the - top-level value profider in case the data source
|
// We also want to base this filtering on the - top-level value provider in case the data source
|
||||||
// on this property doesn't intersect with the ambient data source.
|
// on this property doesn't intersect with the ambient data source.
|
||||||
//
|
//
|
||||||
// Ex:
|
// Ex:
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
var request = bindingContext.OperationBindingContext.HttpContext.Request;
|
||||||
var modelMetadata = bindingContext.ModelMetadata;
|
var modelMetadata = bindingContext.ModelMetadata;
|
||||||
|
|
||||||
// Property name can be null if the model metadata represents a type (rahter than a property or parameter).
|
// Property name can be null if the model metadata represents a type (rather than a property or parameter).
|
||||||
var headerName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName;
|
var headerName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName;
|
||||||
object model = null;
|
object model = null;
|
||||||
if (bindingContext.ModelType == typeof(string))
|
if (bindingContext.ModelType == typeof(string))
|
||||||
|
|
|
||||||
|
|
@ -176,8 +176,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
|
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
|
||||||
metadata.PropertyName);
|
bindingContext.ModelName,
|
||||||
|
metadata.BinderModelName ?? metadata.PropertyName);
|
||||||
|
|
||||||
if (await valueProvider.ContainsPrefixAsync(propertyModelName))
|
if (await valueProvider.ContainsPrefixAsync(propertyModelName))
|
||||||
{
|
{
|
||||||
|
|
@ -357,14 +358,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
|
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
|
||||||
{
|
{
|
||||||
var addedError = false;
|
var addedError = false;
|
||||||
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
|
|
||||||
bindingContext.ModelName, missingRequiredProperty);
|
|
||||||
|
|
||||||
// Update Model as SetProperty() would: Place null value where validator will check for non-null. This
|
// Update Model as SetProperty() would: Place null value where validator will check for non-null. This
|
||||||
// ensures a failure result from a required validator (if any) even for a non-nullable property.
|
// ensures a failure result from a required validator (if any) even for a non-nullable property.
|
||||||
// (Otherwise, propertyMetadata.Model is likely already null.)
|
// (Otherwise, propertyMetadata.Model is likely already null.)
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties[missingRequiredProperty];
|
var propertyMetadata = bindingContext.ModelMetadata.Properties[missingRequiredProperty];
|
||||||
propertyMetadata.Model = null;
|
propertyMetadata.Model = null;
|
||||||
|
var propertyName = propertyMetadata.BinderModelName ?? missingRequiredProperty;
|
||||||
|
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
|
||||||
|
bindingContext.ModelName,
|
||||||
|
propertyName);
|
||||||
|
|
||||||
// Execute validator (if any) to get custom error message.
|
// Execute validator (if any) to get custom error message.
|
||||||
IModelValidator validator;
|
IModelValidator validator;
|
||||||
|
|
@ -379,7 +382,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
bindingContext.ModelState.TryAddModelError(
|
bindingContext.ModelState.TryAddModelError(
|
||||||
modelStateKey,
|
modelStateKey,
|
||||||
Resources.FormatMissingRequiredMember(missingRequiredProperty));
|
Resources.FormatMissingRequiredMember(propertyName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
foreach (var childMetadata in metadata.Properties)
|
foreach (var childMetadata in metadata.Properties)
|
||||||
{
|
{
|
||||||
var childKey = ModelBindingHelper.CreatePropertyModelName(currentModelKey, childMetadata.PropertyName);
|
var propertyName = childMetadata.BinderModelName ?? childMetadata.PropertyName;
|
||||||
|
var childKey = ModelBindingHelper.CreatePropertyModelName(currentModelKey, propertyName);
|
||||||
if (!ValidateNonVisitedNodeAndChildren(childKey, childMetadata, validationContext, validators: null))
|
if (!ValidateNonVisitedNodeAndChildren(childKey, childMetadata, validationContext, validators: null))
|
||||||
{
|
{
|
||||||
isValid = false;
|
isValid = false;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using ModelBindingWebSite;
|
||||||
|
using ModelBindingWebSite.Controllers;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class ModelBindingFromFormTest
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(ModelBindingWebSite));
|
||||||
|
private readonly Action<IApplicationBuilder> _app = new ModelBindingWebSite.Startup().Configure;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromForm_CustomModelPrefix_ForParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url = "http://localhost/FromFormAttribute_Company/CreateCompany";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string,string>("customPrefix.Employees[0].Name", "somename"),
|
||||||
|
};
|
||||||
|
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(company.Employees);
|
||||||
|
|
||||||
|
Assert.Equal("somename", employee.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromForm_CustomModelPrefix_ForCollectionParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url = "http://localhost/FromFormAttribute_Company/CreateCompanyFromEmployees";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("customPrefix[0].Department", "Contoso"),
|
||||||
|
};
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(company.Employees);
|
||||||
|
Assert.Equal("Contoso", employee.Department);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromForm_CustomModelPrefix_ForProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url = "http://localhost/FromFormAttribute_Company/CreateCompany";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("customPrefix.Employees[0].EmployeeSSN", "123132131"),
|
||||||
|
};
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(company.Employees);
|
||||||
|
Assert.Equal("123132131", employee.SSN);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromForm_CustomModelPrefix_ForCollectionProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url = "http://localhost/FromFormAttribute_Company/CreateDepartment";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("department.TestEmployees[0].EmployeeSSN", "123132131"),
|
||||||
|
};
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var department = JsonConvert.DeserializeObject<
|
||||||
|
FromFormAttribute_CompanyController.FromForm_Department>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(department.Employees);
|
||||||
|
Assert.Equal("123132131", employee.SSN);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromForm_NonExistingValueAddsValidationErrors_OnProperty_UsingCustomModelPrefix()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url = "http://localhost/FromFormAttribute_Company/ValidateDepartment";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
|
|
||||||
|
// No values.
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>();
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||||
|
Assert.Null(result.Value);
|
||||||
|
var error = Assert.Single(result.ModelStateErrors);
|
||||||
|
Assert.Equal("TestEmployees", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -93,8 +93,32 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var result = JsonConvert.DeserializeObject<Result>(body);
|
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||||
Assert.Equal<string>(tags, result.HeaderValues);
|
Assert.Equal<string>(tags, result.HeaderValues);
|
||||||
var error = Assert.Single(result.ModelStateErrors);
|
var error = Assert.Single(result.ModelStateErrors);
|
||||||
Assert.Equal("Title", error);
|
Assert.Equal("BlogTitle", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromHeader_NonExistingHeaderAddsValidationErrors_OnCollectionProperty_CustomName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Blog/BindToProperty/CustomName");
|
||||||
|
request.Headers.TryAddWithoutValidation("BlogTitle", "Cooking Receipes.");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||||
|
Assert.Equal<string>("Cooking Receipes.", result.HeaderValue);
|
||||||
|
var error = Assert.Single(result.ModelStateErrors);
|
||||||
|
Assert.Equal("BlogTags", error);
|
||||||
|
}
|
||||||
|
|
||||||
// The action that this test hits will echo back the model-bound value
|
// The action that this test hits will echo back the model-bound value
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task FromHeader_BindHeader_ToString_OnParameter_CustomName()
|
public async Task FromHeader_BindHeader_ToString_OnParameter_CustomName()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using ModelBindingWebSite;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
using ModelBindingWebSite.Controllers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class ModelBindingFromQueryTest
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(ModelBindingWebSite));
|
||||||
|
private readonly Action<IApplicationBuilder> _app = new ModelBindingWebSite.Startup().Configure;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromQuery_CustomModelPrefix_ForParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// [FromQuery(Name = "customPrefix")] is used to apply a prefix
|
||||||
|
var url =
|
||||||
|
"http://localhost/FromQueryAttribute_Company/CreateCompany?customPrefix.Employees[0].Name=somename";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(company.Employees);
|
||||||
|
Assert.Equal("somename", employee.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromQuery_CustomModelPrefix_ForCollectionParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url =
|
||||||
|
"http://localhost/FromQueryAttribute_Company/CreateCompanyFromEmployees?customPrefix[0].Name=somename";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(company.Employees);
|
||||||
|
Assert.Equal("somename", employee.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromQuery_CustomModelPrefix_ForProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// [FromQuery(Name = "EmployeeId")] is used to apply a prefix
|
||||||
|
var url =
|
||||||
|
"http://localhost/FromQueryAttribute_Company/CreateCompany?customPrefix.Employees[0].EmployeeId=1234";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(company.Employees);
|
||||||
|
|
||||||
|
Assert.Equal(1234, employee.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromQuery_CustomModelPrefix_ForCollectionProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url =
|
||||||
|
"http://localhost/FromQueryAttribute_Company/CreateDepartment?TestEmployees[0].EmployeeId=1234";
|
||||||
|
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var department = JsonConvert.DeserializeObject<
|
||||||
|
FromQueryAttribute_CompanyController.FromQuery_Department>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(department.Employees);
|
||||||
|
Assert.Equal(1234, employee.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromQuery_NonExistingValueAddsValidationErrors_OnProperty_UsingCustomModelPrefix()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url =
|
||||||
|
"http://localhost/FromQueryAttribute_Company/ValidateDepartment?TestEmployees[0].Department=contoso";
|
||||||
|
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||||
|
var error = Assert.Single(result.ModelStateErrors);
|
||||||
|
Assert.Equal("TestEmployees[0].EmployeeId", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using ModelBindingWebSite;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class ModelBindingFromRouteTest
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(ModelBindingWebSite));
|
||||||
|
private readonly Action<IApplicationBuilder> _app = new ModelBindingWebSite.Startup().Configure;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromRoute_CustomModelPrefix_ForParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// [FromRoute(Name = "customPrefix")] is used to apply a prefix
|
||||||
|
var url =
|
||||||
|
"http://localhost/FromRouteAttribute_Company/CreateEmployee/somename";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var employee = JsonConvert.DeserializeObject<Employee>(body);
|
||||||
|
Assert.Equal("somename", employee.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromRoute_CustomModelPrefix_ForProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// [FromRoute(Name = "EmployeeId")] is used to apply a prefix
|
||||||
|
var url =
|
||||||
|
"http://localhost/FromRouteAttribute_Company/CreateEmployee/somename/1234";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var employee = JsonConvert.DeserializeObject<Employee>(body);
|
||||||
|
Assert.Equal(1234, employee.TaxId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FromRoute_NonExistingValueAddsValidationErrors_OnProperty_UsingCustomModelPrefix()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// [FromRoute(Name = "TestEmployees")] is used to apply a prefix
|
||||||
|
var url =
|
||||||
|
"http://localhost/FromRouteAttribute_Company/ValidateDepartment/contoso";
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
|
|
||||||
|
// No values.
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>();
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||||
|
Assert.Null(result.Value);
|
||||||
|
var error = Assert.Single(result.ModelStateErrors);
|
||||||
|
Assert.Equal("TestEmployees", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,27 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal("somename", employee.Name);
|
Assert.Equal("somename", employee.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ModelBinderAttribute_CustomModelPrefix_OnProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var url =
|
||||||
|
"http://localhost/ModelBinderAttribute_Company/CreateCompany?employees[0].Alias=somealias";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(body);
|
||||||
|
|
||||||
|
var employee = Assert.Single(company.Employees);
|
||||||
|
Assert.Equal("somealias", employee.EmailAlias);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("GetBinderType_UseModelBinderOnType")]
|
[InlineData("GetBinderType_UseModelBinderOnType")]
|
||||||
[InlineData("GetBinderType_UseModelBinderProviderOnType")]
|
[InlineData("GetBinderType_UseModelBinderProviderOnType")]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
{
|
||||||
|
[Route("FromFormAttribute_Company/[action]")]
|
||||||
|
public class FromFormAttribute_CompanyController : Controller
|
||||||
|
{
|
||||||
|
public Company CreateCompany([FromForm(Name = "customPrefix")] Company company)
|
||||||
|
{
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FromForm_Department CreateDepartment(FromForm_Department department)
|
||||||
|
{
|
||||||
|
return department;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Company CreateCompanyFromEmployees([FromForm(Name = "customPrefix")] IList<Employee> employees)
|
||||||
|
{
|
||||||
|
return new Company { Employees = employees };
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ValidateDepartment(FromForm_Department department)
|
||||||
|
{
|
||||||
|
return new Result()
|
||||||
|
{
|
||||||
|
Value = department.Employees,
|
||||||
|
ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FromForm_Department
|
||||||
|
{
|
||||||
|
[FromForm(Name = "TestEmployees")]
|
||||||
|
[Required]
|
||||||
|
public IEnumerable<Employee> Employees { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -128,6 +128,7 @@ namespace ModelBindingWebSite.Controllers
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
||||||
[FromHeader(Name = "BlogTags")]
|
[FromHeader(Name = "BlogTags")]
|
||||||
|
[Required]
|
||||||
public string[] Tags { get; set; }
|
public string[] Tags { get; set; }
|
||||||
|
|
||||||
public string Author { get; set; }
|
public string Author { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
{
|
||||||
|
[Route("FromQueryAttribute_Company/[action]")]
|
||||||
|
public class FromQueryAttribute_CompanyController : Controller
|
||||||
|
{
|
||||||
|
public Company CreateCompany([FromQuery(Name = "customPrefix")] Company company)
|
||||||
|
{
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Company CreateCompanyFromEmployees([FromQuery(Name = "customPrefix")] IList<Employee> employees)
|
||||||
|
{
|
||||||
|
return new Company { Employees = employees };
|
||||||
|
}
|
||||||
|
|
||||||
|
public FromQuery_Department CreateDepartment(FromQuery_Department department)
|
||||||
|
{
|
||||||
|
return department;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ValidateDepartment(FromQuery_Department department)
|
||||||
|
{
|
||||||
|
return new Result()
|
||||||
|
{
|
||||||
|
Value = department.Employees,
|
||||||
|
ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FromQuery_Department
|
||||||
|
{
|
||||||
|
[FromQuery(Name = "TestEmployees")]
|
||||||
|
[Required]
|
||||||
|
public IEnumerable<Employee> Employees { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
{
|
||||||
|
[Route("FromRouteAttribute_Company/[action]/{customPrefix.Name}")]
|
||||||
|
public class FromRouteAttribute_CompanyController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet("{customPrefix.EmployeeTaxId?}")]
|
||||||
|
public Employee CreateEmployee([FromRoute(Name = "customPrefix")] Employee employee)
|
||||||
|
{
|
||||||
|
return employee;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Company CreateCompanyFromEmployees([FromRoute(Name = "customPrefix")] IList<Employee> employees)
|
||||||
|
{
|
||||||
|
return new Company { Employees = employees };
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ValidateDepartment(FromRoute_Department department)
|
||||||
|
{
|
||||||
|
return new Result()
|
||||||
|
{
|
||||||
|
Value = department.Employees,
|
||||||
|
ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FromRoute_Department
|
||||||
|
{
|
||||||
|
[FromRoute(Name = "TestEmployees")]
|
||||||
|
[Required]
|
||||||
|
public IEnumerable<Employee> Employees { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
namespace ModelBindingWebSite.Controllers
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
|
@ -13,5 +14,10 @@ namespace ModelBindingWebSite.Controllers
|
||||||
{
|
{
|
||||||
return company;
|
return company;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Company CreateCompany(IList<Employee> employees)
|
||||||
|
{
|
||||||
|
return new Company { Employees = employees };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
namespace ModelBindingWebSite
|
namespace ModelBindingWebSite
|
||||||
{
|
{
|
||||||
public class Employee : Person
|
public class Employee : Person
|
||||||
|
|
@ -9,5 +12,18 @@ namespace ModelBindingWebSite
|
||||||
public string Department { get; set; }
|
public string Department { get; set; }
|
||||||
|
|
||||||
public string Location { get; set; }
|
public string Location { get; set; }
|
||||||
|
|
||||||
|
[FromQuery(Name = "EmployeeId")]
|
||||||
|
[Range(1, 10000)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[FromRoute(Name = "EmployeeTaxId")]
|
||||||
|
public int TaxId { get; set; }
|
||||||
|
|
||||||
|
[FromForm(Name = "EmployeeSSN")]
|
||||||
|
public string SSN { get; set; }
|
||||||
|
|
||||||
|
[ModelBinder(Name = "Alias")]
|
||||||
|
public string EmailAlias { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class Result
|
||||||
|
{
|
||||||
|
public object Value { get; set; }
|
||||||
|
|
||||||
|
public string[] ModelStateErrors { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue