Add `ModelMetadata.HtmlEncode` property

- use new `ModelMetadata.HtmlEncode` property in HTML helpers
 - specifically in default HTML display and editor object templates (e.g.
   `@Html.DisplayFor()`) when value is non-`null` and the template is invoked
   with template depth greater than 1
- similar to MVC 5.2 commit [2b12791aee4f](https://aspnetwebstack.codeplex.com/SourceControl/changeset/2b12791aee4ffc56c7928b623bb45ee425813021)

nits:
- remove dupe `null` check in `DefaultDisplayTemplates.ObjectTemplate()`
- move backing fields initialized with constants together in `ModelMetadata`
This commit is contained in:
Doug Bunting 2014-10-31 22:58:18 -07:00
parent d5515bfbb6
commit 90e41b905a
9 changed files with 144 additions and 17 deletions

View File

@ -213,7 +213,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
if (templateInfo.TemplateDepth > 1)
{
return modelMetadata.Model == null ? modelMetadata.NullDisplayText : modelMetadata.SimpleDisplayText;
var text = modelMetadata.SimpleDisplayText;
if (modelMetadata.HtmlEncode)
{
text = html.Encode(text);
}
return text;
}
var serviceProvider = html.ViewContext.HttpContext.RequestServices;

View File

@ -232,7 +232,18 @@ namespace Microsoft.AspNet.Mvc.Rendering
if (templateInfo.TemplateDepth > 1)
{
return modelMetadata.Model == null ? modelMetadata.NullDisplayText : modelMetadata.SimpleDisplayText;
if (modelMetadata.Model == null)
{
return modelMetadata.NullDisplayText;
}
var text = modelMetadata.SimpleDisplayText;
if (modelMetadata.HtmlEncode)
{
text = html.Encode(text);
}
return text;
}
var serviceProvider = html.ViewContext.HttpContext.RequestServices;

View File

@ -205,6 +205,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return base.ComputeHideSurroundingHtml();
}
/// <summary>
/// Calculate <see cref="ModelMetadata.HtmlEncode"/> based on presence of a
/// <see cref="DisplayFormatAttribute"/> and its <see cref="DisplayFormatAttribute.HtmlEncode"/> value.
/// </summary>
/// <returns>
/// Calculated <see cref="ModelMetadata.HtmlEncode"/> value. <c>false</c> if a
/// <see cref="DisplayFormatAttribute"/> exists and its <see cref="DisplayFormatAttribute.HtmlEncode"/> value
/// is <c>false</c>. <c>true</c> otherwise.
/// </returns>
protected override bool ComputeHtmlEncode()
{
if (PrototypeCache.DisplayFormat != null)
{
return PrototypeCache.DisplayFormat.HtmlEncode;
}
return base.ComputeHtmlEncode();
}
protected override bool ComputeIsReadOnly()
{
if (PrototypeCache.Editable != null)

View File

@ -24,6 +24,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private string _editFormatString;
private bool _hasNonDefaultEditFormat;
private bool _hideSurroundingHtml;
private bool _htmlEncode;
private bool _isReadOnly;
private bool _isComplexType;
private bool _isRequired;
@ -39,6 +40,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private bool _editFormatStringComputed;
private bool _hasNonDefaultEditFormatComputed;
private bool _hideSurroundingHtmlComputed;
private bool _htmlEncodeComputed;
private bool _isReadOnlyComputed;
private bool _isComplexTypeComputed;
private bool _isRequiredComputed;
@ -252,6 +254,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <inheritdoc />
public sealed override bool HtmlEncode
{
get
{
if (!_htmlEncodeComputed)
{
_htmlEncode = ComputeHtmlEncode();
_htmlEncodeComputed = true;
}
return _htmlEncode;
}
set
{
_htmlEncode = value;
_htmlEncodeComputed = true;
}
}
public sealed override bool IsReadOnly
{
get
@ -419,6 +442,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return base.HideSurroundingHtml;
}
/// <summary>
/// Calculate the <see cref="HtmlEncode"/> value.
/// </summary>
/// <returns>Calculated <see cref="HtmlEncode"/> value.</returns>
protected virtual bool ComputeHtmlEncode()
{
return base.HtmlEncode;
}
protected virtual bool ComputeIsReadOnly()
{
return base.IsReadOnly;

View File

@ -19,17 +19,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private EfficientTypePropertyKey<Type, string> _cacheKey;
// Backing fields for virtual properties with default values.
private bool _convertEmptyStringToNull;
private bool _isRequired;
private bool _convertEmptyStringToNull = true;
private bool _htmlEncode = true;
private bool _showForDisplay = true;
private bool _showForEdit = true;
private object _model;
private Func<object> _modelAccessor;
private int _order = DefaultOrder;
private bool _isRequired;
private IEnumerable<ModelMetadata> _properties;
private Type _realModelType;
private string _simpleDisplayText;
private bool _showForDisplay = true;
private bool _showForEdit = true;
public ModelMetadata([NotNull] IModelMetadataProvider provider,
Type containerType,
@ -43,7 +44,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
_modelAccessor = modelAccessor;
_modelType = modelType;
_propertyName = propertyName;
_convertEmptyStringToNull = true;
_isRequired = !modelType.AllowsNullValue();
}
@ -111,6 +111,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public virtual bool HasNonDefaultEditFormat { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the value should be HTML-encoded.
/// </summary>
/// <value>If <c>true</c>, value should be HTML-encoded. Default is <c>true</c>.</value>
public virtual bool HtmlEncode
{
get { return _htmlEncode; }
set { _htmlEncode = value; }
}
/// <summary>
/// Gets or sets a value indicating whether the "HiddenInput" display template should return
/// <c>string.Empty</c> (not the expression value) and whether the "HiddenInput" editor template should not

View File

@ -2,7 +2,6 @@
// 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 System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
@ -14,6 +13,33 @@ namespace Microsoft.AspNet.Mvc.Core
{
public class DefaultDisplayTemplateTests
{
// Input value; HTML encode; expected value.
public static TheoryData<string, bool, string> HtmlEncodeData
{
get
{
return new TheoryData<string, bool, string>
{
{ "Simple Display Text", false, "Simple Display Text" },
{ "Simple Display Text", true, "Simple Display Text" },
{ "<blink>text</blink>", false, "<blink>text</blink>" },
{ "<blink>text</blink>", true, "&lt;blink&gt;text&lt;/blink&gt;" },
{ "&'\"", false, "&'\"" },
{ "&'\"", true, "&amp;&#39;&quot;" },
{ " ¡ÿĀ", false, " ¡ÿĀ" }, // high ASCII
{ " ¡ÿĀ", true, "&#160;&#161;&#255;Ā" },
{ "Chinese西雅图Chars", false, "Chinese西雅图Chars" },
{ "Chinese西雅图Chars", true, "Chinese西雅图Chars" },
{ "Unicode؃Format؃Char", false, "Unicode؃Format؃Char" }, // class Cf
{ "Unicode؃Format؃Char", true, "Unicode؃Format؃Char" },
{ "UnicodeῼTitlecaseῼChar", false, "UnicodeῼTitlecaseῼChar" }, // class Lt
{ "UnicodeῼTitlecaseῼChar", true, "UnicodeῼTitlecaseῼChar" },
{ "UnicodeCombiningChar", false, "UnicodeCombiningChar" }, // class Mc
{ "UnicodeCombiningChar", true, "UnicodeCombiningChar" },
};
}
}
[Fact]
public void ObjectTemplateDisplaysSimplePropertiesOnObjectByDefault()
{
@ -54,8 +80,12 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.Equal(metadata.NullDisplayText, result);
}
[Fact]
public void ObjectTemplateDisplaysSimpleDisplayTextWhenTemplateDepthGreaterThanOne()
[Theory]
[MemberData(nameof(HtmlEncodeData))]
public void ObjectTemplateDisplaysSimpleDisplayTextWhenTemplateDepthGreaterThanOne(
string simpleDisplayText,
bool htmlEncode,
string expectedResult)
{
// Arrange
var model = new DefaultTemplatesUtilities.ObjectTemplateModel();
@ -63,7 +93,8 @@ namespace Microsoft.AspNet.Mvc.Core
var metadata =
new EmptyModelMetadataProvider()
.GetMetadataForType(() => model, typeof(DefaultTemplatesUtilities.ObjectTemplateModel));
metadata.SimpleDisplayText = "Simple Display Text";
metadata.HtmlEncode = htmlEncode;
metadata.SimpleDisplayText = simpleDisplayText;
html.ViewData.ModelMetadata = metadata;
html.ViewData.TemplateInfo.AddVisited("foo");
html.ViewData.TemplateInfo.AddVisited("bar");
@ -72,7 +103,7 @@ namespace Microsoft.AspNet.Mvc.Core
var result = DefaultDisplayTemplates.ObjectTemplate(html);
// Assert
Assert.Equal(metadata.SimpleDisplayText, result);
Assert.Equal(expectedResult, result);
}
[Fact]

View File

@ -115,8 +115,12 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.Equal(metadata.NullDisplayText, result);
}
[Fact]
public void ObjectTemplateDisplaysSimpleDisplayTextWithNonNullModelTemplateDepthGreaterThanOne()
[Theory]
[MemberData(nameof(DefaultDisplayTemplateTests.HtmlEncodeData), MemberType = typeof(DefaultDisplayTemplateTests))]
public void ObjectTemplateDisplaysSimpleDisplayTextWithNonNullModelTemplateDepthGreaterThanOne(
string simpleDisplayText,
bool htmlEncode,
string expectedResult)
{
// Arrange
var model = new DefaultTemplatesUtilities.ObjectTemplateModel();
@ -124,9 +128,10 @@ namespace Microsoft.AspNet.Mvc.Core
var metadata =
new EmptyModelMetadataProvider()
.GetMetadataForType(() => model, typeof(DefaultTemplatesUtilities.ObjectTemplateModel));
html.ViewData.ModelMetadata = metadata;
metadata.HtmlEncode = htmlEncode;
metadata.NullDisplayText = "Null Display Text";
metadata.SimpleDisplayText = "Simple Display Text";
metadata.SimpleDisplayText = simpleDisplayText;
html.ViewData.ModelMetadata = metadata;
html.ViewData.TemplateInfo.AddVisited("foo");
html.ViewData.TemplateInfo.AddVisited("bar");
@ -134,7 +139,7 @@ namespace Microsoft.AspNet.Mvc.Core
var result = DefaultEditorTemplates.ObjectTemplate(html);
// Assert
Assert.Equal(metadata.SimpleDisplayText, result);
Assert.Equal(expectedResult, result);
}
[Fact]

View File

@ -32,6 +32,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.True(metadata.ConvertEmptyStringToNull);
Assert.False(metadata.HasNonDefaultEditFormat);
Assert.False(metadata.HideSurroundingHtml);
Assert.True(metadata.HtmlEncode);
Assert.True(metadata.IsComplexType);
Assert.False(metadata.IsReadOnly);
Assert.False(metadata.IsRequired);
@ -157,6 +158,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
metadata => metadata.HasNonDefaultEditFormat,
true
},
{
new DisplayFormatAttribute { HtmlEncode = false },
metadata => metadata.HtmlEncode,
false
},
{
new DisplayFormatAttribute { HtmlEncode = true },
metadata => metadata.HtmlEncode,
true
},
{
new EditableAttribute(allowEdit: false),
metadata => metadata.IsReadOnly,

View File

@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ m => m.ConvertEmptyStringToNull = false, m => m.ConvertEmptyStringToNull, false },
{ m => m.HasNonDefaultEditFormat = true, m => m.HasNonDefaultEditFormat, true },
{ m => m.HideSurroundingHtml = true, m => m.HideSurroundingHtml, true },
{ m => m.HtmlEncode = false, m => m.HtmlEncode, false },
{ m => m.IsReadOnly = true, m => m.IsReadOnly, true },
{ m => m.IsRequired = true, m => m.IsRequired, true },
{ m => m.ShowForDisplay = false, m => m.ShowForDisplay, false },
@ -59,6 +60,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.True(metadata.ConvertEmptyStringToNull);
Assert.False(metadata.HasNonDefaultEditFormat);
Assert.False(metadata.HideSurroundingHtml);
Assert.True(metadata.HtmlEncode);
Assert.False(metadata.IsComplexType);
Assert.False(metadata.IsNullableValueType);
Assert.False(metadata.IsReadOnly);