From 24e1ac7ca11ab04ddc8631eb9c16565cdff01266 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 18 Jun 2014 17:48:50 -0700 Subject: [PATCH] Revive ModelMetadata.ShowForDisplay and ModelMetadata.ShowForEdit Fixes #679 --- .../Rendering/Html/DefaultDisplayTemplates.cs | 2 +- .../Rendering/Html/DefaultEditorTemplates.cs | 2 +- ...CachedDataAnnotationsMetadataAttributes.cs | 3 + .../CachedDataAnnotationsModelMetadata.cs | 14 +++ .../Metadata/CachedModelMetadata.cs | 50 +++++++++ .../Metadata/ModelMetadata.cs | 20 ++++ .../Rendering/DefaultDisplayTemplatesTests.cs | 26 +++++ .../Rendering/DefaultEditorTemplatesTests.cs | 26 +++++ .../Rendering/DefaultTemplatesUtilities.cs | 104 ++++++++++++------ ...ataAnnotationsModelMetadataProviderTest.cs | 48 ++++++++ ...crosoft.AspNet.Mvc.ModelBinding.Test.kproj | 3 +- 11 files changed, 261 insertions(+), 37 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs index f7f17724cc..8ae11179d4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs @@ -259,8 +259,8 @@ namespace Microsoft.AspNet.Mvc.Rendering private static bool ShouldShow(ModelMetadata metadata, TemplateInfo templateInfo) { - // TODO: add ModelMetadata.ShowForDisplay and include in this calculation (first) return + metadata.ShowForDisplay && !metadata.IsComplexType && !templateInfo.Visited(metadata); } diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs index c39b2193c7..52254e2b98 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs @@ -270,8 +270,8 @@ namespace Microsoft.AspNet.Mvc.Rendering private static bool ShouldShow(ModelMetadata metadata, TemplateInfo templateInfo) { - // TODO: add ModelMetadata.ShowForEdit and include in this calculation (first) return + metadata.ShowForEdit && !metadata.IsComplexType && !templateInfo.Visited(metadata); } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs index b01fce52e1..edcbd1556e 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs @@ -17,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding DisplayColumn = attributes.OfType().FirstOrDefault(); Editable = attributes.OfType().FirstOrDefault(); Required = attributes.OfType().FirstOrDefault(); + ScaffoldColumn = attributes.OfType().FirstOrDefault(); } public DisplayAttribute Display { get; protected set; } @@ -28,5 +29,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public EditableAttribute Editable { get; protected set; } public RequiredAttribute Required { get; protected set; } + + public ScaffoldColumnAttribute ScaffoldColumn { get; protected set; } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs index 145813943e..16667c112e 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs @@ -85,6 +85,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return base.ComputeSimpleDisplayText(); } + protected override bool ComputeShowForDisplay() + { + return PrototypeCache.ScaffoldColumn != null + ? PrototypeCache.ScaffoldColumn.Scaffold + : base.ComputeShowForDisplay(); + } + + protected override bool ComputeShowForEdit() + { + return PrototypeCache.ScaffoldColumn != null + ? PrototypeCache.ScaffoldColumn.Scaffold + : base.ComputeShowForEdit(); + } + public override string GetDisplayName() { // DisplayAttribute doesn't require you to set a name, so this could be null. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs index 49ade31a91..4b0aea6c82 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedModelMetadata.cs @@ -21,6 +21,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private bool _isReadOnly; private bool _isComplexType; private bool _isRequired; + private bool _showForDisplay; + private bool _showForEdit; private bool _convertEmptyStringToNullComputed; private bool _nullDisplayTextComputed; @@ -28,6 +30,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private bool _isReadOnlyComputed; private bool _isComplexTypeComputed; private bool _isRequiredComputed; + private bool _showForDisplayComputed; + private bool _showForEditComputed; // Constructor for creating real instances of the metadata class based on a prototype protected CachedModelMetadata(CachedModelMetadata prototype, Func modelAccessor) @@ -158,6 +162,42 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + public sealed override bool ShowForDisplay + { + get + { + if (!_showForDisplayComputed) + { + _showForDisplay = ComputeShowForDisplay(); + _showForDisplayComputed = true; + } + return _showForDisplay; + } + set + { + _showForDisplay = value; + _showForDisplayComputed = true; + } + } + + public sealed override bool ShowForEdit + { + get + { + if (!_showForEditComputed) + { + _showForEdit = ComputeShowForEdit(); + _showForEditComputed = true; + } + return _showForEdit; + } + set + { + _showForEdit = value; + _showForEditComputed = true; + } + } + protected TPrototypeCache PrototypeCache { get; set; } protected virtual bool ComputeConvertEmptyStringToNull() @@ -189,5 +229,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { return base.IsComplexType; } + + protected virtual bool ComputeShowForDisplay() + { + return base.ShowForDisplay; + } + + protected virtual bool ComputeShowForEdit() + { + return base.ShowForEdit; + } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs index ef288c6a43..c462570239 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs @@ -25,6 +25,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private IEnumerable _properties; private Type _realModelType; private string _simpleDisplayText; + private bool _showForDisplay = true; + private bool _showForEdit = true; public ModelMetadata([NotNull] IModelMetadataProvider provider, Type containerType, @@ -164,6 +166,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding set { _simpleDisplayText = value; } } + /// + /// Gets or sets a value that indicates whether the property should be displayed in read-only views. + /// + public virtual bool ShowForDisplay + { + get { return _showForDisplay; } + set { _showForDisplay = value; } + } + + /// + /// Gets or sets a value that indicates whether the property should be displayed in editable views. + /// + public virtual bool ShowForEdit + { + get { return _showForEdit; } + set { _showForEdit = value; } + } + public virtual string TemplateHint { get; set; } internal EfficientTypePropertyKey CacheKey diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTests.cs index 912a9c993c..8f1e05dd12 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTests.cs @@ -2,8 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; +using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.Core.Test @@ -70,5 +73,28 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Assert Assert.Equal(metadata.SimpleDisplayText, result); } + + [Fact] + public void ObjectTemplate_IgnoresPropertiesWith_ScaffoldColumnFalse() + { + // Arrange + var expected = +@"
Property1
+
+
Property3
+
+"; + var model = new DefaultTemplatesUtilities.ObjectWithScaffoldColumn(); + var viewEngine = new Mock(); + viewEngine.Setup(v => v.FindPartialView(It.IsAny>(), It.IsAny())) + .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + + // Act + var result = DefaultDisplayTemplates.ObjectTemplate(htmlHelper); + + // Assert + Assert.Equal(expected, result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTests.cs index c5eee798a2..737e3af01b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTests.cs @@ -2,8 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; +using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.Core.Test @@ -74,5 +77,28 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Assert Assert.Equal(metadata.SimpleDisplayText, result); } + + [Fact] + public void ObjectTemplate_IgnoresPropertiesWith_ScaffoldColumnFalse() + { + // Arrange + var expected = +@"
+
+
+
+"; + var model = new DefaultTemplatesUtilities.ObjectWithScaffoldColumn(); + var viewEngine = new Mock(); + viewEngine.Setup(v => v.FindPartialView(It.IsAny>(), It.IsAny())) + .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + + // Act + var result = DefaultEditorTemplates.ObjectTemplate(htmlHelper); + + // Assert + Assert.Equal(expected, result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs index b48e7d3630..b99f42353a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; @@ -30,7 +32,19 @@ namespace Microsoft.AspNet.Mvc.Core.Test public object ComplexInnerModel { get; set; } } - public static HtmlHelper GetHtmlHelper(object model) + public class ObjectWithScaffoldColumn + { + public string Property1 { get; set; } + + [ScaffoldColumn(false)] + public string Property2 { get; set; } + + [ScaffoldColumn(true)] + public string Property3 { get; set; } + } + + public static HtmlHelper GetHtmlHelper(object model, + IViewEngine viewEngine = null) { var provider = new DataAnnotationsModelMetadataProvider(); var viewData = new ViewDataDictionary(provider); @@ -46,6 +60,59 @@ namespace Microsoft.AspNet.Mvc.Core.Test .Setup(o => o.Items) .Returns(new Dictionary()); + viewEngine = viewEngine ?? CreateViewEngine(); + + var actionContext = new ActionContext(httpContext.Object, + new RouteData(), + new ActionDescriptor()); + + var actionBindingContext = new ActionBindingContext(actionContext, + provider, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Enumerable.Empty()); + var urlHelper = Mock.Of(); + var actionBindingContextProvider = new Mock(); + actionBindingContextProvider + .Setup(c => c.GetActionBindingContextAsync(It.IsAny())) + .Returns(Task.FromResult(actionBindingContext)); + + var serviceProvider = new Mock(); + serviceProvider + .Setup(s => s.GetService(typeof(IViewEngine))) + .Returns(viewEngine); + serviceProvider + .Setup(s => s.GetService(typeof(IUrlHelper))) + .Returns(urlHelper); + serviceProvider + .Setup(s => s.GetService(typeof(IViewComponentHelper))) + .Returns(new Mock().Object); + + httpContext + .Setup(o => o.RequestServices) + .Returns(serviceProvider.Object); + + var viewContext = new ViewContext(actionContext, Mock.Of(), viewData, new StringWriter()); + + var htmlHelper = new HtmlHelper( + viewEngine, + provider, + urlHelper, + GetAntiForgeryInstance(), + actionBindingContextProvider.Object); + htmlHelper.Contextualize(viewContext); + + serviceProvider + .Setup(s => s.GetService(typeof(IHtmlHelper))) + .Returns(htmlHelper); + + + return htmlHelper; + } + + private static IViewEngine CreateViewEngine() + { var view = new Mock(); view .Setup(v => v.RenderAsync(It.IsAny())) @@ -55,42 +122,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test }) .Returns(Task.FromResult(0)); - var routeDictionary = new Dictionary(); var viewEngine = new Mock(); viewEngine - .Setup(v => v.FindPartialView(routeDictionary, It.IsAny())) + .Setup(v => v.FindPartialView(It.IsAny>(), It.IsAny())) .Returns(ViewEngineResult.Found("MyView", view.Object)); - - var serviceProvider = new Mock(); - serviceProvider - .Setup(s => s.GetService(typeof(IViewEngine))) - .Returns(viewEngine.Object); - serviceProvider - .Setup(s => s.GetService(typeof(IUrlHelper))) - .Returns(new Mock().Object); - serviceProvider - .Setup(s => s.GetService(typeof(IViewComponentHelper))) - .Returns(new Mock().Object); - - httpContext - .Setup(o => o.RequestServices) - .Returns(serviceProvider.Object); - - var actionContext = new ActionContext(httpContext.Object, - new RouteData() { Values = routeDictionary }, - new ActionDescriptor()); - - var viewContext = new ViewContext(actionContext, view.Object, viewData, new StringWriter()); - - var htmlHelper = new HtmlHelper( - viewEngine.Object, - provider, - new Mock().Object, - GetAntiForgeryInstance(), - new Mock().Object); - htmlHelper.Contextualize(viewContext); - - return htmlHelper; + return viewEngine.Object; } private static AntiForgery GetAntiForgeryInstance() diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs new file mode 100644 index 0000000000..e6ae3f5b07 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs @@ -0,0 +1,48 @@ +// 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.ComponentModel.DataAnnotations; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class CachedDataAnnotationsModelMetadataProviderTest + { + [Fact] + public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForDisplay() + { + // Arrange + var type = typeof(ScaffoldColumnModel); + var provider = new DataAnnotationsModelMetadataProvider(); + + // Act & Assert + Assert.True(provider.GetMetadataForProperty(null, type, "NoAttribute").ShowForDisplay); + Assert.True(provider.GetMetadataForProperty(null, type, "ScaffoldColumnTrue").ShowForDisplay); + Assert.False(provider.GetMetadataForProperty(null, type, "ScaffoldColumnFalse").ShowForDisplay); + } + + [Fact] + public void DataAnnotationsModelMetadataProvider_ReadsScaffoldColumnAttribute_ForShowForEdit() + { + // Arrange + var type = typeof(ScaffoldColumnModel); + var provider = new DataAnnotationsModelMetadataProvider(); + + // Act & Assert + Assert.True(provider.GetMetadataForProperty(null, type, "NoAttribute").ShowForEdit); + Assert.True(provider.GetMetadataForProperty(null, type, "ScaffoldColumnTrue").ShowForEdit); + Assert.False(provider.GetMetadataForProperty(null, type, "ScaffoldColumnFalse").ShowForEdit); + } + + private class ScaffoldColumnModel + { + public int NoAttribute { get; set; } + + [ScaffoldColumn(scaffold: true)] + public int ScaffoldColumnTrue { get; set; } + + [ScaffoldColumn(scaffold: false)] + public int ScaffoldColumnFalse { get; set; } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Microsoft.AspNet.Mvc.ModelBinding.Test.kproj b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Microsoft.AspNet.Mvc.ModelBinding.Test.kproj index 64abe5fa2c..c40daad21f 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Microsoft.AspNet.Mvc.ModelBinding.Test.kproj +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Microsoft.AspNet.Mvc.ModelBinding.Test.kproj @@ -35,6 +35,7 @@ + @@ -60,4 +61,4 @@ - + \ No newline at end of file