diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Expressions/ExpressionMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Expressions/ExpressionMetadataProvider.cs index df4e88326b..4d7957cbc7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Expressions/ExpressionMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Expressions/ExpressionMetadataProvider.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -73,6 +72,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions modelAccessor, typeof(TValue), propertyName, + container, containerType, metadataProvider); } @@ -92,12 +92,14 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions Type modelType = null; Func modelAccessor = null; string propertyName = null; + object container = null; if (viewDataInfo != null) { if (viewDataInfo.Container != null) { containerType = viewDataInfo.Container.GetType(); + container = viewDataInfo.Container; } modelAccessor = () => viewDataInfo.Value; @@ -124,8 +126,12 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions } } - return GetMetadataFromProvider(modelAccessor, modelType ?? typeof(string), propertyName, containerType, - metadataProvider); + return GetMetadataFromProvider(modelAccessor, + modelType ?? typeof(string), + propertyName, + container, + containerType, + metadataProvider); } private static ModelMetadata FromModel([NotNull] ViewDataDictionary viewData, @@ -138,6 +144,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions modelAccessor: null, modelType: typeof(string), propertyName: null, + container: null, containerType: null, metadataProvider: metadataProvider); } @@ -149,12 +156,23 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions // An IModelMetadataProvider is not required unless this method is called. Therefore other methods in this // class lack [NotNull] attributes for their corresponding parameter. - private static ModelMetadata GetMetadataFromProvider(Func modelAccessor, Type modelType, - string propertyName, Type containerType, [NotNull] IModelMetadataProvider metadataProvider) + private static ModelMetadata GetMetadataFromProvider(Func modelAccessor, + Type modelType, + string propertyName, + object container, + Type containerType, + [NotNull] IModelMetadataProvider metadataProvider) { if (containerType != null && !string.IsNullOrEmpty(propertyName)) { - return metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName); + var metadata = + metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName); + if (metadata != null) + { + metadata.Container = container; + } + + return metadata; } return metadataProvider.GetMetadataForType(modelAccessor, modelType); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs index 1f7a45cf0a..91613f1343 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/AssociatedMetadataProvider.cs @@ -97,7 +97,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { modelAccessor = () => propertyInfo.PropertyHelper.GetValue(container); } - yield return CreatePropertyMetadata(modelAccessor, propertyInfo); + var propertyMetadata = CreatePropertyMetadata(modelAccessor, propertyInfo); + if (propertyMetadata != null) + { + propertyMetadata.Container = container; + } + + yield return propertyMetadata; } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs index 93cab06edb..f8e88371dc 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs @@ -67,6 +67,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public IBinderMetadata BinderMetadata { get; set; } + /// + /// A reference to the model's container . + /// Will be non-null if the model represents a property. + /// + public object Container { get; set; } + public Type ContainerType { get { return _containerType; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/Expression/ExpressionMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/Expression/ExpressionMetadataProviderTest.cs new file mode 100644 index 0000000000..6d6202a56d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/Expression/ExpressionMetadataProviderTest.cs @@ -0,0 +1,59 @@ +// 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 Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Rendering.Expressions +{ + public class ExpressionMetadataProviderTest + { + [Fact] + public void FromLambaExpression_SetsContainerAsExpected() + { + // Arrange + var myModel = new TestModel { SelectedCategory = new Category() }; + var provider = new EmptyModelMetadataProvider(); + var viewData = new ViewDataDictionary(provider); + viewData.Model = myModel; + + // Act + var metadata = ExpressionMetadataProvider.FromLambdaExpression( + model => model.SelectedCategory, + viewData, + provider); + + // Assert + Assert.Same(myModel, metadata.Container); + } + + [Fact] + public void FromStringExpression_SetsContainerAsExpected() + { + // Arrange + var myModel = new TestModel { SelectedCategory = new Category() }; + var provider = new EmptyModelMetadataProvider(); + var viewData = new ViewDataDictionary(provider); + viewData["Object"] = myModel; + + // Act + var metadata = ExpressionMetadataProvider.FromStringExpression("Object.SelectedCategory", + viewData, + provider); + + // Assert + Assert.Same(myModel, metadata.Container); + } + + private class TestModel + { + public Category SelectedCategory { get; set; } + } + + private class Category + { + public int CategoryId { get; set; } + public string CategoryName { get; set; } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs index 5c2c837b8b..f68b830813 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/AssociatedMetadataProviderTest.cs @@ -199,6 +199,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } #endif + [Fact] + public void GetMetadataForProperties_SetsContainerAsExpected() + { + // Arrange + var model = new PropertyModel { LocalAttributes = 42, MetadataAttributes = "hello", MixedAttributes = 21.12 }; + var provider = new EmptyModelMetadataProvider(); + + // Act + var metadata = provider.GetMetadataForProperties(model, typeof(PropertyModel)).ToList(); + + // Assert + Assert.Equal(3, metadata.Count); + Assert.Same(model, metadata[0].Container); + Assert.Same(model, metadata[1].Container); + Assert.Same(model, metadata[2].Container); + } + // Helpers // TODO: This type used System.ComponentModel.MetadataType to separate attribute declaration from property diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs index ebf76df8e0..3ddcb9faee 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs @@ -17,6 +17,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { get { + var emptycontainerModel = new DummyModelContainer(); + var contactModel = new DummyContactModel { FirstName = "test" }; + var nonEmptycontainerModel = new DummyModelContainer { Model = contactModel }; + return new TheoryData, Func, object> { { m => m.ConvertEmptyStringToNull = false, m => m.ConvertEmptyStringToNull, false }, @@ -38,6 +42,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { m => m.TemplateHint = "New template hint", m => m.TemplateHint, "New template hint" }, { m => m.Order = 23, m => m.Order, 23 }, + { m => m.Container = null, m => m.Container, null }, + { m => m.Container = emptycontainerModel, m => m.Container, emptycontainerModel }, + { m => m.Container = nonEmptycontainerModel, m => m.Container, nonEmptycontainerModel }, }; } } @@ -56,6 +63,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Assert Assert.Equal(typeof(Exception), metadata.ContainerType); + Assert.Null(metadata.Container); Assert.True(metadata.ConvertEmptyStringToNull); Assert.False(metadata.HasNonDefaultEditFormat);