diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs index 47080996bf..5218ef5e71 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// A metadata representation of a model type, property or parameter. /// [DebuggerDisplay("{DebuggerToString(),nq}")] - public abstract class ModelMetadata : IEquatable + public abstract class ModelMetadata : IEquatable, IModelMetadataProvider { /// /// The default value of . @@ -42,6 +42,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// public Type ContainerType { get { return Identity.ContainerType; } } + /// + /// Gets the metadata of the container type that the current instance is part of. + /// + public virtual ModelMetadata ContainerMetadata + { + get + { + throw new NotImplementedException(); + } + } + /// /// Gets a value indicating the kind of metadata element represented by the current instance. /// @@ -474,5 +485,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding return $"ModelMetadata (Property: '{ContainerType.Name}.{PropertyName}' Type: '{ModelType.Name}')"; } } + + /// + public virtual ModelMetadata GetMetadataForType(Type modelType) + { + throw new NotImplementedException(); + } + + /// + public virtual IEnumerable GetMetadataForProperties(Type modelType) + { + throw new NotImplementedException(); + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs index 626d253196..cc9b540c5a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs @@ -68,5 +68,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// Gets or sets the /// public ValidationMetadata ValidationMetadata { get; set; } + + /// + /// Gets or sets the of the container type. + /// + public ModelMetadata ContainerMetadata { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs index f9ef945887..f2d7adef2f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -96,6 +96,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata } } + /// + public override ModelMetadata ContainerMetadata + { + get + { + return _details.ContainerMetadata; + } + } + /// /// Gets the for the current instance. /// @@ -597,5 +606,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata return _details.PropertySetter; } } + + /// + public override ModelMetadata GetMetadataForType(Type modelType) + { + return _provider.GetMetadataForType(modelType); + } + + /// + public override IEnumerable GetMetadataForProperties(Type modelType) + { + return _provider.GetMetadataForProperties(modelType); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs index 9353a04a1f..fc43e72ebf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs @@ -87,6 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata var properties = new ModelMetadata[propertyDetails.Length]; for (var i = 0; i < properties.Length; i++) { + propertyDetails[i].ContainerMetadata = cacheEntry.Metadata; properties[i] = CreateModelMetadata(propertyDetails[i]); } diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs index 1f93ca93f0..bb13997b16 100644 --- a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs @@ -302,6 +302,36 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Assert.Equal("String", result); } + [Fact] + public void GetContainerMetadata_ThrowsNotImplementedException_ByDefault() + { + // Arrange + var metadata = new TestModelMetadata(typeof(DerivedList)); + + // Act & Assert + Assert.Throws(() => metadata.ContainerMetadata); + } + + [Fact] + public void GetMetadataForType_ByDefaultThrows_NotImplementedException() + { + // Arrange + var metadata = new TestModelMetadata(typeof(string)); + + // Act & Assert + var result = Assert.Throws(() => metadata.GetMetadataForType(typeof(string))); + } + + [Fact] + public void GetMetadataForProperties_ByDefaultThrows_NotImplementedException() + { + // Arrange + var metadata = new TestModelMetadata(typeof(string)); + + // Act & Assert + var result = Assert.Throws(() => metadata.GetMetadataForProperties(typeof(string))); + } + private class TestModelMetadata : ModelMetadata { private string _displayName; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs index 700be54129..286b589487 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs @@ -57,6 +57,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata Assert.Same(metadata1, metadata2); } + [Fact] + public void GetMetadataForProperties_IncludesContainerMetadataForAllProperties() + { + // Arrange + var provider = CreateProvider(); + var modelType = typeof(ModelType); + + // Act + var metadata = provider.GetMetadataForProperties(modelType).ToArray(); + + // Assert + Assert.Collection( + metadata, + (propertyMetadata) => + { + Assert.Equal("Property1", propertyMetadata.PropertyName); + Assert.NotNull(propertyMetadata.ContainerMetadata); + Assert.Equal(modelType, propertyMetadata.ContainerMetadata.ModelType); + }, + (propertyMetadata) => + { + Assert.Equal("Property2", propertyMetadata.PropertyName); + Assert.NotNull(propertyMetadata.ContainerMetadata); + Assert.Equal(modelType, propertyMetadata.ContainerMetadata.ModelType); + }); + } + [Fact] public void GetMetadataForProperties_IncludesAllProperties() { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs index 62a0bfbcca..88892e3d26 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs @@ -744,6 +744,46 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata Assert.False(validateChildren); } + [Fact] + public void GetMetadataForType_CallsProvider() + { + // Arrange + var detailsProvider = new Mock(); + var key = ModelMetadataIdentity.ForType(typeof(string)); + var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0])); + var metadataProvider = new Mock(); + metadataProvider + .Setup(mp => mp.GetMetadataForType(typeof(string))) + .Verifiable(); + var metadata1 = new DefaultModelMetadata(metadataProvider.Object, detailsProvider.Object, cache); + + // Act + var metadata2 = metadata1.GetMetadataForType(typeof(string)); + + // Assert + metadataProvider.VerifyAll(); + } + + [Fact] + public void GetMetadataForProperties_CallsProvider() + { + // Arrange + var detailsProvider = new Mock(); + var key = ModelMetadataIdentity.ForType(typeof(string)); + var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0])); + var metadataProvider = new Mock(); + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(typeof(Exception))) + .Verifiable(); + var metadata1 = new DefaultModelMetadata(metadataProvider.Object, detailsProvider.Object, cache); + + // Act + var metadata2 = metadata1.GetMetadataForProperties(typeof(Exception)); + + // Assert + metadataProvider.VerifyAll(); + } + private void ActionMethod(string input) { }