From 3601791820f0bbd7485e7a04f3f3f99b01f2b192 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Mon, 17 Aug 2015 15:52:33 -0700 Subject: [PATCH] Preserve `ViewDataDictionary.ModelType` for `Nullable` properties when `Model` is non-`null` - #2539 - reuse `ModelMetadata` and occasionally `ModelExplorer` when `ModelType` is `Nullable` --- .../ViewDataDictionary.cs | 12 +++++-- .../ViewDataDictionaryTest.cs | 36 +++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewDataDictionary.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewDataDictionary.cs index c746d56b61..e1346f0662 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewDataDictionary.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewDataDictionary.cs @@ -186,7 +186,9 @@ protected ViewDataDictionary([NotNull] ViewDataDictionary source, object model, { // This is the core constructor called when Model is known. var modelType = GetModelType(model); - if (modelType == source.ModelMetadata.ModelType && model == source.ModelExplorer.Model) + var metadataModelType = + Nullable.GetUnderlyingType(source.ModelMetadata.ModelType) ?? source.ModelMetadata.ModelType; + if (modelType == metadataModelType && model == source.ModelExplorer.Model) { // Preserve any customizations made to source.ModelExplorer.ModelMetadata if the Type // that will be calculated in SetModel() and source.Model match new instance's values. @@ -384,7 +386,13 @@ protected virtual void SetModel(object value) // null. When called from the Model setter, ModelMetadata will (temporarily) be null. When called from // a constructor, current ModelMetadata may already be set to preserve customizations made in parent scope. var modelType = GetModelType(value); - if (ModelExplorer?.Metadata.ModelType != modelType) + Type metadataModelType = null; + if (ModelExplorer != null) + { + metadataModelType = Nullable.GetUnderlyingType(ModelMetadata.ModelType) ?? ModelMetadata.ModelType; + } + + if (metadataModelType != modelType) { ModelExplorer = _metadataProvider.GetModelExplorerForType(modelType, value); } diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewDataDictionaryTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewDataDictionaryTest.cs index 00c0d568d1..cfb74ac26f 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewDataDictionaryTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewDataDictionaryTest.cs @@ -133,7 +133,7 @@ public void SetModelDoesNotThrowOnEnumerableModel(object model) { // Arrange var vdd = new ViewDataDictionary(new EmptyModelMetadataProvider()); - + // Act vdd.Model = model; @@ -257,12 +257,15 @@ public static TheoryData CopyModelMetadataData { get { - // Instances in this data set must have exactly the same type as the corresponding Type. Otherwise - // the copy constructor ignores the source ModelMetadata. + // Instances in this data set must have exactly the same type as the corresponding Type or be null. + // Otherwise the copy constructor ignores the source ModelMetadata. return new TheoryData { { typeof(int), 23 }, + { typeof(ulong?), 24ul }, + { typeof(ushort?), null }, { typeof(string), "hello" }, + { typeof(string), null }, { typeof(List), new List() }, { typeof(string[]), new string[0] }, { typeof(Dictionary), new Dictionary() }, @@ -359,6 +362,33 @@ public void ModelSetter_SameType_UpdatesModelExplorer() Assert.NotSame(originalExplorer, viewData.ModelExplorer); } + [Fact] + public void ModelSetter_SetNullableNonNull_UpdatesModelExplorer() + { + // Arrange + var metadataProvider = new EmptyModelMetadataProvider(); + var metadata = metadataProvider.GetMetadataForType(typeof(bool?)); + var explorer = new ModelExplorer(metadataProvider, metadata, model: null); + var viewData = new ViewDataDictionary(metadataProvider) + { + ModelExplorer = explorer, + }; + + // Act + viewData.Model = true; + + // Assert + Assert.NotNull(viewData.ModelMetadata); + Assert.NotNull(viewData.ModelExplorer); + Assert.Same(metadata, viewData.ModelMetadata); + Assert.Same(metadata.ModelType, explorer.ModelType); + Assert.NotSame(explorer, viewData.ModelExplorer); + Assert.Equal(viewData.Model, viewData.ModelExplorer.Model); + + var model = Assert.IsType(viewData.Model); + Assert.True(model); + } + [Fact] public void ModelSetter_SameType_BoxedValueTypeUpdatesModelExplorer() {