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:
Harsh Gupta 2015-02-09 18:18:07 -08:00
parent 1cb170ce02
commit 85b6382c69
21 changed files with 617 additions and 26 deletions

View File

@ -10,9 +10,12 @@ namespace Microsoft.AspNet.Mvc
/// Specifies that a parameter or property should be bound using form-data in the request body.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromFormAttribute : Attribute, IBindingSourceMetadata
public class FromFormAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
/// <inheritdoc />
public BindingSource BindingSource { get { return BindingSource.Form; } }
/// <inheritdoc />
public string Name { get; set; }
}
}

View File

@ -10,9 +10,12 @@ namespace Microsoft.AspNet.Mvc
/// Specifies that a parameter or property should be bound using the request query string.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromQueryAttribute : Attribute, IBindingSourceMetadata
public class FromQueryAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
/// <inheritdoc />
public BindingSource BindingSource { get { return BindingSource.Query; } }
/// <inheritdoc />
public string Name { get; set; }
}
}

View File

@ -10,9 +10,12 @@ namespace Microsoft.AspNet.Mvc
/// Specifies that a parameter or property should be bound using route-data from the current request.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromRouteAttribute : Attribute, IBindingSourceMetadata
public class FromRouteAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
/// <inheritdoc />
public BindingSource BindingSource { get { return BindingSource.Path; } }
/// <inheritdoc />
public string Name { get; set; }
}
}

View File

@ -22,8 +22,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var dto = (ComplexModelDto)bindingContext.ModelMetadata.Model;
foreach (var propertyMetadata in dto.PropertyMetadata)
{
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
propertyMetadata.PropertyName);
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
bindingContext.ModelName,
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
var propertyBindingContext = new ModelBindingContext(bindingContext,
propertyModelName,

View File

@ -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
// 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.
//
// Ex:

View File

@ -9,7 +9,7 @@ using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// An <see cref="IModelBinder"/> which binds models from the request headers when a model
/// An <see cref="IModelBinder"/> which binds models from the request headers when a model
/// has the binding source <see cref="BindingSource.Header"/>/
/// </summary>
public class HeaderModelBinder : BindingSourceModelBinder
@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var request = bindingContext.OperationBindingContext.HttpContext.Request;
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;
object model = null;
if (bindingContext.ModelType == typeof(string))

View File

@ -176,8 +176,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
metadata.PropertyName);
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
bindingContext.ModelName,
metadata.BinderModelName ?? metadata.PropertyName);
if (await valueProvider.ContainsPrefixAsync(propertyModelName))
{
@ -357,14 +358,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
{
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
// ensures a failure result from a required validator (if any) even for a non-nullable property.
// (Otherwise, propertyMetadata.Model is likely already null.)
var propertyMetadata = bindingContext.ModelMetadata.Properties[missingRequiredProperty];
propertyMetadata.Model = null;
var propertyName = propertyMetadata.BinderModelName ?? missingRequiredProperty;
var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
bindingContext.ModelName,
propertyName);
// Execute validator (if any) to get custom error message.
IModelValidator validator;
@ -379,7 +382,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
bindingContext.ModelState.TryAddModelError(
modelStateKey,
Resources.FormatMissingRequiredMember(missingRequiredProperty));
Resources.FormatMissingRequiredMember(propertyName));
}
}

View File

@ -156,7 +156,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
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))
{
isValid = false;

View File

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

View File

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

View File

@ -93,8 +93,32 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var result = JsonConvert.DeserializeObject<Result>(body);
Assert.Equal<string>(tags, result.HeaderValues);
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
[Fact]
public async Task FromHeader_BindHeader_ToString_OnParameter_CustomName()

View File

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

View File

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

View File

@ -44,6 +44,27 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
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]
[InlineData("GetBinderType_UseModelBinderOnType")]
[InlineData("GetBinderType_UseModelBinderProviderOnType")]

View File

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

View File

@ -128,6 +128,7 @@ namespace ModelBindingWebSite.Controllers
public string Title { get; set; }
[FromHeader(Name = "BlogTags")]
[Required]
public string[] Tags { get; set; }
public string Author { get; set; }

View File

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

View File

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

View File

@ -1,6 +1,7 @@
// 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 Microsoft.AspNet.Mvc;
namespace ModelBindingWebSite.Controllers
@ -13,5 +14,10 @@ namespace ModelBindingWebSite.Controllers
{
return company;
}
public Company CreateCompany(IList<Employee> employees)
{
return new Company { Employees = employees };
}
}
}

View File

@ -2,6 +2,9 @@
// 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
{
public class Employee : Person
@ -9,5 +12,18 @@ namespace ModelBindingWebSite
public string Department { 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; }
}
}

View File

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