Preserve `ViewDataDictionary.ModelType` for `Nullable<T>` properties when `Model` is non-`null`

- #2539
- reuse `ModelMetadata` and occasionally `ModelExplorer` when `ModelType` is `Nullable<T>`
This commit is contained in:
Doug Bunting 2015-08-17 15:52:33 -07:00
parent c2347e4d4b
commit f10a071da3
2 changed files with 43 additions and 5 deletions

View File

@ -186,7 +186,9 @@ namespace Microsoft.AspNet.Mvc
{
// 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 @@ namespace Microsoft.AspNet.Mvc
// 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);
}

View File

@ -133,7 +133,7 @@ namespace Microsoft.AspNet.Mvc.Core
{
// Arrange
var vdd = new ViewDataDictionary(new EmptyModelMetadataProvider());
// Act
vdd.Model = model;
@ -257,12 +257,15 @@ namespace Microsoft.AspNet.Mvc.Core
{
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<Type, object>
{
{ typeof(int), 23 },
{ typeof(ulong?), 24ul },
{ typeof(ushort?), null },
{ typeof(string), "hello" },
{ typeof(string), null },
{ typeof(List<string>), new List<string>() },
{ typeof(string[]), new string[0] },
{ typeof(Dictionary<string, object>), new Dictionary<string, object>() },
@ -359,6 +362,33 @@ namespace Microsoft.AspNet.Mvc.Core
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<bool>(viewData.Model);
Assert.True(model);
}
[Fact]
public void ModelSetter_SameType_BoxedValueTypeUpdatesModelExplorer()
{