From 28516a0ae9b3d3e9b908cb17f20357aff187118d Mon Sep 17 00:00:00 2001 From: sornaks Date: Tue, 29 Apr 2014 11:45:01 -0700 Subject: [PATCH] Changes to support DisplayColumn attribute. Updating the MvcSample as well to start using DisplayColumn attribute. Adding UnitTests for the same. --- samples/MvcSample.Web/Models/User.cs | 1 + .../MvcSample.Web/Views/Shared/MyView.cshtml | 2 +- ...CachedDataAnnotationsMetadataAttributes.cs | 3 + .../CachedDataAnnotationsModelMetadata.cs | 40 +++++++++++ .../Properties/Resources.Designer.cs | 38 +++++++++++ .../Resources.resx | 6 ++ .../Metadata/ModelMetadataTest.cs | 67 +++++++++++++++++++ 7 files changed, 156 insertions(+), 1 deletion(-) diff --git a/samples/MvcSample.Web/Models/User.cs b/samples/MvcSample.Web/Models/User.cs index 910d7ec676..ed0203abb5 100644 --- a/samples/MvcSample.Web/Models/User.cs +++ b/samples/MvcSample.Web/Models/User.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; namespace MvcSample.Web.Models { + [DisplayColumn("Name")] public class User { public User() diff --git a/samples/MvcSample.Web/Views/Shared/MyView.cshtml b/samples/MvcSample.Web/Views/Shared/MyView.cshtml index 18ae092b23..c8d8ebb613 100644 --- a/samples/MvcSample.Web/Views/Shared/MyView.cshtml +++ b/samples/MvcSample.Web/Views/Shared/MyView.cshtml @@ -54,7 +54,7 @@

Learn more »

-

Hello @Model.Name! Happy @Model.Age birthday.

+

Hello @Html.DisplayTextFor(User => User)! Happy @Model.Age birthday.

This value was retrieved asynchronously: @(await AsyncValueRetrieval())

Partial Async: @await Html.PartialAsync("HelloWorldPartial", Model)

diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs index 4bcd69a49d..b01fce52e1 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsMetadataAttributes.cs @@ -14,6 +14,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { Display = attributes.OfType().FirstOrDefault(); DisplayFormat = attributes.OfType().FirstOrDefault(); + DisplayColumn = attributes.OfType().FirstOrDefault(); Editable = attributes.OfType().FirstOrDefault(); Required = attributes.OfType().FirstOrDefault(); } @@ -22,6 +23,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public DisplayFormatAttribute DisplayFormat { get; protected set; } + public DisplayColumnAttribute DisplayColumn { get; protected set; } + public EditableAttribute Editable { get; protected set; } public RequiredAttribute Required { get; protected set; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs index 43d547c1a8..145813943e 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/CachedDataAnnotationsModelMetadata.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Reflection; namespace Microsoft.AspNet.Mvc.ModelBinding { @@ -63,6 +65,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return (PrototypeCache.Required != null) || base.ComputeIsRequired(); } + protected override string ComputeSimpleDisplayText() + { + if (Model != null && + PrototypeCache.DisplayColumn != null && + !string.IsNullOrEmpty(PrototypeCache.DisplayColumn.DisplayColumn)) + { + var displayColumnProperty = ModelType.GetTypeInfo().GetDeclaredProperty( + PrototypeCache.DisplayColumn.DisplayColumn); + ValidateDisplayColumnAttribute(PrototypeCache.DisplayColumn, displayColumnProperty, ModelType); + + var simpleDisplayTextValue = displayColumnProperty.GetValue(Model, null); + if (simpleDisplayTextValue != null) + { + return simpleDisplayTextValue.ToString(); + } + } + + return base.ComputeSimpleDisplayText(); + } + public override string GetDisplayName() { // DisplayAttribute doesn't require you to set a name, so this could be null. @@ -78,5 +100,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // If DisplayAttribute does not specify a name, we'll fall back to the property name. return base.GetDisplayName(); } + + private static void ValidateDisplayColumnAttribute(DisplayColumnAttribute displayColumnAttribute, + PropertyInfo displayColumnProperty, Type modelType) + { + if (displayColumnProperty == null) + { + throw new InvalidOperationException( + Resources.FormatDataAnnotationsModelMetadataProvider_UnknownProperty( + modelType.FullName, displayColumnAttribute.DisplayColumn)); + } + + if (displayColumnProperty.GetGetMethod() == null) + { + throw new InvalidOperationException( + Resources.FormatDataAnnotationsModelMetadataProvider_UnreadableProperty( + modelType.FullName, displayColumnAttribute.DisplayColumn)); + } + } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs index 73930c3294..a11ddafcda 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs @@ -378,6 +378,44 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return string.Format(CultureInfo.CurrentCulture, GetString("ValueProviderResult_NoConverterExists"), p0, p1); } + /// + /// {0} has a DisplayColumn attribute for {1}, but property {1} does not exist.. + /// + internal static string DataAnnotationsModelMetadataProvider_UnknownProperty + { + get + { + return GetString("DataAnnotationsModelMetadataProvider_UnknownProperty"); + } + } + + /// + /// {0} has a DisplayColumn attribute for {1}, but property {1} does not exist.. + /// + internal static string FormatDataAnnotationsModelMetadataProvider_UnknownProperty(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("DataAnnotationsModelMetadataProvider_UnknownProperty"), p0, p1); + } + + /// + /// {0} has a DisplayColumn attribute for {1}, but property {1} does not have a public 'get' method.. + /// + internal static string DataAnnotationsModelMetadataProvider_UnreadableProperty + { + get + { + return GetString("DataAnnotationsModelMetadataProvider_UnreadableProperty"); + } + } + + /// + /// {0} has a DisplayColumn attribute for {1}, but property {1} does not have a public 'get' method.. + /// + internal static string FormatDataAnnotationsModelMetadataProvider_UnreadableProperty(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("DataAnnotationsModelMetadataProvider_UnreadableProperty"), p0, p1); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx index 22d1017224..5bde6c1dbe 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx @@ -186,4 +186,10 @@ The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types. + + {0} has a DisplayColumn attribute for {1}, but property {1} does not exist. + + + {0} has a DisplayColumn attribute for {1}, but property {1} does not have a public 'get' method. + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs index dcdf510b11..5d51a73881 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs @@ -182,6 +182,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private class Class1 { public string Prop1 { get; set; } + public override string ToString() + { + return "Class1"; + } } private class Class2 @@ -220,6 +224,69 @@ namespace Microsoft.AspNet.Mvc.ModelBinding Assert.Equal("Object", result); } #endif + // SimpleDisplayText + + public static IEnumerable SimpleDisplayTextData + { + get + { + yield return new object[] + { + new Func(() => new ComplexClass() + { + Prop1 = new Class1 { Prop1 = "Hello" } + }), + typeof(ComplexClass), + "Class1" + }; + yield return new object[] + { + new Func(() => new Class1()), + typeof(Class1), + "Class1" + }; + yield return new object[] + { + new Func(() => new ClassWithNoProperties()), + typeof(ClassWithNoProperties), + string.Empty + }; + yield return new object[] + { + null, + typeof(object), + null + }; + } + } + + [Theory] + [MemberData("SimpleDisplayTextData")] + public void TestSimpleDisplayText(Func modelAccessor, Type modelType, string expectedResult) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var metadata = new ModelMetadata(provider, null, modelAccessor, modelType, null); + + // Act + var result = metadata.SimpleDisplayText; + + // Assert + Assert.Equal(expectedResult, result); + } + + private class ClassWithNoProperties + { + public override string ToString() + { + return null; + } + } + + private class ComplexClass + { + public Class1 Prop1 { get; set; } + } // Helpers