Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/Microsoft.AspNet.Mvc.ViewFeatures/ViewDataDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we hoist this to a property on ModelMetadata?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea but suggest doing this (and a bit more) as a follow-up. Filed #2992

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.
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public void SetModelDoesNotThrowOnEnumerableModel(object model)
{
// Arrange
var vdd = new ViewDataDictionary(new EmptyModelMetadataProvider());

// Act
vdd.Model = model;

Expand Down Expand Up @@ -257,12 +257,15 @@ public static TheoryData<Type, object> 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<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>() },
Expand Down Expand Up @@ -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<bool>(viewData.Model);
Assert.True(model);
}

[Fact]
public void ModelSetter_SameType_BoxedValueTypeUpdatesModelExplorer()
{
Expand Down