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

Commit

Permalink
Bug #1354 - ViewComponent View() fails on CoreCLR with IEnumerable<> …
Browse files Browse the repository at this point in the history
…passed in.

Fix - When the model is passed in to a View, ViewDataDictionary sets it. During this process, we recurse through all the properties and create FastPropertyGetters for each of them. In this case, since it is an enumerable, the properties which we recurse through are not the elements of the collection but the properties of the Enumerable instead. i.e - Enumerable.Current. Creating getters for these properties are not necessary. The fix moves the property iteration step to a place where the properties are actually requested.
  • Loading branch information
sornaks committed Dec 13, 2014
1 parent 1631ca1 commit b961aa6
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public IEnumerable<ModelMetadata> GetMetadataForProperties(object container, [No
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "propertyName");
}

var typeInfo = GetTypeInformation(containerType);
var typeInfo = GetTypeInformation(containerType, getPropertyInfo: true);

PropertyInformation propertyInfo;
if (!typeInfo.Properties.TryGetValue(propertyName, out propertyInfo))
{
Expand Down Expand Up @@ -92,7 +93,8 @@ public ModelMetadata GetMetadataForType(Func<object> modelAccessor, [NotNull] Ty

private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
{
var typeInfo = GetTypeInformation(containerType);
var typeInfo = GetTypeInformation(containerType, getPropertyInfo: true);

foreach (var kvp in typeInfo.Properties)
{
var propertyInfo = kvp.Value;
Expand Down Expand Up @@ -122,7 +124,10 @@ private TModelMetadata CreatePropertyMetadata(Func<object> modelAccessor, Proper
return metadata;
}

private TypeInformation GetTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes = null)
private TypeInformation GetTypeInformation(
Type type,
bool getPropertyInfo = false,
IEnumerable<Attribute> associatedAttributes = null)
{
// This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd
// to avoid the performance cost of creating instance delegates
Expand All @@ -132,6 +137,14 @@ private TypeInformation GetTypeInformation(Type type, IEnumerable<Attribute> ass
typeInfo = CreateTypeInformation(type, associatedAttributes);
_typeInfoCache.TryAdd(type, typeInfo);
}

if (getPropertyInfo && typeInfo.Properties == null)
{
var originalTypeInfoValue = typeInfo;
typeInfo.Properties = GetInformationOfProperties(type);
_typeInfoCache.TryUpdate(type, typeInfo, originalTypeInfoValue);
}

return typeInfo;
}

Expand All @@ -151,17 +164,6 @@ private TypeInformation CreateTypeInformation(Type type, IEnumerable<Attribute>
propertyName: null)
};

var properties = new Dictionary<string, PropertyInformation>(StringComparer.Ordinal);
foreach (var propertyHelper in PropertyHelper.GetProperties(type))
{
// Avoid re-generating a property descriptor if one has already been generated for the property name
if (!properties.ContainsKey(propertyHelper.Name))
{
properties.Add(propertyHelper.Name, CreatePropertyInformation(type, propertyHelper));
}
}

info.Properties = properties;
return info;
}

Expand All @@ -179,6 +181,22 @@ private PropertyInformation CreatePropertyInformation(Type containerType, Proper
};
}

// This is a safe race because it does not update any shared resource.
private Dictionary<string, PropertyInformation> GetInformationOfProperties(Type containerType)
{
var properties = new Dictionary<string, PropertyInformation>(StringComparer.Ordinal);
foreach (var propertyHelper in PropertyHelper.GetProperties(containerType))
{
// Avoid re-generating a property descriptor if one has already been generated for the property name
if (!properties.ContainsKey(propertyHelper.Name))
{
properties.Add(propertyHelper.Name, CreatePropertyInformation(containerType, propertyHelper));
}
}

return properties;
}

private ParameterInformation CreateParameterInfo(
Type parameterType,
IEnumerable<object> attributes,
Expand Down
14 changes: 14 additions & 0 deletions test/Microsoft.AspNet.Mvc.FunctionalTests/ViewComponentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ public async Task ViewComponents_SupportsValueType()
Assert.Equal("10", body.Trim());
}

[Fact]
public async Task ViewComponents_SupportsEnumerableModel()
{
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();

// Act
// The invoked ViewComponent has a model which is Enumerable.
var body = await client.GetStringAsync("http://localhost/Home/ViewComponentWithEnumerableModel");

// Assert
Assert.Equal("<p>Hello</p><p>World</p><p>Sample</p><p>Test</p>", body.Trim());
}

[Theory]
[InlineData("ViewComponentWebSite.Namespace1.SameName")]
[InlineData("ViewComponentWebSite.Namespace2.SameName")]
Expand Down
23 changes: 23 additions & 0 deletions test/WebSites/ViewComponentWebSite/EnumerableViewComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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 System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc;

namespace ViewComponentWebSite
{
public class EnumerableViewComponent : ViewComponent
{
public IViewComponentResult Invoke()
{
var modelList = new List<SampleModel>()
{
new SampleModel { Prop1 = "Hello", Prop2 = "World" },
new SampleModel { Prop1 = "Sample", Prop2 = "Test" },
};

return View(modelList.Select(e => e));
}
}
}
5 changes: 5 additions & 0 deletions test/WebSites/ViewComponentWebSite/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@ public ViewResult ViewWithIntegerViewComponent()
{
return new ViewResult();
}

public ViewResult ViewComponentWithEnumerableModel()
{
return new ViewResult();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@model IEnumerable<SampleModel>
@foreach (var m in Model)
{<p>@m.Prop1</p><p>@m.Prop2</p>}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@Component.Invoke("Enumerable")

0 comments on commit b961aa6

Please sign in to comment.