Add support for get-only indexer properties
- #399 - move invalid `HtmlAttributeNameAttribute.Name` checking to `TagHelperDescriptorFactory` - add a few new error cases - but does not cover all the new error cases e.g. `[HtmlAttributeName(...)]` on a get-only `int` property nit: - `resx` target removed some older resources from `RazorResources.Designer.cs`
This commit is contained in:
parent
1f480386f4
commit
69d8e52bf9
|
|
@ -234,6 +234,22 @@ namespace Microsoft.AspNet.Razor.Runtime
|
||||||
return GetString("TagHelperDescriptorFactory_Tag");
|
return GetString("TagHelperDescriptorFactory_Tag");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameAttribute
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(object p0, object p1, object p2, object p3)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"), p0, p1, p2, p3);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -283,19 +299,67 @@ namespace Microsoft.AspNet.Razor.Runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string TagHelperDescriptorFactory_InvalidAttributePrefix
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty
|
||||||
{
|
{
|
||||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefix"); }
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(object p0, object p1, object p2, object p3)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"), p0, p1, p2, p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNull
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"), p0, p1, p2, p3, p4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefix(object p0, object p1, object p2, object p3, object p4)
|
internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNotNull
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefix"), p0, p1, p2, p3, p4);
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"), p0, p1, p2, p3, p4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,9 @@
|
||||||
<data name="TagHelperDescriptorFactory_Tag" xml:space="preserve">
|
<data name="TagHelperDescriptorFactory_Tag" xml:space="preserve">
|
||||||
<value>Tag</value>
|
<value>Tag</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameAttribute" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.</value>
|
||||||
|
</data>
|
||||||
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter" xml:space="preserve">
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter" xml:space="preserve">
|
||||||
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.</value>
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -168,7 +171,16 @@
|
||||||
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace" xml:space="preserve">
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace" xml:space="preserve">
|
||||||
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.</value>
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TagHelperDescriptorFactory_InvalidAttributePrefix" xml:space="preserve">
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNull" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNotNull" xml:space="preserve">
|
||||||
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.</value>
|
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TagHelperAttributeList_CannotAddWithNullName" xml:space="preserve">
|
<data name="TagHelperAttributeList_CannotAddWithNullName" xml:space="preserve">
|
||||||
|
|
|
||||||
|
|
@ -13,23 +13,41 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
{
|
{
|
||||||
private string _dictionaryAttributePrefix;
|
private string _dictionaryAttributePrefix;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class with <see cref="Name"/>
|
||||||
|
/// equal to <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Associated property must not have a public setter and must be compatible with
|
||||||
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||||
|
/// <see cref="string"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public HtmlAttributeNameAttribute()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class.
|
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">HTML attribute name for the associated property.</param>
|
/// <param name="name">
|
||||||
|
/// HTML attribute name for the associated property. Must be <c>null</c> or empty if associated property does
|
||||||
|
/// not have a public setter and is compatible with
|
||||||
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||||
|
/// <see cref="string"/>. Otherwise must not be <c>null</c> or empty.
|
||||||
|
/// </param>
|
||||||
public HtmlAttributeNameAttribute(string name)
|
public HtmlAttributeNameAttribute(string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// HTML attribute name of the associated property.
|
/// HTML attribute name of the associated property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>null</c> or empty if and only if associated property does not have a public setter and is compatible
|
||||||
|
/// with <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||||
|
/// <see cref="string"/>.
|
||||||
|
/// </value>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -42,9 +60,14 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// <see cref="string"/>.
|
/// <see cref="string"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <value>
|
/// <value>
|
||||||
|
/// <para>
|
||||||
/// If associated property is compatible with
|
/// If associated property is compatible with
|
||||||
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>, default value is <c>Name + "-"</c>.
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>, default value is <c>Name + "-"</c>.
|
||||||
|
/// <see cref="Name"/> must not be <c>null</c> or empty in this case.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
/// Otherwise default value is <c>null</c>.
|
/// Otherwise default value is <c>null</c>.
|
||||||
|
/// </para>
|
||||||
/// </value>
|
/// </value>
|
||||||
public string DictionaryAttributePrefix
|
public string DictionaryAttributePrefix
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -256,45 +256,72 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
bool designTime,
|
bool designTime,
|
||||||
ErrorSink errorSink)
|
ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
|
|
||||||
var attributeDescriptors = new List<TagHelperAttributeDescriptor>();
|
var attributeDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||||
|
|
||||||
// Keep indexer descriptors separate to avoid sorting the combined list later.
|
// Keep indexer descriptors separate to avoid sorting the combined list later.
|
||||||
var indexerDescriptors = new List<TagHelperAttributeDescriptor>();
|
var indexerDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||||
|
|
||||||
|
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
|
||||||
foreach (var property in accessibleProperties)
|
foreach (var property in accessibleProperties)
|
||||||
{
|
{
|
||||||
var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
|
var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
|
||||||
var descriptor = ToAttributeDescriptor(property, attributeNameAttribute, designTime);
|
var hasExplicitName =
|
||||||
if (ValidateTagHelperAttributeDescriptor(descriptor, type, errorSink))
|
attributeNameAttribute != null && !string.IsNullOrEmpty(attributeNameAttribute.Name);
|
||||||
|
var attributeName = hasExplicitName ? attributeNameAttribute.Name : ToHtmlCase(property.Name);
|
||||||
|
|
||||||
|
TagHelperAttributeDescriptor mainDescriptor = null;
|
||||||
|
if (property.SetMethod?.IsPublic == true)
|
||||||
{
|
{
|
||||||
bool isInvalid;
|
mainDescriptor = ToAttributeDescriptor(property, attributeName, designTime);
|
||||||
var indexerDescriptor = ToIndexerAttributeDescriptor(
|
if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink))
|
||||||
property,
|
|
||||||
attributeNameAttribute,
|
|
||||||
parentType: type,
|
|
||||||
errorSink: errorSink,
|
|
||||||
defaultPrefix: descriptor.Name + "-",
|
|
||||||
designTime: designTime,
|
|
||||||
isInvalid: out isInvalid);
|
|
||||||
|
|
||||||
if (indexerDescriptor != null &&
|
|
||||||
!ValidateTagHelperAttributeDescriptor(indexerDescriptor, type, errorSink))
|
|
||||||
{
|
{
|
||||||
isInvalid = true;
|
// HtmlAttributeNameAttribute.Name is invalid. Ignore this property completely.
|
||||||
}
|
|
||||||
|
|
||||||
if (isInvalid)
|
|
||||||
{
|
|
||||||
// HtmlAttributeNameAttribute was not valid. Ignore this property completely.
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (hasExplicitName)
|
||||||
|
{
|
||||||
|
// Specified HtmlAttributeNameAttribute.Name though property has no public setter.
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(
|
||||||
|
type.FullName,
|
||||||
|
property.Name,
|
||||||
|
typeof(HtmlAttributeNameAttribute).FullName,
|
||||||
|
nameof(HtmlAttributeNameAttribute.Name)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
attributeDescriptors.Add(descriptor);
|
bool isInvalid;
|
||||||
if (indexerDescriptor != null)
|
var indexerDescriptor = ToIndexerAttributeDescriptor(
|
||||||
{
|
property,
|
||||||
indexerDescriptors.Add(indexerDescriptor);
|
attributeNameAttribute,
|
||||||
}
|
parentType: type,
|
||||||
|
errorSink: errorSink,
|
||||||
|
defaultPrefix: attributeName + "-",
|
||||||
|
designTime: designTime,
|
||||||
|
isInvalid: out isInvalid);
|
||||||
|
if (indexerDescriptor != null &&
|
||||||
|
!ValidateTagHelperAttributeDescriptor(indexerDescriptor, type, errorSink))
|
||||||
|
{
|
||||||
|
isInvalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInvalid)
|
||||||
|
{
|
||||||
|
// The property type or HtmlAttributeNameAttribute.DictionaryAttributePrefix (or perhaps the
|
||||||
|
// HTML-casing of the property name) is invalid. Ignore this property completely.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainDescriptor != null)
|
||||||
|
{
|
||||||
|
attributeDescriptors.Add(mainDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexerDescriptor != null)
|
||||||
|
{
|
||||||
|
indexerDescriptors.Add(indexerDescriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,9 +336,26 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Type parentType,
|
Type parentType,
|
||||||
ErrorSink errorSink)
|
ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var nameOrPrefix = attributeDescriptor.IsIndexer ?
|
string nameOrPrefix;
|
||||||
Resources.TagHelperDescriptorFactory_Prefix :
|
if (attributeDescriptor.IsIndexer)
|
||||||
Resources.TagHelperDescriptorFactory_Name;
|
{
|
||||||
|
nameOrPrefix = Resources.TagHelperDescriptorFactory_Prefix;
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(attributeDescriptor.Name))
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(
|
||||||
|
parentType.FullName,
|
||||||
|
attributeDescriptor.PropertyName));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nameOrPrefix = Resources.TagHelperDescriptorFactory_Name;
|
||||||
|
}
|
||||||
|
|
||||||
return ValidateTagHelperAttributeNameOrPrefix(
|
return ValidateTagHelperAttributeNameOrPrefix(
|
||||||
attributeDescriptor.Name,
|
attributeDescriptor.Name,
|
||||||
parentType,
|
parentType,
|
||||||
|
|
@ -329,8 +373,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(attributeNameOrPrefix))
|
if (string.IsNullOrEmpty(attributeNameOrPrefix))
|
||||||
{
|
{
|
||||||
// HtmlAttributeNameAttribute validates Name is non-null and non-empty. Both are valid for
|
// ValidateTagHelperAttributeDescriptor validates Name is non-null and non-empty. The empty string is
|
||||||
// DictionaryAttributePrefix. (Empty DictionaryAttributePrefix is a corner case which would bind every
|
// valid for DictionaryAttributePrefix and null is impossible at this point because it means "don't
|
||||||
|
// create a descriptor". (Empty DictionaryAttributePrefix is a corner case which would bind every
|
||||||
// attribute of a target element. Likely not particularly useful but unclear what minimum length
|
// attribute of a target element. Likely not particularly useful but unclear what minimum length
|
||||||
// should be required and what scenarios a minimum length would break.)
|
// should be required and what scenarios a minimum length would break.)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -388,13 +433,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
|
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
|
||||||
PropertyInfo property,
|
PropertyInfo property,
|
||||||
HtmlAttributeNameAttribute attributeNameAttribute,
|
string attributeName,
|
||||||
bool designTime)
|
bool designTime)
|
||||||
{
|
{
|
||||||
var attributeName = attributeNameAttribute != null ?
|
|
||||||
attributeNameAttribute.Name :
|
|
||||||
ToHtmlCase(property.Name);
|
|
||||||
|
|
||||||
return ToAttributeDescriptor(
|
return ToAttributeDescriptor(
|
||||||
property,
|
property,
|
||||||
attributeName,
|
attributeName,
|
||||||
|
|
@ -413,6 +454,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
out bool isInvalid)
|
out bool isInvalid)
|
||||||
{
|
{
|
||||||
isInvalid = false;
|
isInvalid = false;
|
||||||
|
var hasPublicSetter = property.SetMethod?.IsPublic == true;
|
||||||
var dictionaryTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
|
var dictionaryTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
|
||||||
property.PropertyType,
|
property.PropertyType,
|
||||||
typeof(IDictionary<,>))
|
typeof(IDictionary<,>))
|
||||||
|
|
@ -426,13 +468,44 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
isInvalid = true;
|
isInvalid = true;
|
||||||
errorSink.OnError(
|
errorSink.OnError(
|
||||||
SourceLocation.Zero,
|
SourceLocation.Zero,
|
||||||
Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefix(
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(
|
||||||
parentType.FullName,
|
parentType.FullName,
|
||||||
property.Name,
|
property.Name,
|
||||||
nameof(HtmlAttributeNameAttribute),
|
nameof(HtmlAttributeNameAttribute),
|
||||||
nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
|
nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
|
||||||
"IDictionary<string, TValue>"));
|
"IDictionary<string, TValue>"));
|
||||||
}
|
}
|
||||||
|
else if (attributeNameAttribute != null && !hasPublicSetter)
|
||||||
|
{
|
||||||
|
// Associated an HtmlAttributeNameAttribute with a non-dictionary property that lacks a public
|
||||||
|
// setter.
|
||||||
|
isInvalid = true;
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(
|
||||||
|
parentType.FullName,
|
||||||
|
property.Name,
|
||||||
|
nameof(HtmlAttributeNameAttribute),
|
||||||
|
"IDictionary<string, TValue>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (!hasPublicSetter &&
|
||||||
|
attributeNameAttribute != null &&
|
||||||
|
!attributeNameAttribute.DictionaryAttributePrefixSet)
|
||||||
|
{
|
||||||
|
// Must set DictionaryAttributePrefix when using HtmlAttributeNameAttribute with a dictionary property
|
||||||
|
// that lacks a public setter.
|
||||||
|
isInvalid = true;
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(
|
||||||
|
parentType.FullName,
|
||||||
|
property.Name,
|
||||||
|
nameof(HtmlAttributeNameAttribute),
|
||||||
|
nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
|
||||||
|
"IDictionary<string, TValue>"));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -482,9 +555,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
private static bool IsAccessibleProperty(PropertyInfo property)
|
private static bool IsAccessibleProperty(PropertyInfo property)
|
||||||
{
|
{
|
||||||
// Accessible properties are those with public getters and setters and without [HtmlAttributeNotBound].
|
// Accessible properties are those with public getters and without [HtmlAttributeNotBound].
|
||||||
return property.GetMethod?.IsPublic == true &&
|
return property.GetMethod?.IsPublic == true &&
|
||||||
property.SetMethod?.IsPublic == true &&
|
|
||||||
property.GetCustomAttribute<HtmlAttributeNotBoundAttribute>(inherit: false) == null;
|
property.GetCustomAttribute<HtmlAttributeNotBoundAttribute>(inherit: false) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1286,22 +1286,6 @@ namespace Microsoft.AspNet.Razor
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_CannotHaveCSharpInTagDeclaration"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_CannotHaveCSharpInTagDeclaration"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A TagHelperChunkGenerator must only be used with TagHelperBlocks.
|
|
||||||
/// </summary>
|
|
||||||
internal static string TagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock
|
|
||||||
{
|
|
||||||
get { return GetString("TagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A TagHelperChunkGenerator must only be used with TagHelperBlocks.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatTagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock()
|
|
||||||
{
|
|
||||||
return GetString("TagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Directive '{0}' must have a value.
|
/// Directive '{0}' must have a value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -958,7 +958,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
isStringProperty: false,
|
isStringProperty: false,
|
||||||
designTimeDescriptor: null),
|
designTimeDescriptor: null),
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "valid-prefix",
|
name: "valid-name-",
|
||||||
propertyName: nameof(SingleValidHtmlAttributePrefix.DictionaryProperty),
|
propertyName: nameof(SingleValidHtmlAttributePrefix.DictionaryProperty),
|
||||||
typeName: typeof(string).FullName,
|
typeName: typeof(string).FullName,
|
||||||
isIndexer: true,
|
isIndexer: true,
|
||||||
|
|
@ -1048,6 +1048,20 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
isIndexer: true,
|
isIndexer: true,
|
||||||
isStringProperty: false,
|
isStringProperty: false,
|
||||||
designTimeDescriptor: null),
|
designTimeDescriptor: null),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "get-only-dictionary-property-",
|
||||||
|
propertyName: nameof(MultipleValidHtmlAttributePrefix.GetOnlyDictionaryProperty),
|
||||||
|
typeName: typeof(int).FullName,
|
||||||
|
isIndexer: true,
|
||||||
|
isStringProperty: false,
|
||||||
|
designTimeDescriptor: null),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "valid-prefix6",
|
||||||
|
propertyName: nameof(MultipleValidHtmlAttributePrefix.GetOnlyDictionaryPropertyWithAttributePrefix),
|
||||||
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: true,
|
||||||
|
isStringProperty: true,
|
||||||
|
designTimeDescriptor: null),
|
||||||
},
|
},
|
||||||
new string[0]
|
new string[0]
|
||||||
},
|
},
|
||||||
|
|
@ -1087,6 +1101,14 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
onError(
|
onError(
|
||||||
typeof(MultipleInvalidHtmlAttributePrefix).FullName,
|
typeof(MultipleInvalidHtmlAttributePrefix).FullName,
|
||||||
nameof(MultipleInvalidHtmlAttributePrefix.DictionaryOfIntSubclassProperty)),
|
nameof(MultipleInvalidHtmlAttributePrefix.DictionaryOfIntSubclassProperty)),
|
||||||
|
onError(
|
||||||
|
typeof(MultipleInvalidHtmlAttributePrefix).FullName,
|
||||||
|
nameof(MultipleInvalidHtmlAttributePrefix.GetOnlyDictionaryAttributePrefix)),
|
||||||
|
$"Invalid tag helper bound property '{ typeof(MultipleInvalidHtmlAttributePrefix).FullName }." +
|
||||||
|
$"{ nameof(MultipleInvalidHtmlAttributePrefix.GetOnlyDictionaryPropertyWithAttributeName) }'. " +
|
||||||
|
$"'{ typeof(HtmlAttributeNameAttribute).FullName }." +
|
||||||
|
$"{ nameof(HtmlAttributeNameAttribute.Name) }' must be null or empty if property has " +
|
||||||
|
"no public setter.",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -1128,14 +1150,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
TagHelperAttributeDescriptorComparer.Default);
|
TagHelperAttributeDescriptorComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TheoryData<string> ValidAttributeNameOrPrefixData
|
public static TheoryData<string> ValidAttributeNameData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return new TheoryData<string>
|
return new TheoryData<string>
|
||||||
{
|
{
|
||||||
null,
|
|
||||||
string.Empty,
|
|
||||||
"data",
|
"data",
|
||||||
"dataa-",
|
"dataa-",
|
||||||
"ValidName",
|
"ValidName",
|
||||||
|
|
@ -1147,7 +1167,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(ValidAttributeNameOrPrefixData))]
|
[MemberData(nameof(ValidAttributeNameData))]
|
||||||
public void ValidateTagHelperAttributeDescriptor_WithValidName_ReturnsTrue(string name)
|
public void ValidateTagHelperAttributeDescriptor_WithValidName_ReturnsTrue(string name)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -1170,8 +1190,25 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Assert.Empty(errorSink.Errors);
|
Assert.Empty(errorSink.Errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TheoryData<string> ValidAttributePrefixData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<string>
|
||||||
|
{
|
||||||
|
string.Empty,
|
||||||
|
"data",
|
||||||
|
"dataa-",
|
||||||
|
"ValidName",
|
||||||
|
"valid-name",
|
||||||
|
"--valid--name--",
|
||||||
|
",,--__..oddly.valid::;;",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(ValidAttributeNameOrPrefixData))]
|
[MemberData(nameof(ValidAttributePrefixData))]
|
||||||
public void ValidateTagHelperAttributeDescriptor_WithValidPrefix_ReturnsTrue(string prefix)
|
public void ValidateTagHelperAttributeDescriptor_WithValidPrefix_ReturnsTrue(string prefix)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -1732,7 +1769,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
private class SingleValidHtmlAttributePrefix : TagHelper
|
private class SingleValidHtmlAttributePrefix : TagHelper
|
||||||
{
|
{
|
||||||
[HtmlAttributeName("valid-name", DictionaryAttributePrefix = "valid-prefix")]
|
[HtmlAttributeName("valid-name")]
|
||||||
public IDictionary<string, string> DictionaryProperty { get; set; }
|
public IDictionary<string, string> DictionaryProperty { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1755,6 +1792,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
[HtmlAttributeName("valid-name6")]
|
[HtmlAttributeName("valid-name6")]
|
||||||
public string StringProperty { get; set; }
|
public string StringProperty { get; set; }
|
||||||
|
|
||||||
|
public IDictionary<string, int> GetOnlyDictionaryProperty { get; }
|
||||||
|
|
||||||
|
[HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")]
|
||||||
|
public IDictionary<string, string> GetOnlyDictionaryPropertyWithAttributePrefix { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SingleInvalidHtmlAttributePrefix : TagHelper
|
private class SingleInvalidHtmlAttributePrefix : TagHelper
|
||||||
|
|
@ -1779,6 +1821,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
[HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")]
|
[HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")]
|
||||||
public DictionaryOfIntSubclass DictionaryOfIntSubclassProperty { get; set; }
|
public DictionaryOfIntSubclass DictionaryOfIntSubclassProperty { get; set; }
|
||||||
|
|
||||||
|
[HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")]
|
||||||
|
public IDictionary<int, string> GetOnlyDictionaryAttributePrefix { get; }
|
||||||
|
|
||||||
|
[HtmlAttributeName("invalid-name7")]
|
||||||
|
public IDictionary<string, object> GetOnlyDictionaryPropertyWithAttributeName { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DictionarySubclass : Dictionary<string, string>
|
private class DictionarySubclass : Dictionary<string, string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue