diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs
index 6854684b12..8e9b37db46 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@@ -82,6 +83,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
PrototypeCache = prototypeCache;
}
+ // Sealing for consistency with other properties.
+ // ModelMetadata already caches the collection and we have no use case for a ComputeAdditionalValues() method.
+ ///
+ public sealed override IDictionary AdditionalValues
+ {
+ get
+ {
+ return base.AdditionalValues;
+ }
+ }
+
///
public sealed override IBinderMetadata BinderMetadata
{
@@ -419,6 +431,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
+ // Sealing for consistency with other properties.
+ // ModelMetadata already caches the collection and we have no use case for a ComputeProperties() method.
+ ///
+ public override ModelPropertyCollection Properties
+ {
+ get
+ {
+ return base.Properties;
+ }
+ }
+
///
public sealed override bool ShowForDisplay
{
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs
index b841024d50..07e74cda4e 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs
@@ -47,6 +47,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
_isRequired = !modelType.AllowsNullValue();
}
+ ///
+ /// Gets a collection of additional information about the model.
+ ///
+ public virtual IDictionary AdditionalValues { get; }
+ = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
///
/// Gets or sets the name of a model if specified explicitly using .
///
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs
index af986d4cf4..1da95943f7 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs
@@ -1551,5 +1551,55 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var result = await response.Content.ReadAsStringAsync();
Assert.Equal("The value 'random string' is not valid for birthdate.", result);
}
+
+ [Fact]
+ public async Task OverriddenMetadataProvider_CanChangeAdditionalValues()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+ var url = "http://localhost/AdditionalValues";
+ var expectedDictionary = new Dictionary
+ {
+ { "key1", "7d6d0de2-8d59-49ac-99cc-881423b75a76" },
+ { "key2", "value2" },
+ };
+
+ // Act
+ var response = await client.GetAsync(url);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var dictionary = JsonConvert.DeserializeObject>(responseContent);
+ Assert.Equal(expectedDictionary, dictionary);
+ }
+
+ [Fact]
+ public async Task OverriddenMetadataProvider_CanUseAttributesToChangeAdditionalValues()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+ var url = "http://localhost/GroupNames";
+ var expectedDictionary = new Dictionary
+ {
+ { "Model", "MakeAndModelGroup" },
+ { "Make", "MakeAndModelGroup" },
+ { "Vin", null },
+ { "Year", null },
+ { "InspectedDates", null },
+ { "LastUpdatedTrackingId", "TrackingIdGroup" },
+ };
+
+ // Act
+ var response = await client.GetAsync(url);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var dictionary = JsonConvert.DeserializeObject>(responseContent);
+ Assert.Equal(expectedDictionary, dictionary);
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs
index 3adf5b448d..ae225ff1b2 100644
--- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs
@@ -29,11 +29,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
attributes: attributes);
// Assert
+ Assert.NotNull(metadata.AdditionalValues);
+ Assert.Empty(metadata.AdditionalValues);
+ Assert.Null(metadata.Container);
+ Assert.Null(metadata.ContainerType);
+
Assert.True(metadata.ConvertEmptyStringToNull);
Assert.False(metadata.HasNonDefaultEditFormat);
Assert.False(metadata.HideSurroundingHtml);
Assert.True(metadata.HtmlEncode);
+ Assert.False(metadata.IsCollectionType);
Assert.True(metadata.IsComplexType);
+ Assert.False(metadata.IsNullableValueType);
Assert.False(metadata.IsReadOnly);
Assert.False(metadata.IsRequired);
Assert.True(metadata.ShowForDisplay);
@@ -48,6 +55,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(metadata.SimpleDisplayText);
Assert.Null(metadata.TemplateHint);
+ Assert.Null(metadata.Model);
+ Assert.Equal(typeof(object), metadata.ModelType);
+ Assert.Equal(typeof(object), metadata.RealModelType);
+ Assert.Null(metadata.PropertyName);
+
Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order);
Assert.Null(metadata.BinderModelName);
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs
index 9f53c03c5e..d61651162d 100644
--- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using Xunit;
@@ -76,6 +75,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
new ModelMetadata(provider, typeof(Exception), () => "model", typeof(string), "propertyName");
// Assert
+ Assert.NotNull(metadata.AdditionalValues);
+ Assert.Empty(metadata.AdditionalValues);
Assert.Equal(typeof(Exception), metadata.ContainerType);
Assert.Null(metadata.Container);
@@ -114,6 +115,58 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(metadata.PropertyBindingPredicateProvider);
}
+
+ // AdditionalValues
+
+ [Fact]
+ public void AdditionalValues_CreatedOnce()
+ {
+
+ // Arrange
+ var provider = new EmptyModelMetadataProvider();
+ var metadata = new ModelMetadata(
+ provider,
+ containerType: null,
+ modelAccessor: () => null,
+ modelType: typeof(object),
+ propertyName: null);
+
+ // Act
+ var result1 = metadata.AdditionalValues;
+ var result2 = metadata.AdditionalValues;
+
+ // Assert
+ Assert.Same(result1, result2);
+ }
+
+ [Fact]
+ public void AdditionalValues_ChangesPersist()
+ {
+ // Arrange
+ var provider = new EmptyModelMetadataProvider();
+ var metadata = new ModelMetadata(
+ provider,
+ containerType: null,
+ modelAccessor: () => null,
+ modelType: typeof(object),
+ propertyName: null);
+ var valuesDictionary = new Dictionary
+ {
+ { "key1", new object() },
+ { "key2", "value2" },
+ { "key3", new object() },
+ };
+
+ // Act
+ foreach (var keyValuePair in valuesDictionary)
+ {
+ metadata.AdditionalValues.Add(keyValuePair);
+ }
+
+ // Assert
+ Assert.Equal(valuesDictionary, metadata.AdditionalValues);
+ }
+
// IsComplexType
private struct IsComplexTypeModel
@@ -304,7 +357,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
get
{
- // ModelMetadata does not reorder properties Reflection returns without an Order override.
+ // ModelMetadata does not reorder properties the provider returns without an Order override.
return new TheoryData, IEnumerable>
{
{
@@ -329,7 +382,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Theory]
[MemberData(nameof(PropertyNamesTheoryData))]
- public void PropertiesProperty_WithDefaultOrder_OrdersPropertyNamesAlphabetically(
+ public void PropertiesProperty_WithDefaultOrder_OrdersPropertyNamesAsProvided(
IEnumerable originalNames,
IEnumerable expectedNames)
{
@@ -418,7 +471,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Theory]
[MemberData(nameof(PropertyNamesAndOrdersTheoryData))]
- public void PropertiesProperty_OrdersPropertyNamesUsingOrder_ThenAlphabetically(
+ public void PropertiesProperty_OrdersPropertyNamesUsingOrder_ThenAsProvided(
IEnumerable> originalNamesAndOrders,
IEnumerable expectedNames)
{
diff --git a/test/WebSites/ModelBindingWebSite/AdditionalValuesMetadataProvider.cs b/test/WebSites/ModelBindingWebSite/AdditionalValuesMetadataProvider.cs
new file mode 100644
index 0000000000..59a50670d3
--- /dev/null
+++ b/test/WebSites/ModelBindingWebSite/AdditionalValuesMetadataProvider.cs
@@ -0,0 +1,54 @@
+// 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.ComponentModel.DataAnnotations;
+using System.Linq;
+using Microsoft.AspNet.Mvc.ModelBinding;
+using ModelBindingWebSite.Models;
+
+namespace ModelBindingWebSite
+{
+ public class AdditionalValuesMetadataProvider : DataAnnotationsModelMetadataProvider
+ {
+ public static readonly string GroupNameKey = "__GroupName";
+ private static Guid _guid = new Guid("7d6d0de2-8d59-49ac-99cc-881423b75a76");
+
+ protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(
+ CachedDataAnnotationsModelMetadata prototype,
+ Func