Bug #1354 - ViewComponent View() fails on CoreCLR with IEnumerable<> 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. - Splitting TypeInformation class into two and separating their caches appropriately.
This commit is contained in:
parent
2df482aa5c
commit
1570e198ef
|
|
@ -12,8 +12,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public abstract class AssociatedMetadataProvider<TModelMetadata> : IModelMetadataProvider
|
public abstract class AssociatedMetadataProvider<TModelMetadata> : IModelMetadataProvider
|
||||||
where TModelMetadata : ModelMetadata
|
where TModelMetadata : ModelMetadata
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<Type, TypeInformation> _typeInfoCache =
|
private readonly ConcurrentDictionary<Type, TModelMetadata> _typeInfoCache =
|
||||||
new ConcurrentDictionary<Type, TypeInformation>();
|
new ConcurrentDictionary<Type, TModelMetadata>();
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<Type, Dictionary<string, PropertyInformation>> _typePropertyInfoCache =
|
||||||
|
new ConcurrentDictionary<Type, Dictionary<string, PropertyInformation>>();
|
||||||
|
|
||||||
public IEnumerable<ModelMetadata> GetMetadataForProperties(object container, [NotNull] Type containerType)
|
public IEnumerable<ModelMetadata> GetMetadataForProperties(object container, [NotNull] Type containerType)
|
||||||
{
|
{
|
||||||
|
|
@ -26,15 +29,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(propertyName))
|
if (string.IsNullOrEmpty(propertyName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "propertyName");
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeInfo = GetTypeInformation(containerType);
|
var typePropertyInfo = GetTypePropertyInformation(containerType);
|
||||||
|
|
||||||
PropertyInformation propertyInfo;
|
PropertyInformation propertyInfo;
|
||||||
if (!typeInfo.Properties.TryGetValue(propertyName, out propertyInfo))
|
if (!typePropertyInfo.TryGetValue(propertyName, out propertyInfo))
|
||||||
{
|
{
|
||||||
var message = Resources.FormatCommon_PropertyNotFound(containerType, propertyName);
|
var message = Resources.FormatCommon_PropertyNotFound(containerType, propertyName);
|
||||||
throw new ArgumentException(message, "propertyName");
|
throw new ArgumentException(message, nameof(propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreatePropertyMetadata(modelAccessor, propertyInfo);
|
return CreatePropertyMetadata(modelAccessor, propertyInfo);
|
||||||
|
|
@ -42,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
public ModelMetadata GetMetadataForType(Func<object> modelAccessor, [NotNull] Type modelType)
|
public ModelMetadata GetMetadataForType(Func<object> modelAccessor, [NotNull] Type modelType)
|
||||||
{
|
{
|
||||||
var prototype = GetTypeInformation(modelType).Prototype;
|
var prototype = GetTypeInformation(modelType);
|
||||||
return CreateMetadataFromPrototype(prototype, modelAccessor);
|
return CreateMetadataFromPrototype(prototype, modelAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(parameterName))
|
if (string.IsNullOrEmpty(parameterName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "parameterName");
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(parameterName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var parameter = methodInfo.GetParameters().FirstOrDefault(
|
var parameter = methodInfo.GetParameters().FirstOrDefault(
|
||||||
|
|
@ -92,8 +96,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
|
private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
|
||||||
{
|
{
|
||||||
var typeInfo = GetTypeInformation(containerType);
|
var typePropertyInfo = GetTypePropertyInformation(containerType);
|
||||||
foreach (var kvp in typeInfo.Properties)
|
|
||||||
|
foreach (var kvp in typePropertyInfo)
|
||||||
{
|
{
|
||||||
var propertyInfo = kvp.Value;
|
var propertyInfo = kvp.Value;
|
||||||
Func<object> modelAccessor = null;
|
Func<object> modelAccessor = null;
|
||||||
|
|
@ -122,47 +127,43 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeInformation GetTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes = null)
|
private TModelMetadata GetTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes = null)
|
||||||
{
|
{
|
||||||
// This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd
|
// This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd
|
||||||
// to avoid the performance cost of creating instance delegates
|
// to avoid the performance cost of creating instance delegates
|
||||||
TypeInformation typeInfo;
|
TModelMetadata typeInfo;
|
||||||
if (!_typeInfoCache.TryGetValue(type, out typeInfo))
|
if (!_typeInfoCache.TryGetValue(type, out typeInfo))
|
||||||
{
|
{
|
||||||
typeInfo = CreateTypeInformation(type, associatedAttributes);
|
typeInfo = CreateTypeInformation(type, associatedAttributes);
|
||||||
_typeInfoCache.TryAdd(type, typeInfo);
|
_typeInfoCache.TryAdd(type, typeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeInfo;
|
return typeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeInformation CreateTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes)
|
private Dictionary<string, PropertyInformation> GetTypePropertyInformation(Type type)
|
||||||
{
|
{
|
||||||
var typeInfo = type.GetTypeInfo();
|
// This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd
|
||||||
var attributes = typeInfo.GetCustomAttributes();
|
// to avoid the performance cost of creating instance delegates
|
||||||
|
Dictionary<string, PropertyInformation> typePropertyInfo;
|
||||||
|
if (!_typePropertyInfoCache.TryGetValue(type, out typePropertyInfo))
|
||||||
|
{
|
||||||
|
typePropertyInfo = GetPropertiesLookup(type);
|
||||||
|
_typePropertyInfoCache.TryAdd(type, typePropertyInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return typePropertyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TModelMetadata CreateTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes)
|
||||||
|
{
|
||||||
|
var attributes = type.GetTypeInfo().GetCustomAttributes();
|
||||||
if (associatedAttributes != null)
|
if (associatedAttributes != null)
|
||||||
{
|
{
|
||||||
attributes = attributes.Concat(associatedAttributes);
|
attributes = attributes.Concat(associatedAttributes);
|
||||||
}
|
}
|
||||||
var info = new TypeInformation
|
|
||||||
{
|
|
||||||
Prototype = CreateMetadataPrototype(attributes,
|
|
||||||
containerType: null,
|
|
||||||
modelType: type,
|
|
||||||
propertyName: null)
|
|
||||||
};
|
|
||||||
|
|
||||||
var properties = new Dictionary<string, PropertyInformation>(StringComparer.Ordinal);
|
return CreateMetadataPrototype(attributes, containerType: null, modelType: type, propertyName: null);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyHelper helper)
|
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyHelper helper)
|
||||||
|
|
@ -179,6 +180,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, PropertyInformation> GetPropertiesLookup(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(
|
private ParameterInformation CreateParameterInfo(
|
||||||
Type parameterType,
|
Type parameterType,
|
||||||
IEnumerable<object> attributes,
|
IEnumerable<object> attributes,
|
||||||
|
|
@ -200,12 +216,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public TModelMetadata Prototype { get; set; }
|
public TModelMetadata Prototype { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class TypeInformation
|
|
||||||
{
|
|
||||||
public TModelMetadata Prototype { get; set; }
|
|
||||||
public Dictionary<string, PropertyInformation> Properties { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class PropertyInformation
|
private sealed class PropertyInformation
|
||||||
{
|
{
|
||||||
public PropertyHelper PropertyHelper { get; set; }
|
public PropertyHelper PropertyHelper { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Testing;
|
using Microsoft.AspNet.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -73,6 +74,73 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
metadataProvider.Verify();
|
metadataProvider.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When SetModel is called, only GetMetadataForType from MetadataProvider is expected to be called.
|
||||||
|
[Fact]
|
||||||
|
public void SetModelCallsGetMetadataForTypeExactlyOnce()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
|
||||||
|
metadataProvider
|
||||||
|
.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(object)))
|
||||||
|
.Returns(new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)))
|
||||||
|
.Verifiable();
|
||||||
|
metadataProvider
|
||||||
|
.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(TestModel)))
|
||||||
|
.Returns(new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(TestModel)))
|
||||||
|
.Verifiable();
|
||||||
|
var modelState = new ModelStateDictionary();
|
||||||
|
var viewData = new TestViewDataDictionary(metadataProvider.Object, modelState);
|
||||||
|
var model = new TestModel();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
viewData.SetModelPublic(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(viewData.ModelMetadata);
|
||||||
|
// Verifies if the GetMetadataForType is called only once.
|
||||||
|
metadataProvider.Verify(
|
||||||
|
m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(object)), Times.Once());
|
||||||
|
// Verifies if GetMetadataForProperties and GetMetadataForProperty is not called.
|
||||||
|
metadataProvider.Verify(
|
||||||
|
m => m.GetMetadataForProperties(It.IsAny<Func<object>>(), typeof(object)), Times.Never());
|
||||||
|
metadataProvider.Verify(
|
||||||
|
m => m.GetMetadataForProperty(
|
||||||
|
It.IsAny<Func<object>>(), typeof(object), It.IsAny<string>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<object> SetModelData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var model = new List<TestModel>()
|
||||||
|
{
|
||||||
|
new TestModel(),
|
||||||
|
new TestModel()
|
||||||
|
};
|
||||||
|
|
||||||
|
return new TheoryData<object>
|
||||||
|
{
|
||||||
|
{ model.Select(t => t) },
|
||||||
|
{ model.Where(t => t != null) },
|
||||||
|
{ model.SelectMany(t => t.ToString()) },
|
||||||
|
{ model.Take(2) },
|
||||||
|
{ model.TakeWhile(t => t != null) },
|
||||||
|
{ model.Union(model) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(SetModelData))]
|
||||||
|
public void SetModelDoesNotThrowOnEnumerableModel(object model)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var vdd = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.DoesNotThrow(() => { vdd.Model = model; });
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CopyConstructorInitalizesModelAndModelMetadataBasedOnSource()
|
public void CopyConstructorInitalizesModelAndModelMetadataBasedOnSource()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,29 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal("10", body.Trim());
|
Assert.Equal("10", body.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("http://localhost/Home/ViewComponentWithEnumerableModelUsingWhere", "Where")]
|
||||||
|
[InlineData("http://localhost/Home/ViewComponentWithEnumerableModelUsingSelect", "Select")]
|
||||||
|
[InlineData("http://localhost/Home/ViewComponentWithEnumerableModelUsingSelectMany", "SelectMany")]
|
||||||
|
[InlineData("http://localhost/Home/ViewComponentWithEnumerableModelUsingTake", "Take")]
|
||||||
|
[InlineData("http://localhost/Home/ViewComponentWithEnumerableModelUsingTakeWhile", "TakeWhile")]
|
||||||
|
[InlineData("http://localhost/Home/ViewComponentWithEnumerableModelUsingUnion", "Union")]
|
||||||
|
public async Task ViewComponents_SupportsEnumerableModel(string url, string linqQueryType)
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// https://github.com/aspnet/Mvc/issues/1354
|
||||||
|
// The invoked ViewComponent/View has a model which is an internal type implementing Enumerable.
|
||||||
|
// For ex - TestEnumerableObject.Select(t => t) returns WhereSelectListIterator
|
||||||
|
var body = await client.GetStringAsync(url);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("<p>Hello</p><p>World</p><p>Sample</p><p>Test</p>"
|
||||||
|
+ "<p>Hello</p><p>World</p><p>" + linqQueryType + "</p><p>Test</p>", body.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("ViewComponentWebSite.Namespace1.SameName")]
|
[InlineData("ViewComponentWebSite.Namespace1.SameName")]
|
||||||
[InlineData("ViewComponentWebSite.Namespace2.SameName")]
|
[InlineData("ViewComponentWebSite.Namespace2.SameName")]
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,22 @@ ViewWithNestedLayout-Content
|
||||||
Assert.Equal(expected, body.Trim());
|
Assert.Equal(expected, body.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RazorView_DoesNotThrow_PartialViewWithEnumerableModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "HelloWorld";
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var body = await client.GetStringAsync(
|
||||||
|
"http://localhost/ViewEngine/ViewWithPartialTakingModelFromIEnumerable");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, body.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RazorView_PassesViewContextBetweenViewAndLayout()
|
public async Task RazorView_PassesViewContextBetweenViewAndLayout()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
namespace RazorWebSite.Controllers
|
namespace RazorWebSite.Controllers
|
||||||
|
|
@ -37,6 +38,17 @@ namespace RazorWebSite.Controllers
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IActionResult ViewWithPartialTakingModelFromIEnumerable()
|
||||||
|
{
|
||||||
|
var model = new List<Person>()
|
||||||
|
{
|
||||||
|
new Person() { Name = "Hello" },
|
||||||
|
new Person() { Name = "World" }
|
||||||
|
};
|
||||||
|
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
|
||||||
public ViewResult ViewPassesViewDataToLayout()
|
public ViewResult ViewPassesViewDataToLayout()
|
||||||
{
|
{
|
||||||
ViewData["Title"] = "Controller title";
|
ViewData["Title"] = "Controller title";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@model Person
|
||||||
|
@Html.DisplayFor(m => m.Name)
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@using RazorWebSite
|
||||||
|
@model IEnumerable<Person>
|
||||||
|
@foreach (var item in Model)
|
||||||
|
{@await Html.PartialAsync("_PartialWithModelFromEnumerable", item)}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// 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(string linqQueryType)
|
||||||
|
{
|
||||||
|
var modelList = new List<SampleModel>()
|
||||||
|
{
|
||||||
|
new SampleModel { Prop1 = "Hello", Prop2 = "World" },
|
||||||
|
new SampleModel { Prop1 = linqQueryType, Prop2 = "Test" },
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (linqQueryType) {
|
||||||
|
case "Where":
|
||||||
|
return View(modelList.Where(e => e != null));
|
||||||
|
|
||||||
|
case "Take":
|
||||||
|
return View(modelList.Take(2));
|
||||||
|
|
||||||
|
case "TakeWhile":
|
||||||
|
return View(modelList.TakeWhile(a => a != null));
|
||||||
|
|
||||||
|
case "Union":
|
||||||
|
return View(modelList.Union(modelList));
|
||||||
|
|
||||||
|
case "SelectMany":
|
||||||
|
var selectManySampleModelList = new List<SelectManySampleModel>
|
||||||
|
{
|
||||||
|
new SelectManySampleModel {
|
||||||
|
TestModel =
|
||||||
|
new List<SampleModel> { new SampleModel { Prop1 = "Hello", Prop2 = "World" } } },
|
||||||
|
new SelectManySampleModel {
|
||||||
|
TestModel =
|
||||||
|
new List<SampleModel> { new SampleModel{ Prop1 = linqQueryType, Prop2 = "Test" } } }
|
||||||
|
};
|
||||||
|
|
||||||
|
return View(selectManySampleModelList.SelectMany(a => a.TestModel));
|
||||||
|
};
|
||||||
|
|
||||||
|
return View(modelList.Select(e => e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// 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;
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
namespace ViewComponentWebSite
|
namespace ViewComponentWebSite
|
||||||
{
|
{
|
||||||
public class HomeController
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
|
private IEnumerable<SampleModel> ModelList { get; set; }
|
||||||
|
|
||||||
|
public HomeController()
|
||||||
|
{
|
||||||
|
ModelList = new List<SampleModel>()
|
||||||
|
{
|
||||||
|
new SampleModel { Prop1 = "Hello", Prop2 = "World" },
|
||||||
|
new SampleModel { Prop1 = "Sample", Prop2 = "Test" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public ViewResult ViewWithAsyncComponents()
|
public ViewResult ViewWithAsyncComponents()
|
||||||
{
|
{
|
||||||
return new ViewResult();
|
return new ViewResult();
|
||||||
|
|
@ -21,5 +34,51 @@ namespace ViewComponentWebSite
|
||||||
{
|
{
|
||||||
return new ViewResult();
|
return new ViewResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ViewResult ViewComponentWithEnumerableModelUsingWhere()
|
||||||
|
{
|
||||||
|
ViewBag.LinqQueryType = "Where";
|
||||||
|
return View("ViewComponentWithEnumerableModel", ModelList.Where(a => a != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewResult ViewComponentWithEnumerableModelUsingSelect()
|
||||||
|
{
|
||||||
|
ViewBag.LinqQueryType = "Select";
|
||||||
|
return View("ViewComponentWithEnumerableModel", ModelList.Select(a => a));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewResult ViewComponentWithEnumerableModelUsingTake()
|
||||||
|
{
|
||||||
|
ViewBag.LinqQueryType = "Take";
|
||||||
|
return View("ViewComponentWithEnumerableModel", ModelList.Take(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewResult ViewComponentWithEnumerableModelUsingTakeWhile()
|
||||||
|
{
|
||||||
|
ViewBag.LinqQueryType = "TakeWhile";
|
||||||
|
return View("ViewComponentWithEnumerableModel", ModelList.TakeWhile(a => a != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewResult ViewComponentWithEnumerableModelUsingUnion()
|
||||||
|
{
|
||||||
|
ViewBag.LinqQueryType = "Union";
|
||||||
|
return View("ViewComponentWithEnumerableModel", ModelList.Union(ModelList));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewResult ViewComponentWithEnumerableModelUsingSelectMany()
|
||||||
|
{
|
||||||
|
var selectManySampleModelList = new List<SelectManySampleModel>
|
||||||
|
{
|
||||||
|
new SelectManySampleModel {
|
||||||
|
TestModel =
|
||||||
|
new List<SampleModel> { ModelList.ElementAt(0) } },
|
||||||
|
new SelectManySampleModel {
|
||||||
|
TestModel =
|
||||||
|
new List<SampleModel> { ModelList.ElementAt(1) } }
|
||||||
|
};
|
||||||
|
|
||||||
|
ViewBag.LinqQueryType = "SelectMany";
|
||||||
|
return View("ViewComponentWithEnumerableModel", selectManySampleModelList.SelectMany(s => s.TestModel));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace ViewComponentWebSite
|
||||||
|
{
|
||||||
|
public class SelectManySampleModel
|
||||||
|
{
|
||||||
|
public List<SampleModel> TestModel { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
@model IEnumerable<SampleModel>
|
||||||
|
@foreach (var m in Model)
|
||||||
|
{<p>@m.Prop1</p><p>@m.Prop2</p>}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
@model IEnumerable<SampleModel>
|
||||||
|
@foreach (var m in Model)
|
||||||
|
{<p>@m.Prop1</p><p>@m.Prop2</p>}
|
||||||
|
@Component.Invoke("Enumerable", ViewBag.LinqQueryType)
|
||||||
Loading…
Reference in New Issue