Add `[HtmlAttributeName(..., DictionaryAttributePrefix="prefix")]` part III
- #89 remainder - support adding attributes (that aren't otherwise bound) to a tag helper dictionary - use two `TagHelperAttributeDescriptor`s for dictionary and indexer - most exising descriptor properties have two meanings depending on new `IsIndexer` - add `TagHelperAttributeDescriptor.IsNameMatch()` - create no `TagHelperAttributeDescriptor`s if property name or `HtmlAttributeNameAttribute` is invalid - avoid corner case misfeatures where invalidity removes just one descriptor - extend handling of invalid attribute names to include `[HtmlAttributeName]` - handle prefix matches in `TagHelperBlockRewriter` - add parse error when resolved dictionary key is `string.Empty` - generate code for indexer property assignments - add code generation for runtime error if using indexer when property is `null` - use new `GeneratedTagHelperContext.FormatInvalidIndexerAssignmentMethodName` for message - code generation now handles attributes in source order; thus above errors occur only when expected if dictionary is also initialized in the Razor source - surprisingly generation order change did not break existing tests! nits: - improve `TagHelperDescriptorFactory_InvalidBoundAttributeName` wording - rename resource to `TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart` - correct order of arguments to `FormatTagHelperDescriptorFactory_InvalidBoundAttributeName` - correct `TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed` resource - correct `TagHelperDescriptorFactoryTest` test names - remove a few unnecessary `ToArray()` calls - update `TagHelperAttributeDescriptorComparer` comments
This commit is contained in:
parent
1879ac6427
commit
60c47c8874
|
|
@ -186,6 +186,38 @@ namespace Microsoft.AspNet.Razor.Runtime
|
||||||
return GetString("TagHelperDescriptorFactory_Attribute");
|
return GetString("TagHelperDescriptorFactory_Attribute");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// name
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_Name
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_Name"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// name
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_Name()
|
||||||
|
{
|
||||||
|
return GetString("TagHelperDescriptorFactory_Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// prefix
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_Prefix
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_Prefix"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// prefix
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_Prefix()
|
||||||
|
{
|
||||||
|
return GetString("TagHelperDescriptorFactory_Prefix");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tag
|
/// Tag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -203,19 +235,67 @@ namespace Microsoft.AspNet.Razor.Runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.
|
/// 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>
|
||||||
internal static string TagHelperDescriptorFactory_InvalidBoundAttributeName
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter
|
||||||
{
|
{
|
||||||
get { return GetString("TagHelperDescriptorFactory_InvalidBoundAttributeName"); }
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.
|
/// 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>
|
||||||
internal static string FormatTagHelperDescriptorFactory_InvalidBoundAttributeName(object p0, object p1, object p2)
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter(object p0, object p1, object p2, object p3, object p4)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidBoundAttributeName"), p0, p1, p2);
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"), p0, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"), p0, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidAttributePrefix
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefix"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefix(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefix"), p0, p1, p2, p3, p4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -150,11 +150,26 @@
|
||||||
<data name="TagHelperDescriptorFactory_Attribute" xml:space="preserve">
|
<data name="TagHelperDescriptorFactory_Attribute" xml:space="preserve">
|
||||||
<value>Attribute</value>
|
<value>Attribute</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_Name" xml:space="preserve">
|
||||||
|
<value>name</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_Prefix" xml:space="preserve">
|
||||||
|
<value>prefix</value>
|
||||||
|
</data>
|
||||||
<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_InvalidBoundAttributeName" 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 beginning with '{2}'.</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 name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidAttributePrefix" xml:space="preserve">
|
||||||
|
<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">
|
||||||
<value>Cannot add a '{0}' with a null '{1}'.</value>
|
<value>Cannot add a '{0}' with a null '{1}'.</value>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||||
public sealed class HtmlAttributeNameAttribute : Attribute
|
public sealed class HtmlAttributeNameAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
private string _dictionaryAttributePrefix;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class.
|
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -29,5 +31,39 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// HTML attribute name of the associated property.
|
/// HTML attribute name of the associated property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the prefix used to match HTML attribute names. Matching attributes are added to the
|
||||||
|
/// associated property (an <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If non-<c>null</c> associated property must be compatible with
|
||||||
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||||
|
/// <see cref="string"/>.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value>
|
||||||
|
/// If associated property is compatible with
|
||||||
|
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>, default value is <c>Name + "-"</c>.
|
||||||
|
/// Otherwise default value is <c>null</c>.
|
||||||
|
/// </value>
|
||||||
|
public string DictionaryAttributePrefix
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _dictionaryAttributePrefix;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_dictionaryAttributePrefix = value;
|
||||||
|
DictionaryAttributePrefixSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an indication whether <see cref="DictionaryAttributePrefix"/> has been set. Used to distinguish an
|
||||||
|
/// uninitialized <see cref="DictionaryAttributePrefix"/> value from an explicit <c>null</c> setting.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if <see cref="DictionaryAttributePrefix"/> was set. <c>false</c> otherwise.</value>
|
||||||
|
public bool DictionaryAttributePrefixSet { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assemblyName">The assembly name that contains <paramref name="type"/>.</param>
|
/// <param name="assemblyName">The assembly name that contains <paramref name="type"/>.</param>
|
||||||
/// <param name="type">The type to create a <see cref="TagHelperDescriptor"/> from.</param>
|
/// <param name="type">The type to create a <see cref="TagHelperDescriptor"/> from.</param>
|
||||||
/// <returns>A <see cref="TagHelperDescriptor"/> that describes the given <paramref name="type"/>.</returns>
|
/// <returns>
|
||||||
|
/// A collection of <see cref="TagHelperDescriptor"/>s that describe the given <paramref name="type"/>.
|
||||||
|
/// </returns>
|
||||||
public static IEnumerable<TagHelperDescriptor> CreateDescriptors(
|
public static IEnumerable<TagHelperDescriptor> CreateDescriptors(
|
||||||
string assemblyName,
|
string assemblyName,
|
||||||
[NotNull] Type type,
|
[NotNull] Type type,
|
||||||
|
|
@ -209,48 +211,195 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
|
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.
|
||||||
|
var indexerDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||||
|
|
||||||
foreach (var property in accessibleProperties)
|
foreach (var property in accessibleProperties)
|
||||||
{
|
{
|
||||||
var descriptor = ToAttributeDescriptor(property);
|
var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
|
||||||
|
var descriptor = ToAttributeDescriptor(property, attributeNameAttribute);
|
||||||
if (ValidateTagHelperAttributeDescriptor(descriptor, type, errorSink))
|
if (ValidateTagHelperAttributeDescriptor(descriptor, type, errorSink))
|
||||||
{
|
{
|
||||||
|
bool isInvalid;
|
||||||
|
var indexerDescriptor = ToIndexerAttributeDescriptor(
|
||||||
|
property,
|
||||||
|
attributeNameAttribute,
|
||||||
|
parentType: type,
|
||||||
|
errorSink: errorSink,
|
||||||
|
defaultPrefix: descriptor.Name + "-",
|
||||||
|
isInvalid: out isInvalid);
|
||||||
|
|
||||||
|
if (indexerDescriptor != null &&
|
||||||
|
!ValidateTagHelperAttributeDescriptor(indexerDescriptor, type, errorSink))
|
||||||
|
{
|
||||||
|
isInvalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInvalid)
|
||||||
|
{
|
||||||
|
// HtmlAttributeNameAttribute was not valid. Ignore this property completely.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
attributeDescriptors.Add(descriptor);
|
attributeDescriptors.Add(descriptor);
|
||||||
|
if (indexerDescriptor != null)
|
||||||
|
{
|
||||||
|
indexerDescriptors.Add(indexerDescriptor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attributeDescriptors.AddRange(indexerDescriptors);
|
||||||
|
|
||||||
return attributeDescriptors;
|
return attributeDescriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ValidateTagHelperAttributeDescriptor(
|
// Internal for testing.
|
||||||
|
internal static bool ValidateTagHelperAttributeDescriptor(
|
||||||
TagHelperAttributeDescriptor attributeDescriptor,
|
TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
Type parentType,
|
Type parentType,
|
||||||
ErrorSink errorSink)
|
ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
|
var nameOrPrefix = attributeDescriptor.IsIndexer ?
|
||||||
|
Resources.TagHelperDescriptorFactory_Prefix :
|
||||||
|
Resources.TagHelperDescriptorFactory_Name;
|
||||||
|
return ValidateTagHelperAttributeNameOrPrefix(
|
||||||
|
attributeDescriptor.Name,
|
||||||
|
parentType,
|
||||||
|
attributeDescriptor.PropertyName,
|
||||||
|
errorSink,
|
||||||
|
nameOrPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ValidateTagHelperAttributeNameOrPrefix(
|
||||||
|
string attributeNameOrPrefix,
|
||||||
|
Type parentType,
|
||||||
|
string propertyName,
|
||||||
|
ErrorSink errorSink,
|
||||||
|
string nameOrPrefix)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(attributeNameOrPrefix))
|
||||||
|
{
|
||||||
|
// HtmlAttributeNameAttribute validates Name is non-null and non-empty. Both are valid for
|
||||||
|
// DictionaryAttributePrefix. (Empty DictionaryAttributePrefix is a corner case which would bind every
|
||||||
|
// 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.)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(attributeNameOrPrefix))
|
||||||
|
{
|
||||||
|
// Provide a single error if the entire name is whitespace, not an error per character.
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(
|
||||||
|
parentType.FullName,
|
||||||
|
propertyName,
|
||||||
|
nameOrPrefix));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// data-* attributes are explicitly not implemented by user agents and are not intended for use on
|
// data-* attributes are explicitly not implemented by user agents and are not intended for use on
|
||||||
// the server; therefore it's invalid for TagHelpers to bind to them.
|
// the server; therefore it's invalid for TagHelpers to bind to them.
|
||||||
if (attributeDescriptor.Name.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase))
|
if (attributeNameOrPrefix.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
errorSink.OnError(
|
errorSink.OnError(
|
||||||
SourceLocation.Zero,
|
SourceLocation.Zero,
|
||||||
Resources.FormatTagHelperDescriptorFactory_InvalidBoundAttributeName(
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart(
|
||||||
attributeDescriptor.PropertyName,
|
|
||||||
parentType.FullName,
|
parentType.FullName,
|
||||||
|
propertyName,
|
||||||
|
nameOrPrefix,
|
||||||
|
attributeNameOrPrefix,
|
||||||
DataDashPrefix));
|
DataDashPrefix));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
var isValid = true;
|
||||||
|
foreach (var character in attributeNameOrPrefix)
|
||||||
|
{
|
||||||
|
if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character))
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter(
|
||||||
|
parentType.FullName,
|
||||||
|
propertyName,
|
||||||
|
nameOrPrefix,
|
||||||
|
attributeNameOrPrefix,
|
||||||
|
character));
|
||||||
|
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TagHelperAttributeDescriptor ToAttributeDescriptor(PropertyInfo property)
|
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
|
||||||
|
PropertyInfo property,
|
||||||
|
HtmlAttributeNameAttribute attributeNameAttribute)
|
||||||
{
|
{
|
||||||
var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
|
|
||||||
var attributeName = attributeNameAttribute != null ?
|
var attributeName = attributeNameAttribute != null ?
|
||||||
attributeNameAttribute.Name :
|
attributeNameAttribute.Name :
|
||||||
ToHtmlCase(property.Name);
|
ToHtmlCase(property.Name);
|
||||||
|
|
||||||
return new TagHelperAttributeDescriptor(attributeName, property.Name, property.PropertyType.FullName);
|
return new TagHelperAttributeDescriptor(
|
||||||
|
attributeName,
|
||||||
|
property.Name,
|
||||||
|
property.PropertyType.FullName,
|
||||||
|
isIndexer: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
|
||||||
|
PropertyInfo property,
|
||||||
|
HtmlAttributeNameAttribute attributeNameAttribute,
|
||||||
|
Type parentType,
|
||||||
|
ErrorSink errorSink,
|
||||||
|
string defaultPrefix,
|
||||||
|
out bool isInvalid)
|
||||||
|
{
|
||||||
|
isInvalid = false;
|
||||||
|
var dictionaryTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
|
||||||
|
property.PropertyType,
|
||||||
|
typeof(IDictionary<,>))
|
||||||
|
?.GenericTypeArguments;
|
||||||
|
if (dictionaryTypeArguments?[0] != typeof(string))
|
||||||
|
{
|
||||||
|
if (attributeNameAttribute?.DictionaryAttributePrefix != null)
|
||||||
|
{
|
||||||
|
// DictionaryAttributePrefix is not supported unless associated with an
|
||||||
|
// IDictionary<string, TValue> property.
|
||||||
|
isInvalid = true;
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefix(
|
||||||
|
parentType.FullName,
|
||||||
|
property.Name,
|
||||||
|
nameof(HtmlAttributeNameAttribute),
|
||||||
|
nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
|
||||||
|
"IDictionary<string, TValue>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Potential prefix case. Use default prefix (based on name)?
|
||||||
|
var useDefault = attributeNameAttribute == null || !attributeNameAttribute.DictionaryAttributePrefixSet;
|
||||||
|
|
||||||
|
var prefix = useDefault ? defaultPrefix : attributeNameAttribute.DictionaryAttributePrefix;
|
||||||
|
if (prefix == null)
|
||||||
|
{
|
||||||
|
// DictionaryAttributePrefix explicitly set to null. Ignore.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TagHelperAttributeDescriptor(
|
||||||
|
name: prefix,
|
||||||
|
propertyName: property.Name,
|
||||||
|
typeName: dictionaryTypeArguments[1].FullName,
|
||||||
|
isIndexer: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsAccessibleProperty(PropertyInfo property)
|
private static bool IsAccessibleProperty(PropertyInfo property)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Razor": "4.0.0-*",
|
"Microsoft.AspNet.Razor": "4.0.0-*",
|
||||||
"Microsoft.Framework.BufferEntryCollection.Sources": { "type": "build", "version": "1.0.0-*" },
|
"Microsoft.Framework.BufferEntryCollection.Sources": { "type": "build", "version": "1.0.0-*" },
|
||||||
|
"Microsoft.Framework.ClosedGenericMatcher.Sources": { "type": "build", "version": "1.0.0-*" },
|
||||||
"Microsoft.Framework.CopyOnWriteDictionary.Sources": { "type": "build", "version": "1.0.0-*" },
|
"Microsoft.Framework.CopyOnWriteDictionary.Sources": { "type": "build", "version": "1.0.0-*" },
|
||||||
"Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" }
|
"Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" }
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,9 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
|
|
||||||
// Determine what attributes exist in the element and divide them up.
|
// Determine what attributes exist in the element and divide them up.
|
||||||
var htmlAttributes = chunk.Attributes;
|
var htmlAttributes = chunk.Attributes;
|
||||||
var attributeDescriptors = tagHelperDescriptors.SelectMany(descriptor => descriptor.Attributes);
|
var attributeDescriptors = tagHelperDescriptors.SelectMany(descriptor => descriptor.Attributes).ToArray();
|
||||||
var unboundHtmlAttributes = htmlAttributes.Where(
|
var unboundHtmlAttributes = htmlAttributes.Where(attribute => !attributeDescriptors.Any(
|
||||||
attribute => !attributeDescriptors.Any(
|
attributeDescriptor => attributeDescriptor.IsNameMatch(attribute.Key)));
|
||||||
descriptor => string.Equals(attribute.Key, descriptor.Name, StringComparison.OrdinalIgnoreCase)));
|
|
||||||
|
|
||||||
RenderUnboundHTMLAttributes(unboundHtmlAttributes);
|
RenderUnboundHTMLAttributes(unboundHtmlAttributes);
|
||||||
|
|
||||||
|
|
@ -182,6 +181,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
// Render all of the bound attribute values for the tag helper.
|
// Render all of the bound attribute values for the tag helper.
|
||||||
RenderBoundHTMLAttributes(
|
RenderBoundHTMLAttributes(
|
||||||
chunk.Attributes,
|
chunk.Attributes,
|
||||||
|
tagHelperDescriptor.TypeName,
|
||||||
tagHelperVariableName,
|
tagHelperVariableName,
|
||||||
tagHelperDescriptor.Attributes,
|
tagHelperDescriptor.Attributes,
|
||||||
htmlAttributeValues);
|
htmlAttributeValues);
|
||||||
|
|
@ -190,81 +190,126 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
|
|
||||||
private void RenderBoundHTMLAttributes(
|
private void RenderBoundHTMLAttributes(
|
||||||
IList<KeyValuePair<string, Chunk>> chunkAttributes,
|
IList<KeyValuePair<string, Chunk>> chunkAttributes,
|
||||||
|
string tagHelperTypeName,
|
||||||
string tagHelperVariableName,
|
string tagHelperVariableName,
|
||||||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||||
Dictionary<string, string> htmlAttributeValues)
|
Dictionary<string, string> htmlAttributeValues)
|
||||||
{
|
{
|
||||||
foreach (var attributeDescriptor in attributeDescriptors)
|
// Track dictionary properties we have confirmed are non-null.
|
||||||
|
var confirmedDictionaries = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
// First attribute wins, even if there are duplicates.
|
||||||
|
var distinctAttributes = chunkAttributes.Distinct(KeyValuePairKeyComparer.Default);
|
||||||
|
|
||||||
|
// Go through the HTML attributes in source order, assigning to properties or indexers as we go.
|
||||||
|
foreach (var attributeKeyValuePair in distinctAttributes)
|
||||||
{
|
{
|
||||||
var matchingAttributes = chunkAttributes.Where(
|
var attributeName = attributeKeyValuePair.Key;
|
||||||
kvp => string.Equals(kvp.Key, attributeDescriptor.Name, StringComparison.OrdinalIgnoreCase));
|
var attributeValueChunk = attributeKeyValuePair.Value;
|
||||||
|
if (attributeValueChunk == null)
|
||||||
if (matchingAttributes.Any())
|
|
||||||
{
|
{
|
||||||
// First attribute wins, even if there's duplicates.
|
// Minimized attributes are not valid for bound attributes. TagHelperBlockRewriter has already
|
||||||
var firstAttribute = matchingAttributes.First();
|
// logged an error if it was a bound attribute; so we can skip.
|
||||||
var attributeValueChunk = firstAttribute.Value;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Minimized attributes are not valid for bound attributes. There will be an error for the bound
|
// Find the matching TagHelperAttributeDescriptor.
|
||||||
// attribute logged by TagHelperBlockRewriter already so we can skip.
|
var attributeDescriptor = attributeDescriptors.FirstOrDefault(
|
||||||
if (attributeValueChunk == null)
|
descriptor => descriptor.IsNameMatch(attributeName));
|
||||||
|
if (attributeDescriptor == null)
|
||||||
|
{
|
||||||
|
// Attribute is not bound to a property or indexer in this tag helper.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We capture the tag helper's property value accessor so we can retrieve it later (if we need to).
|
||||||
|
var valueAccessor = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0}.{1}",
|
||||||
|
tagHelperVariableName,
|
||||||
|
attributeDescriptor.PropertyName);
|
||||||
|
|
||||||
|
if (attributeDescriptor.IsIndexer)
|
||||||
|
{
|
||||||
|
// Need a different valueAccessor in this case. But first need to throw a reasonable Exception at
|
||||||
|
// runtime if the property is null. The check is not required at design time.
|
||||||
|
if (!_designTimeMode && confirmedDictionaries.Add(attributeDescriptor.PropertyName))
|
||||||
|
{
|
||||||
|
_writer
|
||||||
|
.Write("if (")
|
||||||
|
.Write(valueAccessor)
|
||||||
|
.WriteLine(" == null)");
|
||||||
|
using (_writer.BuildScope())
|
||||||
|
{
|
||||||
|
// System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName
|
||||||
|
// of InvalidOperationException type.
|
||||||
|
_writer
|
||||||
|
.Write("throw ")
|
||||||
|
.WriteStartNewObject(nameof(InvalidOperationException))
|
||||||
|
.WriteStartMethodInvocation(_tagHelperContext.FormatInvalidIndexerAssignmentMethodName)
|
||||||
|
.WriteStringLiteral(attributeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStringLiteral(tagHelperTypeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStringLiteral(attributeDescriptor.PropertyName)
|
||||||
|
.WriteEndMethodInvocation(endLine: false) // End of method call
|
||||||
|
.WriteEndMethodInvocation(endLine: true); // End of new expression / throw statement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dictionaryKey = attributeName.Substring(attributeDescriptor.Name.Length);
|
||||||
|
valueAccessor = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0}.{1}[\"{2}\"]",
|
||||||
|
tagHelperVariableName,
|
||||||
|
attributeDescriptor.PropertyName,
|
||||||
|
dictionaryKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we haven't recorded this attribute value before then we need to record its value.
|
||||||
|
var attributeValueRecorded = htmlAttributeValues.ContainsKey(attributeName);
|
||||||
|
if (!attributeValueRecorded)
|
||||||
|
{
|
||||||
|
// We only need to create attribute values once per HTML element (not once per tag helper).
|
||||||
|
// We're saving the value accessor so we can retrieve it later if there are more tag
|
||||||
|
// helpers that need the value.
|
||||||
|
htmlAttributeValues.Add(attributeName, valueAccessor);
|
||||||
|
|
||||||
|
// Bufferable attributes are attributes that can have Razor code inside of them. Such
|
||||||
|
// attributes have string values and may be calculated using a temporary TextWriter or other
|
||||||
|
// buffer.
|
||||||
|
var bufferableAttribute = attributeDescriptor.IsStringProperty;
|
||||||
|
|
||||||
|
RenderNewAttributeValueAssignment(
|
||||||
|
attributeDescriptor,
|
||||||
|
bufferableAttribute,
|
||||||
|
attributeValueChunk,
|
||||||
|
valueAccessor);
|
||||||
|
|
||||||
|
// Execution contexts are a runtime feature.
|
||||||
|
if (_designTimeMode)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We capture the tag helpers property value accessor so we can retrieve it later (if we need to).
|
// We need to inform the context of the attribute value.
|
||||||
var valueAccessor = string.Format(
|
_writer
|
||||||
CultureInfo.InvariantCulture,
|
.WriteStartInstanceMethodInvocation(
|
||||||
"{0}.{1}",
|
ExecutionContextVariableName,
|
||||||
tagHelperVariableName,
|
_tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName)
|
||||||
attributeDescriptor.PropertyName);
|
.WriteStringLiteral(attributeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
// If we haven't recorded this attribute value before then we need to record its value.
|
.Write(valueAccessor)
|
||||||
var attributeValueRecorded = htmlAttributeValues.ContainsKey(attributeDescriptor.Name);
|
.WriteEndMethodInvocation();
|
||||||
if (!attributeValueRecorded)
|
}
|
||||||
{
|
else
|
||||||
// We only need to create attribute values once per HTML element (not once per tag helper).
|
{
|
||||||
// We're saving the value accessor so we can retrieve it later if there are more tag
|
// The attribute value has already been recorded, lets retrieve it from the stored value
|
||||||
// helpers that need the value.
|
// accessors.
|
||||||
htmlAttributeValues.Add(attributeDescriptor.Name, valueAccessor);
|
_writer
|
||||||
|
.WriteStartAssignment(valueAccessor)
|
||||||
// Bufferable attributes are attributes that can have Razor code inside of them. Such
|
.Write(htmlAttributeValues[attributeName])
|
||||||
// attributes have string values and may be calculated using a temporary TextWriter or other
|
.WriteLine(";");
|
||||||
// buffer.
|
|
||||||
var bufferableAttribute = attributeDescriptor.IsStringProperty;
|
|
||||||
|
|
||||||
RenderNewAttributeValueAssignment(
|
|
||||||
attributeDescriptor,
|
|
||||||
bufferableAttribute,
|
|
||||||
attributeValueChunk,
|
|
||||||
valueAccessor);
|
|
||||||
|
|
||||||
// Execution contexts are a runtime feature.
|
|
||||||
if (_designTimeMode)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to inform the context of the attribute value.
|
|
||||||
var attributeName = firstAttribute.Key;
|
|
||||||
_writer
|
|
||||||
.WriteStartInstanceMethodInvocation(
|
|
||||||
ExecutionContextVariableName,
|
|
||||||
_tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName)
|
|
||||||
.WriteStringLiteral(attributeName)
|
|
||||||
.WriteParameterSeparator()
|
|
||||||
.Write(valueAccessor)
|
|
||||||
.WriteEndMethodInvocation();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The attribute value has already been recorded, lets retrieve it from the stored value
|
|
||||||
// accessors.
|
|
||||||
_writer
|
|
||||||
.WriteStartAssignment(valueAccessor)
|
|
||||||
.Write(htmlAttributeValues[attributeDescriptor.Name])
|
|
||||||
.WriteLine(";");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -580,6 +625,26 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An IEqualityComparer for string -> Chunk mappings which compares only the Key.
|
||||||
|
private class KeyValuePairKeyComparer : IEqualityComparer<KeyValuePair<string, Chunk>>
|
||||||
|
{
|
||||||
|
public static KeyValuePairKeyComparer Default = new KeyValuePairKeyComparer();
|
||||||
|
|
||||||
|
private KeyValuePairKeyComparer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(KeyValuePair<string, Chunk> keyValuePairX, KeyValuePair<string, Chunk> keyValuePairY)
|
||||||
|
{
|
||||||
|
return string.Equals(keyValuePairX.Key, keyValuePairY.Key, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(KeyValuePair<string, Chunk> keyValuePair)
|
||||||
|
{
|
||||||
|
return keyValuePair.Key.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A CSharpCodeVisitor which does not HTML encode values. Used when rendering bound string attribute values.
|
// A CSharpCodeVisitor which does not HTML encode values. Used when rendering bound string attribute values.
|
||||||
private class CSharpLiteralCodeVisitor : CSharpCodeVisitor
|
private class CSharpLiteralCodeVisitor : CSharpCodeVisitor
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
ExecutionContextAddMinimizedHtmlAttributeMethodName = "AddMinimizedHtmlAttribute";
|
ExecutionContextAddMinimizedHtmlAttributeMethodName = "AddMinimizedHtmlAttribute";
|
||||||
ExecutionContextAddHtmlAttributeMethodName = "AddHtmlAttribute";
|
ExecutionContextAddHtmlAttributeMethodName = "AddHtmlAttribute";
|
||||||
ExecutionContextOutputPropertyName = "Output";
|
ExecutionContextOutputPropertyName = "Output";
|
||||||
|
FormatInvalidIndexerAssignmentMethodName = "FormatInvalidIndexerAssignment";
|
||||||
MarkAsHtmlEncodedMethodName = "Html.Raw";
|
MarkAsHtmlEncodedMethodName = "Html.Raw";
|
||||||
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope";
|
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope";
|
||||||
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope";
|
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope";
|
||||||
|
|
@ -78,6 +79,21 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ExecutionContextOutputPropertyName { get; set; }
|
public string ExecutionContextOutputPropertyName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to format an error message about using an indexer when the tag helper property
|
||||||
|
/// is <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Method signature should be
|
||||||
|
/// <code>
|
||||||
|
/// public string FormatInvalidIndexerAssignment(
|
||||||
|
/// string attributeName, // Name of the HTML attribute associated with the indexer.
|
||||||
|
/// string tagHelperTypeName, // Full name of the tag helper type.
|
||||||
|
/// string propertyName) // Dictionary property in the tag helper.
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
public string FormatInvalidIndexerAssignmentMethodName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the method used to wrap a <see cref="string"/> value and mark it as HTML-encoded.
|
/// The name of the method used to wrap a <see cref="string"/> value and mark it as HTML-encoded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. 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;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -65,16 +64,18 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
// Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
|
// Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
|
SourceLocation? errorLocation = null;
|
||||||
|
|
||||||
// Check if it's a bound attribute that is minimized or if it's a bound non-string attribute that
|
// Check if it's a bound attribute that is minimized or if it's a bound non-string attribute that
|
||||||
// is null or whitespace.
|
// is null or whitespace.
|
||||||
if ((result.IsBoundAttribute && result.AttributeValueNode == null) ||
|
if ((result.IsBoundAttribute && result.AttributeValueNode == null) ||
|
||||||
(result.IsBoundNonStringAttribute &&
|
(result.IsBoundNonStringAttribute &&
|
||||||
IsNullOrWhitespaceAttributeValue(result.AttributeValueNode)))
|
IsNullOrWhitespaceAttributeValue(result.AttributeValueNode)))
|
||||||
{
|
{
|
||||||
var errorLocation = GetAttributeNameStartLocation(child);
|
errorLocation = GetAttributeNameStartLocation(child);
|
||||||
|
|
||||||
errorSink.OnError(
|
errorSink.OnError(
|
||||||
errorLocation,
|
errorLocation.Value,
|
||||||
RazorResources.FormatRewriterError_EmptyTagHelperBoundAttribute(
|
RazorResources.FormatRewriterError_EmptyTagHelperBoundAttribute(
|
||||||
result.AttributeName,
|
result.AttributeName,
|
||||||
tagName,
|
tagName,
|
||||||
|
|
@ -82,6 +83,23 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
result.AttributeName.Length);
|
result.AttributeName.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the attribute was a prefix match for a tag helper dictionary property but the
|
||||||
|
// dictionary key would be the empty string.
|
||||||
|
if (result.IsMissingDictionaryKey)
|
||||||
|
{
|
||||||
|
if (!errorLocation.HasValue)
|
||||||
|
{
|
||||||
|
errorLocation = GetAttributeNameStartLocation(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorSink.OnError(
|
||||||
|
errorLocation.Value,
|
||||||
|
RazorResources.FormatTagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey(
|
||||||
|
result.AttributeName,
|
||||||
|
tagName),
|
||||||
|
result.AttributeName.Length);
|
||||||
|
}
|
||||||
|
|
||||||
attributes.Add(
|
attributes.Add(
|
||||||
new KeyValuePair<string, SyntaxTreeNode>(result.AttributeName, result.AttributeValueNode));
|
new KeyValuePair<string, SyntaxTreeNode>(result.AttributeName, result.AttributeValueNode));
|
||||||
}
|
}
|
||||||
|
|
@ -224,24 +242,19 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
errorSink.OnError(
|
errorSink.OnError(
|
||||||
span.Start,
|
span.Start,
|
||||||
RazorResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed,
|
RazorResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed,
|
||||||
span.Content.Length);
|
span.Content.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isBoundNonStringAttribute;
|
var result = CreateTryParseResult(name, descriptors);
|
||||||
var result = new TryParseResult
|
|
||||||
{
|
|
||||||
IsBoundAttribute = IsBoundAttribute(name, descriptors, out isBoundNonStringAttribute),
|
|
||||||
AttributeName = name,
|
|
||||||
};
|
|
||||||
result.IsBoundNonStringAttribute = isBoundNonStringAttribute;
|
|
||||||
|
|
||||||
// If we're not after an equal then we should treat the value as if it were a minimized attribute.
|
// If we're not after an equal then we should treat the value as if it were a minimized attribute.
|
||||||
var attributeValueBuilder = afterEquals ? builder : null;
|
var attributeValueBuilder = afterEquals ? builder : null;
|
||||||
result.AttributeValueNode = CreateMarkupAttribute(name, attributeValueBuilder, isBoundNonStringAttribute);
|
result.AttributeValueNode =
|
||||||
|
CreateMarkupAttribute(name, attributeValueBuilder, result.IsBoundNonStringAttribute);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -286,13 +299,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have a name now. Able to determine correct isBoundNonStringAttribute value.
|
// Have a name now. Able to determine correct isBoundNonStringAttribute value.
|
||||||
bool isBoundNonStringAttribute;
|
var result = CreateTryParseResult(name, descriptors);
|
||||||
var result = new TryParseResult
|
|
||||||
{
|
|
||||||
IsBoundAttribute = IsBoundAttribute(name, descriptors, out isBoundNonStringAttribute),
|
|
||||||
AttributeName = name,
|
|
||||||
};
|
|
||||||
result.IsBoundNonStringAttribute = isBoundNonStringAttribute;
|
|
||||||
|
|
||||||
// Remove first child i.e. foo="
|
// Remove first child i.e. foo="
|
||||||
builder.Children.RemoveAt(0);
|
builder.Children.RemoveAt(0);
|
||||||
|
|
@ -324,13 +331,13 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
if (block.Children.Count() == 1)
|
if (block.Children.Count() == 1)
|
||||||
{
|
{
|
||||||
var child = block.Children.First() as Span;
|
var child = block.Children.First() as Span;
|
||||||
|
|
||||||
if (child != null)
|
if (child != null)
|
||||||
{
|
{
|
||||||
// After pulling apart the block we just have a value span.
|
// After pulling apart the block we just have a value span.
|
||||||
var spanBuilder = new SpanBuilder(child);
|
var spanBuilder = new SpanBuilder(child);
|
||||||
|
|
||||||
result.AttributeValueNode = CreateMarkupAttribute(name, spanBuilder, isBoundNonStringAttribute);
|
result.AttributeValueNode =
|
||||||
|
CreateMarkupAttribute(name, spanBuilder, result.IsBoundNonStringAttribute);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -485,17 +492,23 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
return firstBoundAttribute?.TypeName;
|
return firstBoundAttribute?.TypeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determines whether an attribute with the given name is bound to a non-string tag helper property.
|
// Create a TryParseResult for given name, filling in binding details.
|
||||||
private static bool IsBoundAttribute(
|
private static TryParseResult CreateTryParseResult(string name, IEnumerable<TagHelperDescriptor> descriptors)
|
||||||
string name,
|
|
||||||
IEnumerable<TagHelperDescriptor> descriptors,
|
|
||||||
out bool isBoundNonStringAttribute)
|
|
||||||
{
|
{
|
||||||
var firstBoundAttribute = FindFirstBoundAttribute(name, descriptors);
|
var firstBoundAttribute = FindFirstBoundAttribute(name, descriptors);
|
||||||
var isBoundAttribute = firstBoundAttribute != null;
|
var isBoundAttribute = firstBoundAttribute != null;
|
||||||
isBoundNonStringAttribute = isBoundAttribute && !firstBoundAttribute.IsStringProperty;
|
var isBoundNonStringAttribute = isBoundAttribute && !firstBoundAttribute.IsStringProperty;
|
||||||
|
var isMissingDictionaryKey = isBoundAttribute &&
|
||||||
|
firstBoundAttribute.IsIndexer &&
|
||||||
|
name.Length == firstBoundAttribute.Name.Length;
|
||||||
|
|
||||||
return isBoundAttribute;
|
return new TryParseResult
|
||||||
|
{
|
||||||
|
AttributeName = name,
|
||||||
|
IsBoundAttribute = isBoundAttribute,
|
||||||
|
IsBoundNonStringAttribute = isBoundNonStringAttribute,
|
||||||
|
IsMissingDictionaryKey = isMissingDictionaryKey,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds first TagHelperAttributeDescriptor matching given name.
|
// Finds first TagHelperAttributeDescriptor matching given name.
|
||||||
|
|
@ -503,9 +516,13 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
string name,
|
string name,
|
||||||
IEnumerable<TagHelperDescriptor> descriptors)
|
IEnumerable<TagHelperDescriptor> descriptors)
|
||||||
{
|
{
|
||||||
return descriptors
|
// Non-indexers (exact HTML attribute name matches) have higher precedence than indexers (prefix matches).
|
||||||
|
// Attributes already sorted to ensure this precedence.
|
||||||
|
var firstBoundAttribute = descriptors
|
||||||
.SelectMany(descriptor => descriptor.Attributes)
|
.SelectMany(descriptor => descriptor.Attributes)
|
||||||
.FirstOrDefault(attribute => string.Equals(attribute.Name, name, StringComparison.OrdinalIgnoreCase));
|
.FirstOrDefault(attributeDescriptor => attributeDescriptor.IsNameMatch(name));
|
||||||
|
|
||||||
|
return firstBoundAttribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsQuote(HtmlSymbol htmlSymbol)
|
private static bool IsQuote(HtmlSymbol htmlSymbol)
|
||||||
|
|
@ -523,6 +540,8 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
public bool IsBoundAttribute { get; set; }
|
public bool IsBoundAttribute { get; set; }
|
||||||
|
|
||||||
public bool IsBoundNonStringAttribute { get; set; }
|
public bool IsBoundNonStringAttribute { get; set; }
|
||||||
|
|
||||||
|
public bool IsMissingDictionaryKey { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1367,19 +1367,35 @@ namespace Microsoft.AspNet.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TagHelper attributes must be welformed.
|
/// TagHelper attributes must be well-formed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed
|
internal static string TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed
|
||||||
{
|
{
|
||||||
get { return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed"); }
|
get { return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TagHelper attributes must be welformed.
|
/// TagHelper attributes must be well-formed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatTagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed()
|
internal static string FormatTagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed()
|
||||||
{
|
{
|
||||||
return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed");
|
return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tag helper attribute '{0}' in element '{1}' is missing a key. The syntax is '<{1} {0}{{ key }}="value">'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tag helper attribute '{0}' in element '{1}' is missing a key. The syntax is '<{1} {0}{{ key }}="value">'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
|
|
@ -26,36 +26,36 @@
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -390,8 +390,11 @@ Instead, wrap the contents of the block in "{{}}":
|
||||||
<data name="TagHelpersParseTreeRewriter_MissingCloseAngle" xml:space="preserve">
|
<data name="TagHelpersParseTreeRewriter_MissingCloseAngle" xml:space="preserve">
|
||||||
<value>Missing close angle for tag helper '{0}'.</value>
|
<value>Missing close angle for tag helper '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed" xml:space="preserve">
|
<data name="TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed" xml:space="preserve">
|
||||||
<value>TagHelper attributes must be welformed.</value>
|
<value>TagHelper attributes must be well-formed.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey" xml:space="preserve">
|
||||||
|
<value>The tag helper attribute '{0}' in element '{1}' is missing a key. The syntax is '<{1} {0}{{ key }}="value">'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TagHelpers_AttributeExpressionRequired" xml:space="preserve">
|
<data name="TagHelpers_AttributeExpressionRequired" xml:space="preserve">
|
||||||
<value>Non-string tag helper attribute values must not be empty. Add an expression to this attribute value.</value>
|
<value>Non-string tag helper attribute values must not be empty. Add an expression to this attribute value.</value>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
name,
|
name,
|
||||||
propertyInfo.Name,
|
propertyInfo.Name,
|
||||||
propertyInfo.PropertyType.FullName,
|
propertyInfo.PropertyType.FullName,
|
||||||
|
isIndexer: false,
|
||||||
isStringProperty: propertyInfo.PropertyType == typeof(string))
|
isStringProperty: propertyInfo.PropertyType == typeof(string))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -25,48 +26,77 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instantiates a new instance of the <see cref="TagHelperAttributeDescriptor"/> class.
|
/// Instantiates a new instance of the <see cref="TagHelperAttributeDescriptor"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The HTML attribute name.</param>
|
/// <param name="name">
|
||||||
|
/// The HTML attribute name or, if <paramref name="isIndexer"/> is <c>true</c>, the prefix for matching
|
||||||
|
/// attribute names.
|
||||||
|
/// </param>
|
||||||
/// <param name="propertyName">The name of the CLR property that corresponds to the HTML attribute.</param>
|
/// <param name="propertyName">The name of the CLR property that corresponds to the HTML attribute.</param>
|
||||||
/// <param name="typeName">
|
/// <param name="typeName">
|
||||||
/// The full name of the named (see <paramref name="propertyName"/>) property's <see cref="System.Type"/>.
|
/// The full name of the named (see <paramref name="propertyName"/>) property's <see cref="Type"/> or,
|
||||||
|
/// if <paramref name="isIndexer"/> is <c>true</c>, the full name of the indexer's value <see cref="Type"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="isIndexer">
|
||||||
|
/// If <c>true</c> this <see cref="TagHelperAttributeDescriptor"/> is used for dictionary indexer assignments.
|
||||||
|
/// Otherwise this <see cref="TagHelperAttributeDescriptor"/> is used for property assignment.
|
||||||
|
/// </param>
|
||||||
|
/// <remarks>
|
||||||
|
/// HTML attribute names are matched case-insensitively, regardless of <paramref name="isIndexer"/>.
|
||||||
|
/// </remarks>
|
||||||
public TagHelperAttributeDescriptor(
|
public TagHelperAttributeDescriptor(
|
||||||
[NotNull] string name,
|
[NotNull] string name,
|
||||||
[NotNull] string propertyName,
|
[NotNull] string propertyName,
|
||||||
[NotNull] string typeName)
|
[NotNull] string typeName,
|
||||||
|
bool isIndexer)
|
||||||
: this(
|
: this(
|
||||||
name,
|
name,
|
||||||
propertyName,
|
propertyName,
|
||||||
typeName,
|
typeName,
|
||||||
|
isIndexer,
|
||||||
isStringProperty: string.Equals(typeName, typeof(string).FullName, StringComparison.Ordinal))
|
isStringProperty: string.Equals(typeName, typeof(string).FullName, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing i.e. for confirming above constructor sets `IsStringProperty` as expected.
|
// Internal for testing i.e. for confirming above constructor sets IsStringProperty as expected.
|
||||||
internal TagHelperAttributeDescriptor(
|
internal TagHelperAttributeDescriptor(
|
||||||
[NotNull] string name,
|
[NotNull] string name,
|
||||||
[NotNull] string propertyName,
|
[NotNull] string propertyName,
|
||||||
[NotNull] string typeName,
|
[NotNull] string typeName,
|
||||||
|
bool isIndexer,
|
||||||
bool isStringProperty)
|
bool isStringProperty)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
PropertyName = propertyName;
|
PropertyName = propertyName;
|
||||||
TypeName = typeName;
|
TypeName = typeName;
|
||||||
|
IsIndexer = isIndexer;
|
||||||
IsStringProperty = isStringProperty;
|
IsStringProperty = isStringProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an indication whether this property is of type <see cref="string"/>.
|
/// Gets an indication whether this <see cref="TagHelperAttributeDescriptor"/> is used for dictionary indexer
|
||||||
|
/// assignments.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// If <c>true</c> this <see cref="TagHelperAttributeDescriptor"/> should be associated with all HTML
|
||||||
|
/// attributes that have names starting with <see cref="Name"/>. Otherwise this
|
||||||
|
/// <see cref="TagHelperAttributeDescriptor"/> is used for property assignment and is only associated with an
|
||||||
|
/// HTML attribute that has the exact <see cref="Name"/>.
|
||||||
|
/// </value>
|
||||||
|
public bool IsIndexer { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an indication whether this property is of type <see cref="string"/> or, if <see cref="IsIndexer"/> is
|
||||||
|
/// <c>true</c>, whether the indexer's value is of type <see cref="string"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>
|
/// <value>
|
||||||
/// If <c>true</c> the <see cref="TypeName"/> is for <see cref="string"/>. This causes the Razor parser
|
/// If <c>true</c> the <see cref="TypeName"/> is for <see cref="string"/>. This causes the Razor parser
|
||||||
/// to allow empty values for attributes that have names matching <see cref="Name"/>. If <c>false</c>
|
/// to allow empty values for HTML attributes matching this <see cref="TagHelperAttributeDescriptor"/>. If
|
||||||
/// empty values for such matching attributes lead to errors.
|
/// <c>false</c> empty values for such matching attributes lead to errors.
|
||||||
/// </value>
|
/// </value>
|
||||||
public bool IsStringProperty { get; }
|
public bool IsStringProperty { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The HTML attribute name.
|
/// The HTML attribute name or, if <see cref="IsIndexer"/> is <c>true</c>, the prefix for matching attribute
|
||||||
|
/// names.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
|
|
@ -76,8 +106,30 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
public string PropertyName { get; }
|
public string PropertyName { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The full name of the named (see <see name="PropertyName"/>) property's <see cref="System.Type"/>.
|
/// The full name of the named (see <see name="PropertyName"/>) property's <see cref="Type"/> or, if
|
||||||
|
/// <see cref="IsIndexer"/> is <c>true</c>, the full name of the indexer's value <see cref="Type"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TypeName { get; }
|
public string TypeName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether HTML attribute <paramref name="name"/> matches this
|
||||||
|
/// <see cref="TagHelperAttributeDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name of the HTML attribute to check.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if this <see cref="TagHelperAttributeDescriptor"/> matches <paramref name="name"/>.
|
||||||
|
/// <c>false</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public bool IsNameMatch(string name)
|
||||||
|
{
|
||||||
|
if (IsIndexer)
|
||||||
|
{
|
||||||
|
return name.StartsWith(Name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return string.Equals(name, Name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,9 +29,11 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Determines equality based on <see cref="TagHelperAttributeDescriptor.Name"/>,
|
/// Determines equality based on <see cref="TagHelperAttributeDescriptor.IsIndexer"/>,
|
||||||
/// <see cref="TagHelperAttributeDescriptor.PropertyName"/>,
|
/// <see cref="TagHelperAttributeDescriptor.Name"/>, <see cref="TagHelperAttributeDescriptor.PropertyName"/>,
|
||||||
/// and <see cref="TagHelperAttributeDescriptor.TypeName"/>.
|
/// and <see cref="TagHelperAttributeDescriptor.TypeName"/>. Ignores
|
||||||
|
/// <see cref="TagHelperAttributeDescriptor.IsStringProperty"/> because it can be inferred directly from
|
||||||
|
/// <see cref="TagHelperAttributeDescriptor.TypeName"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public virtual bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY)
|
public virtual bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY)
|
||||||
{
|
{
|
||||||
|
|
@ -40,7 +42,11 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check Name and TypeName though each property in a particular tag helper has at most two
|
||||||
|
// TagHelperAttributeDescriptors (one for the indexer and one not). May be comparing attributes between
|
||||||
|
// tag helpers and should be as specific as we can.
|
||||||
return descriptorX != null &&
|
return descriptorX != null &&
|
||||||
|
descriptorX.IsIndexer == descriptorY.IsIndexer &&
|
||||||
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase) &&
|
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase) &&
|
||||||
string.Equals(descriptorX.PropertyName, descriptorY.PropertyName, StringComparison.Ordinal) &&
|
string.Equals(descriptorX.PropertyName, descriptorY.PropertyName, StringComparison.Ordinal) &&
|
||||||
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal);
|
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal);
|
||||||
|
|
@ -49,7 +55,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual int GetHashCode([NotNull] TagHelperAttributeDescriptor descriptor)
|
public virtual int GetHashCode([NotNull] TagHelperAttributeDescriptor descriptor)
|
||||||
{
|
{
|
||||||
|
// Rarely if ever hash TagHelperAttributeDescriptor. If we do, include the Name and TypeName since context
|
||||||
|
// information is not available in the hash.
|
||||||
return HashCodeCombiner.Start()
|
return HashCodeCombiner.Start()
|
||||||
|
.Add(descriptor.IsIndexer)
|
||||||
.Add(descriptor.Name, StringComparer.OrdinalIgnoreCase)
|
.Add(descriptor.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
.Add(descriptor.PropertyName, StringComparer.Ordinal)
|
.Add(descriptor.PropertyName, StringComparer.Ordinal)
|
||||||
.Add(descriptor.TypeName, StringComparer.Ordinal)
|
.Add(descriptor.TypeName, StringComparer.Ordinal)
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal comparer doesn't care about case, in tests we do. Also double-check IsStringProperty though
|
// Base comparer does not care about Name case but in tests we do. Also double-check IsStringProperty
|
||||||
// it is inferred from TypeName.
|
// though it is inferred from TypeName.
|
||||||
return base.Equals(descriptorX, descriptorY) &&
|
return base.Equals(descriptorX, descriptorY) &&
|
||||||
descriptorX.IsStringProperty == descriptorY.IsStringProperty &&
|
descriptorX.IsStringProperty == descriptorY.IsStringProperty &&
|
||||||
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal);
|
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal);
|
||||||
|
|
@ -33,8 +33,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
public override int GetHashCode(TagHelperAttributeDescriptor descriptor)
|
public override int GetHashCode(TagHelperAttributeDescriptor descriptor)
|
||||||
{
|
{
|
||||||
// Rarely if ever hash TagHelperAttributeDescriptor. If we do, ignore IsStringProperty since it should
|
// Ignore IsStringProperty because it is directly inferred from TypeName and thus won't vary the hash
|
||||||
// not vary for a given TypeName i.e. will not change the bucket.
|
// bucket. Base comparer does not care about Name case in its hash code but in tests we do.
|
||||||
return HashCodeCombiner.Start()
|
return HashCodeCombiner.Start()
|
||||||
.Add(base.GetHashCode(descriptor))
|
.Add(base.GetHashCode(descriptor))
|
||||||
.Add(descriptor.Name, StringComparer.Ordinal)
|
.Add(descriptor.Name, StringComparer.Ordinal)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -34,7 +34,8 @@ namespace Microsoft.AspNet.Razor.Test.Generator
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
"catchall-bound-string",
|
"catchall-bound-string",
|
||||||
"BoundRequiredString",
|
"BoundRequiredString",
|
||||||
typeof(string).FullName),
|
typeof(string).FullName,
|
||||||
|
isIndexer: false),
|
||||||
},
|
},
|
||||||
requiredAttributes: new[] { "catchall-unbound-required" }),
|
requiredAttributes: new[] { "catchall-unbound-required" }),
|
||||||
new TagHelperDescriptor(
|
new TagHelperDescriptor(
|
||||||
|
|
@ -46,11 +47,13 @@ namespace Microsoft.AspNet.Razor.Test.Generator
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
"input-bound-required-string",
|
"input-bound-required-string",
|
||||||
"BoundRequiredString",
|
"BoundRequiredString",
|
||||||
typeof(string).FullName),
|
typeof(string).FullName,
|
||||||
|
isIndexer: false),
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
"input-bound-string",
|
"input-bound-string",
|
||||||
"BoundString",
|
"BoundString",
|
||||||
typeof(string).FullName)
|
typeof(string).FullName,
|
||||||
|
isIndexer: false)
|
||||||
},
|
},
|
||||||
requiredAttributes: new[] { "input-bound-required-string", "input-unbound-required" }),
|
requiredAttributes: new[] { "input-bound-required-string", "input-unbound-required" }),
|
||||||
};
|
};
|
||||||
|
|
@ -152,6 +155,80 @@ namespace Microsoft.AspNet.Razor.Test.Generator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<TagHelperDescriptor> PrefixedAttributeTagHelperDescriptors
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new TagHelperDescriptor(
|
||||||
|
tagName: "input",
|
||||||
|
typeName: "InputTagHelper1",
|
||||||
|
assemblyName: "SomeAssembly",
|
||||||
|
attributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "int-prefix-grabber",
|
||||||
|
propertyName: "IntProperty",
|
||||||
|
typeName: typeof(int).FullName,
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "int-dictionary",
|
||||||
|
propertyName: "IntDictionaryProperty",
|
||||||
|
typeName: typeof(IDictionary<string, int>).FullName,
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "string-dictionary",
|
||||||
|
propertyName: "StringDictionaryProperty",
|
||||||
|
typeName: "Namespace.DictionaryWithoutParameterlessConstructor<string, string>",
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "string-prefix-grabber",
|
||||||
|
propertyName: "StringProperty",
|
||||||
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "int-prefix-",
|
||||||
|
propertyName: "IntDictionaryProperty",
|
||||||
|
typeName: typeof(int).FullName,
|
||||||
|
isIndexer: true),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "string-prefix-",
|
||||||
|
propertyName: "StringDictionaryProperty",
|
||||||
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: true),
|
||||||
|
}),
|
||||||
|
new TagHelperDescriptor(
|
||||||
|
tagName: "input",
|
||||||
|
typeName: "InputTagHelper2",
|
||||||
|
assemblyName: "SomeAssembly",
|
||||||
|
attributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "int-dictionary",
|
||||||
|
propertyName: "IntDictionaryProperty",
|
||||||
|
typeName: typeof(IDictionary<string, int>).FullName,
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "string-dictionary",
|
||||||
|
propertyName: "StringDictionaryProperty",
|
||||||
|
typeName: "Namespace.DictionaryWithoutParameterlessConstructor<string, string>",
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "int-prefix-",
|
||||||
|
propertyName: "IntDictionaryProperty",
|
||||||
|
typeName: typeof(int).FullName,
|
||||||
|
isIndexer: true),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "string-prefix-",
|
||||||
|
propertyName: "StringDictionaryProperty",
|
||||||
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: true),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static TheoryData TagHelperDescriptorFlowTestData
|
public static TheoryData TagHelperDescriptorFlowTestData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -498,6 +575,101 @@ namespace Microsoft.AspNet.Razor.Test.Generator
|
||||||
contentLength: 4)
|
contentLength: 4)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"PrefixedAttributeTagHelpers",
|
||||||
|
"PrefixedAttributeTagHelpers.DesignTime",
|
||||||
|
PrefixedAttributeTagHelperDescriptors,
|
||||||
|
new List<LineMapping>
|
||||||
|
{
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 14,
|
||||||
|
documentLineIndex: 0,
|
||||||
|
generatedAbsoluteIndex: 499,
|
||||||
|
generatedLineIndex: 15,
|
||||||
|
characterOffsetIndex: 14,
|
||||||
|
contentLength: 17),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 37,
|
||||||
|
documentLineIndex: 2,
|
||||||
|
generatedAbsoluteIndex: 996,
|
||||||
|
generatedLineIndex: 34,
|
||||||
|
characterOffsetIndex: 2,
|
||||||
|
contentLength: 242),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 370,
|
||||||
|
documentLineIndex: 15,
|
||||||
|
generatedAbsoluteIndex: 1430,
|
||||||
|
generatedLineIndex: 50,
|
||||||
|
characterOffsetIndex: 43,
|
||||||
|
contentLength: 13),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 404,
|
||||||
|
documentLineIndex: 15,
|
||||||
|
generatedAbsoluteIndex: 1601,
|
||||||
|
generatedLineIndex: 55,
|
||||||
|
characterOffsetIndex: 77,
|
||||||
|
contentLength: 16),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 468,
|
||||||
|
documentLineIndex: 16,
|
||||||
|
generatedAbsoluteIndex: 2077,
|
||||||
|
generatedLineIndex: 64,
|
||||||
|
characterOffsetIndex: 43,
|
||||||
|
contentLength: 13),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 502,
|
||||||
|
documentLineIndex: 16,
|
||||||
|
generatedAbsoluteIndex: 2248,
|
||||||
|
generatedLineIndex: 69,
|
||||||
|
characterOffsetIndex: 77,
|
||||||
|
contentLength: 2),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 526,
|
||||||
|
documentLineIndex: 16,
|
||||||
|
generatedAbsoluteIndex: 2432,
|
||||||
|
generatedLineIndex: 74,
|
||||||
|
characterOffsetIndex: 101,
|
||||||
|
contentLength: 2),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 590,
|
||||||
|
documentLineIndex: 18,
|
||||||
|
documentCharacterOffsetIndex: 31,
|
||||||
|
generatedAbsoluteIndex: 2994,
|
||||||
|
generatedLineIndex: 84,
|
||||||
|
generatedCharacterOffsetIndex: 32,
|
||||||
|
contentLength: 2),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 611,
|
||||||
|
documentLineIndex: 18,
|
||||||
|
generatedAbsoluteIndex: 3129,
|
||||||
|
generatedLineIndex: 89,
|
||||||
|
characterOffsetIndex: 52,
|
||||||
|
contentLength: 2),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 634,
|
||||||
|
documentLineIndex: 18,
|
||||||
|
generatedAbsoluteIndex: 3287,
|
||||||
|
generatedLineIndex: 94,
|
||||||
|
characterOffsetIndex: 75,
|
||||||
|
contentLength: 2),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 783,
|
||||||
|
documentLineIndex: 20,
|
||||||
|
documentCharacterOffsetIndex: 42,
|
||||||
|
generatedAbsoluteIndex: 3521,
|
||||||
|
generatedLineIndex: 101,
|
||||||
|
generatedCharacterOffsetIndex: 6,
|
||||||
|
contentLength: 8),
|
||||||
|
BuildLineMapping(
|
||||||
|
documentAbsoluteIndex: 826,
|
||||||
|
documentLineIndex: 21,
|
||||||
|
documentCharacterOffsetIndex: 29,
|
||||||
|
generatedAbsoluteIndex: 4552,
|
||||||
|
generatedLineIndex: 115,
|
||||||
|
generatedCharacterOffsetIndex: 51,
|
||||||
|
contentLength: 2),
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -523,28 +695,36 @@ namespace Microsoft.AspNet.Razor.Test.Generator
|
||||||
{
|
{
|
||||||
// Test resource name, expected TagHelperDescriptors
|
// Test resource name, expected TagHelperDescriptors
|
||||||
// Note: The baseline resource name is equivalent to the test resource name.
|
// Note: The baseline resource name is equivalent to the test resource name.
|
||||||
return new TheoryData<string, IEnumerable<TagHelperDescriptor>>
|
return new TheoryData<string, string, IEnumerable<TagHelperDescriptor>>
|
||||||
{
|
{
|
||||||
{ "SingleTagHelper", DefaultPAndInputTagHelperDescriptors },
|
{ "SingleTagHelper", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
{ "BasicTagHelpers", DefaultPAndInputTagHelperDescriptors },
|
{ "BasicTagHelpers", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
{ "BasicTagHelpers.RemoveTagHelper", DefaultPAndInputTagHelperDescriptors },
|
{ "BasicTagHelpers.RemoveTagHelper", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
{ "BasicTagHelpers.Prefixed", PrefixedPAndInputTagHelperDescriptors },
|
{ "BasicTagHelpers.Prefixed", null, PrefixedPAndInputTagHelperDescriptors },
|
||||||
{ "ComplexTagHelpers", DefaultPAndInputTagHelperDescriptors },
|
{ "ComplexTagHelpers", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
{ "DuplicateTargetTagHelper", DuplicateTargetTagHelperDescriptors },
|
{ "DuplicateTargetTagHelper", null, DuplicateTargetTagHelperDescriptors },
|
||||||
{ "EmptyAttributeTagHelpers", DefaultPAndInputTagHelperDescriptors },
|
{ "EmptyAttributeTagHelpers", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
{ "EscapedTagHelpers", DefaultPAndInputTagHelperDescriptors },
|
{ "EscapedTagHelpers", null, DefaultPAndInputTagHelperDescriptors },
|
||||||
{ "AttributeTargetingTagHelpers", AttributeTargetingTagHelperDescriptors },
|
{ "AttributeTargetingTagHelpers", null, AttributeTargetingTagHelperDescriptors },
|
||||||
|
{ "PrefixedAttributeTagHelpers", null, PrefixedAttributeTagHelperDescriptors },
|
||||||
|
{
|
||||||
|
"PrefixedAttributeTagHelpers",
|
||||||
|
"PrefixedAttributeTagHelpers.Reversed",
|
||||||
|
PrefixedAttributeTagHelperDescriptors.Reverse()
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RuntimeTimeTagHelperTestData))]
|
[MemberData(nameof(RuntimeTimeTagHelperTestData))]
|
||||||
public void TagHelpers_GenerateExpectedRuntimeOutput(string testName,
|
public void TagHelpers_GenerateExpectedRuntimeOutput(
|
||||||
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
string testName,
|
||||||
|
string baseLineName,
|
||||||
|
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||||
{
|
{
|
||||||
// Act & Assert
|
// Arrange & Act & Assert
|
||||||
RunTagHelperTest(testName, tagHelperDescriptors: tagHelperDescriptors);
|
RunTagHelperTest(testName, baseLineName, tagHelperDescriptors: tagHelperDescriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var noErrors = new RazorError[0];
|
var noErrors = new RazorError[0];
|
||||||
var errorFormat = "Attribute '{0}' on tag helper element '{1}' requires a value. Tag helper bound " +
|
var errorFormat = "Attribute '{0}' on tag helper element '{1}' requires a value. Tag helper bound " +
|
||||||
"attributes of type '{2}' cannot be empty or contain only whitespace.";
|
"attributes of type '{2}' cannot be empty or contain only whitespace.";
|
||||||
|
var emptyKeyFormat = "The tag helper attribute '{0}' in element '{1}' is missing a key. The " +
|
||||||
|
"syntax is '<{1} {0}{{ key }}=\"value\">'.";
|
||||||
var stringType = typeof(string).FullName;
|
var stringType = typeof(string).FullName;
|
||||||
var intType = typeof(int).FullName;
|
var intType = typeof(int).FullName;
|
||||||
var expressionString = "@DateTime.Now + 1";
|
var expressionString = "@DateTime.Now + 1";
|
||||||
|
|
@ -269,6 +271,196 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
})),
|
})),
|
||||||
new[] { new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 3, 0, 3, 9) }
|
new[] { new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 3, 0, 3, 9) }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"<input int-dictionary/>",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("int-dictionary", null),
|
||||||
|
})),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new RazorError(
|
||||||
|
string.Format(errorFormat, "int-dictionary", "input", typeof(IDictionary<string, int>).FullName),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 14),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input string-dictionary />",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("string-dictionary", null),
|
||||||
|
})),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new RazorError(
|
||||||
|
string.Format(errorFormat, "string-dictionary", "input", typeof(IDictionary<string, string>).FullName),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 17),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input int-prefix- />",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("int-prefix-", null),
|
||||||
|
})),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new RazorError(
|
||||||
|
string.Format(errorFormat, "int-prefix-", "input", typeof(int).FullName),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 11),
|
||||||
|
new RazorError(
|
||||||
|
string.Format(emptyKeyFormat, "int-prefix-", "input"),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 11),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input string-prefix-/>",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("string-prefix-", null),
|
||||||
|
})),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new RazorError(
|
||||||
|
string.Format(errorFormat, "string-prefix-", "input", typeof(string).FullName),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 14),
|
||||||
|
new RazorError(
|
||||||
|
string.Format(emptyKeyFormat, "string-prefix-", "input"),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 14),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input int-prefix-value/>",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("int-prefix-value", null),
|
||||||
|
})),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new RazorError(
|
||||||
|
string.Format(errorFormat, "int-prefix-value", "input", typeof(int).FullName),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 16),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input string-prefix-value />",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("string-prefix-value", null),
|
||||||
|
})),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new RazorError(
|
||||||
|
string.Format(errorFormat, "string-prefix-value", "input", typeof(string).FullName),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 19),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input int-prefix-value='' />",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("int-prefix-value", new MarkupBlock()),
|
||||||
|
})),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new RazorError(
|
||||||
|
string.Format(errorFormat, "int-prefix-value", "input", typeof(int).FullName),
|
||||||
|
absoluteIndex: 7,
|
||||||
|
lineIndex: 0,
|
||||||
|
columnIndex: 7,
|
||||||
|
length: 16),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input string-prefix-value=''/>",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("string-prefix-value", new MarkupBlock()),
|
||||||
|
})),
|
||||||
|
new RazorError[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input int-prefix-value='3'/>",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("int-prefix-value", factory.CodeMarkup("3")),
|
||||||
|
})),
|
||||||
|
new RazorError[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"<input string-prefix-value='some string' />",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"input",
|
||||||
|
selfClosing: true,
|
||||||
|
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, SyntaxTreeNode>("string-prefix-value", new MarkupBlock(
|
||||||
|
factory.Markup("some"),
|
||||||
|
factory.Markup(" string"))),
|
||||||
|
})),
|
||||||
|
new RazorError[0]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"<input unbound-required bound-required-string />",
|
"<input unbound-required bound-required-string />",
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
|
|
@ -878,7 +1070,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
"bound-required-string",
|
"bound-required-string",
|
||||||
"BoundRequiredString",
|
"BoundRequiredString",
|
||||||
typeof(string).FullName)
|
typeof(string).FullName,
|
||||||
|
isIndexer: false)
|
||||||
},
|
},
|
||||||
requiredAttributes: new[] { "unbound-required" }),
|
requiredAttributes: new[] { "unbound-required" }),
|
||||||
new TagHelperDescriptor(
|
new TagHelperDescriptor(
|
||||||
|
|
@ -890,7 +1083,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
"bound-required-string",
|
"bound-required-string",
|
||||||
"BoundRequiredString",
|
"BoundRequiredString",
|
||||||
typeof(string).FullName)
|
typeof(string).FullName,
|
||||||
|
isIndexer: false)
|
||||||
},
|
},
|
||||||
requiredAttributes: new[] { "bound-required-string" }),
|
requiredAttributes: new[] { "bound-required-string" }),
|
||||||
new TagHelperDescriptor(
|
new TagHelperDescriptor(
|
||||||
|
|
@ -902,9 +1096,38 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
"bound-required-int",
|
"bound-required-int",
|
||||||
"BoundRequiredInt",
|
"BoundRequiredInt",
|
||||||
typeof(int).FullName)
|
typeof(int).FullName,
|
||||||
|
isIndexer: false)
|
||||||
},
|
},
|
||||||
requiredAttributes: new[] { "bound-required-int" }),
|
requiredAttributes: new[] { "bound-required-int" }),
|
||||||
|
new TagHelperDescriptor(
|
||||||
|
tagName: "input",
|
||||||
|
typeName: "InputTagHelper3",
|
||||||
|
assemblyName: "SomeAssembly",
|
||||||
|
attributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
"int-dictionary",
|
||||||
|
"DictionaryOfIntProperty",
|
||||||
|
typeof(IDictionary<string, int>).FullName,
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
"string-dictionary",
|
||||||
|
"DictionaryOfStringProperty",
|
||||||
|
typeof(IDictionary<string, string>).FullName,
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
"int-prefix-",
|
||||||
|
"DictionaryOfIntProperty",
|
||||||
|
typeof(int).FullName,
|
||||||
|
isIndexer: true),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
"string-prefix-",
|
||||||
|
"DictionaryOfStringProperty",
|
||||||
|
typeof(string).FullName,
|
||||||
|
isIndexer: true),
|
||||||
|
},
|
||||||
|
requiredAttributes: Enumerable.Empty<string>()),
|
||||||
new TagHelperDescriptor(
|
new TagHelperDescriptor(
|
||||||
tagName: "p",
|
tagName: "p",
|
||||||
typeName: "PTagHelper",
|
typeName: "PTagHelper",
|
||||||
|
|
@ -914,11 +1137,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
"bound-string",
|
"bound-string",
|
||||||
"BoundRequiredString",
|
"BoundRequiredString",
|
||||||
typeof(string).FullName),
|
typeof(string).FullName,
|
||||||
|
isIndexer: false),
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
"bound-int",
|
"bound-int",
|
||||||
"BoundRequiredString",
|
"BoundRequiredString",
|
||||||
typeof(int).FullName)
|
typeof(int).FullName,
|
||||||
|
isIndexer: false)
|
||||||
},
|
},
|
||||||
requiredAttributes: Enumerable.Empty<string>()),
|
requiredAttributes: Enumerable.Empty<string>()),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "attribute one",
|
name: "attribute one",
|
||||||
propertyName: "property name",
|
propertyName: "property name",
|
||||||
typeName: "property type name"),
|
typeName: "property type name",
|
||||||
|
isIndexer: false),
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "attribute two",
|
name: "attribute two",
|
||||||
propertyName: "property name",
|
propertyName: "property name",
|
||||||
typeName: typeof(string).FullName),
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: false),
|
||||||
},
|
},
|
||||||
requiredAttributes: Enumerable.Empty<string>());
|
requiredAttributes: Enumerable.Empty<string>());
|
||||||
var expectedSerializedDescriptor =
|
var expectedSerializedDescriptor =
|
||||||
|
|
@ -66,11 +68,62 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
$"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," +
|
$"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" +
|
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" +
|
||||||
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," +
|
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":false," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"}}," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"}}," +
|
||||||
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," +
|
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":false," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"}}]," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]}}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var serializedDescriptor = JsonConvert.SerializeObject(descriptor);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedSerializedDescriptor, serializedDescriptor, StringComparer.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TagHelperDescriptor_WithIndexerAttributes_CanBeSerialized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new TagHelperDescriptor(
|
||||||
|
prefix: "prefix:",
|
||||||
|
tagName: "tag name",
|
||||||
|
typeName: "type name",
|
||||||
|
assemblyName: "assembly name",
|
||||||
|
attributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "attribute one",
|
||||||
|
propertyName: "property name",
|
||||||
|
typeName: "property type name",
|
||||||
|
isIndexer: true),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "attribute two",
|
||||||
|
propertyName: "property name",
|
||||||
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: true),
|
||||||
|
},
|
||||||
|
requiredAttributes: Enumerable.Empty<string>());
|
||||||
|
var expectedSerializedDescriptor =
|
||||||
|
$"{{\"{ nameof(TagHelperDescriptor.Prefix) }\":\"prefix:\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.TagName) }\":\"tag name\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.FullTagName) }\":\"prefix:tag name\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" +
|
||||||
|
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":true," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"}}," +
|
||||||
|
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":true," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"}}]," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"}}]," +
|
||||||
|
|
@ -129,11 +182,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
$"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," +
|
$"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" +
|
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" +
|
||||||
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," +
|
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":false," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"}}," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"}}," +
|
||||||
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," +
|
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":false," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"}}]," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"}}]," +
|
||||||
|
|
@ -148,11 +203,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "attribute one",
|
name: "attribute one",
|
||||||
propertyName: "property name",
|
propertyName: "property name",
|
||||||
typeName: "property type name"),
|
typeName: "property type name",
|
||||||
|
isIndexer: false),
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "attribute two",
|
name: "attribute two",
|
||||||
propertyName: "property name",
|
propertyName: "property name",
|
||||||
typeName: typeof(string).FullName),
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: false),
|
||||||
},
|
},
|
||||||
requiredAttributes: Enumerable.Empty<string>());
|
requiredAttributes: Enumerable.Empty<string>());
|
||||||
|
|
||||||
|
|
@ -166,7 +223,9 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
Assert.Equal(expectedDescriptor.FullTagName, descriptor.FullTagName, StringComparer.Ordinal);
|
Assert.Equal(expectedDescriptor.FullTagName, descriptor.FullTagName, StringComparer.Ordinal);
|
||||||
Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal);
|
Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal);
|
||||||
Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal);
|
Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal);
|
||||||
|
|
||||||
Assert.Equal(2, descriptor.Attributes.Count);
|
Assert.Equal(2, descriptor.Attributes.Count);
|
||||||
|
Assert.Equal(expectedDescriptor.Attributes[0].IsIndexer, descriptor.Attributes[0].IsIndexer);
|
||||||
Assert.Equal(expectedDescriptor.Attributes[0].IsStringProperty, descriptor.Attributes[0].IsStringProperty);
|
Assert.Equal(expectedDescriptor.Attributes[0].IsStringProperty, descriptor.Attributes[0].IsStringProperty);
|
||||||
Assert.Equal(expectedDescriptor.Attributes[0].Name, descriptor.Attributes[0].Name, StringComparer.Ordinal);
|
Assert.Equal(expectedDescriptor.Attributes[0].Name, descriptor.Attributes[0].Name, StringComparer.Ordinal);
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
|
|
@ -177,6 +236,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
expectedDescriptor.Attributes[0].TypeName,
|
expectedDescriptor.Attributes[0].TypeName,
|
||||||
descriptor.Attributes[0].TypeName,
|
descriptor.Attributes[0].TypeName,
|
||||||
StringComparer.Ordinal);
|
StringComparer.Ordinal);
|
||||||
|
|
||||||
|
Assert.Equal(expectedDescriptor.Attributes[1].IsIndexer, descriptor.Attributes[1].IsIndexer);
|
||||||
Assert.Equal(expectedDescriptor.Attributes[1].IsStringProperty, descriptor.Attributes[1].IsStringProperty);
|
Assert.Equal(expectedDescriptor.Attributes[1].IsStringProperty, descriptor.Attributes[1].IsStringProperty);
|
||||||
Assert.Equal(expectedDescriptor.Attributes[1].Name, descriptor.Attributes[1].Name, StringComparer.Ordinal);
|
Assert.Equal(expectedDescriptor.Attributes[1].Name, descriptor.Attributes[1].Name, StringComparer.Ordinal);
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
|
|
@ -187,6 +248,88 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
expectedDescriptor.Attributes[1].TypeName,
|
expectedDescriptor.Attributes[1].TypeName,
|
||||||
descriptor.Attributes[1].TypeName,
|
descriptor.Attributes[1].TypeName,
|
||||||
StringComparer.Ordinal);
|
StringComparer.Ordinal);
|
||||||
|
|
||||||
|
Assert.Empty(descriptor.RequiredAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TagHelperDescriptor_WithIndexerAttributes_CanBeDeserialized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var serializedDescriptor =
|
||||||
|
$"{{\"{ nameof(TagHelperDescriptor.Prefix) }\":\"prefix:\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.TagName) }\":\"tag name\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.FullTagName) }\":\"prefix:tag name\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" +
|
||||||
|
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":true," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"}}," +
|
||||||
|
$"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":true," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," +
|
||||||
|
$"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"}}]," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]}}";
|
||||||
|
var expectedDescriptor = new TagHelperDescriptor(
|
||||||
|
prefix: "prefix:",
|
||||||
|
tagName: "tag name",
|
||||||
|
typeName: "type name",
|
||||||
|
assemblyName: "assembly name",
|
||||||
|
attributes: new[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "attribute one",
|
||||||
|
propertyName: "property name",
|
||||||
|
typeName: "property type name",
|
||||||
|
isIndexer: true),
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
name: "attribute two",
|
||||||
|
propertyName: "property name",
|
||||||
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: true),
|
||||||
|
},
|
||||||
|
requiredAttributes: Enumerable.Empty<string>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptor = JsonConvert.DeserializeObject<TagHelperDescriptor>(serializedDescriptor);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(descriptor);
|
||||||
|
Assert.Equal(expectedDescriptor.Prefix, descriptor.Prefix, StringComparer.Ordinal);
|
||||||
|
Assert.Equal(expectedDescriptor.TagName, descriptor.TagName, StringComparer.Ordinal);
|
||||||
|
Assert.Equal(expectedDescriptor.FullTagName, descriptor.FullTagName, StringComparer.Ordinal);
|
||||||
|
Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal);
|
||||||
|
Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
Assert.Equal(2, descriptor.Attributes.Count);
|
||||||
|
Assert.Equal(expectedDescriptor.Attributes[0].IsIndexer, descriptor.Attributes[0].IsIndexer);
|
||||||
|
Assert.Equal(expectedDescriptor.Attributes[0].IsStringProperty, descriptor.Attributes[0].IsStringProperty);
|
||||||
|
Assert.Equal(expectedDescriptor.Attributes[0].Name, descriptor.Attributes[0].Name, StringComparer.Ordinal);
|
||||||
|
Assert.Equal(
|
||||||
|
expectedDescriptor.Attributes[0].PropertyName,
|
||||||
|
descriptor.Attributes[0].PropertyName,
|
||||||
|
StringComparer.Ordinal);
|
||||||
|
Assert.Equal(
|
||||||
|
expectedDescriptor.Attributes[0].TypeName,
|
||||||
|
descriptor.Attributes[0].TypeName,
|
||||||
|
StringComparer.Ordinal);
|
||||||
|
|
||||||
|
Assert.Equal(expectedDescriptor.Attributes[1].IsIndexer, descriptor.Attributes[1].IsIndexer);
|
||||||
|
Assert.Equal(expectedDescriptor.Attributes[1].IsStringProperty, descriptor.Attributes[1].IsStringProperty);
|
||||||
|
Assert.Equal(expectedDescriptor.Attributes[1].Name, descriptor.Attributes[1].Name, StringComparer.Ordinal);
|
||||||
|
Assert.Equal(
|
||||||
|
expectedDescriptor.Attributes[1].PropertyName,
|
||||||
|
descriptor.Attributes[1].PropertyName,
|
||||||
|
StringComparer.Ordinal);
|
||||||
|
Assert.Equal(
|
||||||
|
expectedDescriptor.Attributes[1].TypeName,
|
||||||
|
descriptor.Attributes[1].TypeName,
|
||||||
|
StringComparer.Ordinal);
|
||||||
|
|
||||||
Assert.Empty(descriptor.RequiredAttributes);
|
Assert.Empty(descriptor.RequiredAttributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -921,7 +921,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "bound",
|
name: "bound",
|
||||||
propertyName: "Bound",
|
propertyName: "Bound",
|
||||||
typeName: typeof(bool).FullName),
|
typeName: typeof(bool).FullName,
|
||||||
|
isIndexer: false),
|
||||||
},
|
},
|
||||||
requiredAttributes: Enumerable.Empty<string>())
|
requiredAttributes: Enumerable.Empty<string>())
|
||||||
};
|
};
|
||||||
|
|
@ -944,7 +945,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "bound",
|
name: "bound",
|
||||||
propertyName: "Bound",
|
propertyName: "Bound",
|
||||||
typeName: typeof(bool).FullName),
|
typeName: typeof(bool).FullName,
|
||||||
|
isIndexer: false),
|
||||||
},
|
},
|
||||||
requiredAttributes: Enumerable.Empty<string>())
|
requiredAttributes: Enumerable.Empty<string>())
|
||||||
};
|
};
|
||||||
|
|
@ -1508,11 +1510,13 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "bound",
|
name: "bound",
|
||||||
propertyName: "Bound",
|
propertyName: "Bound",
|
||||||
typeName: typeof(bool).FullName),
|
typeName: typeof(bool).FullName,
|
||||||
|
isIndexer: false),
|
||||||
new TagHelperAttributeDescriptor(
|
new TagHelperAttributeDescriptor(
|
||||||
name: "name",
|
name: "name",
|
||||||
propertyName: "Name",
|
propertyName: "Name",
|
||||||
typeName: typeof(string).FullName)
|
typeName: typeof(string).FullName,
|
||||||
|
isIndexer: false)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
@ -3396,7 +3400,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
string.Format(CultureInfo.InvariantCulture, errorFormatUnclosed, "p"),
|
string.Format(CultureInfo.InvariantCulture, errorFormatUnclosed, "p"),
|
||||||
SourceLocation.Zero),
|
SourceLocation.Zero),
|
||||||
new RazorError(
|
new RazorError(
|
||||||
"TagHelper attributes must be welformed.",
|
"TagHelper attributes must be well-formed.",
|
||||||
absoluteIndex: 12,
|
absoluteIndex: 12,
|
||||||
lineIndex: 0,
|
lineIndex: 0,
|
||||||
columnIndex: 12)
|
columnIndex: 12)
|
||||||
|
|
@ -3962,9 +3966,13 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
new TagHelperDescriptor("person", "PersonTagHelper", "personAssembly",
|
new TagHelperDescriptor("person", "PersonTagHelper", "personAssembly",
|
||||||
attributes: new[]
|
attributes: new[]
|
||||||
{
|
{
|
||||||
new TagHelperAttributeDescriptor("age", "Age", typeof(int).FullName),
|
new TagHelperAttributeDescriptor("age", "Age", typeof(int).FullName, isIndexer: false),
|
||||||
new TagHelperAttributeDescriptor("birthday", "BirthDay", typeof(DateTime).FullName),
|
new TagHelperAttributeDescriptor(
|
||||||
new TagHelperAttributeDescriptor("name", "Name", typeof(string).FullName),
|
"birthday",
|
||||||
|
"BirthDay",
|
||||||
|
typeof(DateTime).FullName,
|
||||||
|
isIndexer: false),
|
||||||
|
new TagHelperAttributeDescriptor("name", "Name", typeof(string).FullName, isIndexer: false),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
var providerContext = new TagHelperDescriptorProvider(descriptors);
|
var providerContext = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
namespace TestOutput
|
||||||
|
{
|
||||||
|
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class PrefixedAttributeTagHelpers
|
||||||
|
{
|
||||||
|
private static object @__o;
|
||||||
|
private void @__RazorDesignTimeHelpers__()
|
||||||
|
{
|
||||||
|
#pragma warning disable 219
|
||||||
|
string __tagHelperDirectiveSyntaxHelper = null;
|
||||||
|
__tagHelperDirectiveSyntaxHelper =
|
||||||
|
#line 1 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
"something, nice"
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
;
|
||||||
|
#pragma warning restore 219
|
||||||
|
}
|
||||||
|
#line hidden
|
||||||
|
private InputTagHelper1 __InputTagHelper1 = null;
|
||||||
|
private InputTagHelper2 __InputTagHelper2 = null;
|
||||||
|
#line hidden
|
||||||
|
public PrefixedAttributeTagHelpers()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable 1998
|
||||||
|
public override async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
#line 3 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
|
||||||
|
var literate = "or illiterate";
|
||||||
|
var intDictionary = new Dictionary<string, int>
|
||||||
|
{
|
||||||
|
{ "three", 3 },
|
||||||
|
};
|
||||||
|
var stringDictionary = new SortedDictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "name", "value" },
|
||||||
|
};
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
#line 16 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty = intDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
#line 16 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.StringDictionaryProperty = stringDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__InputTagHelper2.IntDictionaryProperty = __InputTagHelper1.IntDictionaryProperty;
|
||||||
|
__InputTagHelper2.StringDictionaryProperty = __InputTagHelper1.StringDictionaryProperty;
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty = intDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["garlic"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntProperty = 42;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__InputTagHelper2.IntDictionaryProperty = __InputTagHelper1.IntDictionaryProperty;
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["garlic"] = __InputTagHelper1.IntDictionaryProperty["garlic"];
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["grabber"] = __InputTagHelper1.IntProperty;
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntProperty = 42;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["salt"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["pepper"] = 98;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__InputTagHelper1.StringProperty = "string";
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["paprika"] = "another string";
|
||||||
|
#line 21 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__o = literate;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["cumin"] = string.Empty;
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["grabber"] = __InputTagHelper1.IntProperty;
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["salt"] = __InputTagHelper1.IntDictionaryProperty["salt"];
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["pepper"] = __InputTagHelper1.IntDictionaryProperty["pepper"];
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["grabber"] = __InputTagHelper1.StringProperty;
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["paprika"] = __InputTagHelper1.StringDictionaryProperty["paprika"];
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["cumin"] = __InputTagHelper1.StringDictionaryProperty["cumin"];
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
#line 22 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["value"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["thyme"] = "string";
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["value"] = __InputTagHelper1.IntDictionaryProperty["value"];
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["thyme"] = __InputTagHelper1.StringDictionaryProperty["thyme"];
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
#pragma checksum "PrefixedAttributeTagHelpers.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "4e7fe9697b745af1a07d41f6a8532fdc288fa046"
|
||||||
|
namespace TestOutput
|
||||||
|
{
|
||||||
|
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class PrefixedAttributeTagHelpers
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
#pragma warning disable 0414
|
||||||
|
private TagHelperContent __tagHelperStringValueBuffer = null;
|
||||||
|
#pragma warning restore 0414
|
||||||
|
private TagHelperExecutionContext __tagHelperExecutionContext = null;
|
||||||
|
private TagHelperRunner __tagHelperRunner = null;
|
||||||
|
private TagHelperScopeManager __tagHelperScopeManager = new TagHelperScopeManager();
|
||||||
|
private InputTagHelper2 __InputTagHelper2 = null;
|
||||||
|
private InputTagHelper1 __InputTagHelper1 = null;
|
||||||
|
#line hidden
|
||||||
|
public PrefixedAttributeTagHelpers()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable 1998
|
||||||
|
public override async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
__tagHelperRunner = __tagHelperRunner ?? new TagHelperRunner();
|
||||||
|
Instrumentation.BeginContext(33, 2, true);
|
||||||
|
WriteLiteral("\r\n");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
#line 3 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
|
||||||
|
var literate = "or illiterate";
|
||||||
|
var intDictionary = new Dictionary<string, int>
|
||||||
|
{
|
||||||
|
{ "three", 3 },
|
||||||
|
};
|
||||||
|
var stringDictionary = new SortedDictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "name", "value" },
|
||||||
|
};
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
Instrumentation.BeginContext(280, 51, true);
|
||||||
|
WriteLiteral("\r\n\r\n<div class=\"randomNonTagHelperAttribute\">\r\n ");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||||
|
}
|
||||||
|
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper2);
|
||||||
|
#line 16 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.IntDictionaryProperty = intDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-dictionary", __InputTagHelper2.IntDictionaryProperty);
|
||||||
|
#line 16 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.StringDictionaryProperty = stringDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-dictionary", __InputTagHelper2.StringDictionaryProperty);
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper1);
|
||||||
|
__InputTagHelper1.IntDictionaryProperty = __InputTagHelper2.IntDictionaryProperty;
|
||||||
|
__InputTagHelper1.StringDictionaryProperty = __InputTagHelper2.StringDictionaryProperty;
|
||||||
|
__tagHelperExecutionContext.AddHtmlAttribute("type", Html.Raw("checkbox"));
|
||||||
|
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||||
|
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||||
|
Instrumentation.BeginContext(423, 6, true);
|
||||||
|
WriteLiteral("\r\n ");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||||
|
}
|
||||||
|
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper2);
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.IntDictionaryProperty = intDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-dictionary", __InputTagHelper2.IntDictionaryProperty);
|
||||||
|
if (__InputTagHelper2.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-garlic", "InputTagHelper2", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["garlic"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-garlic", __InputTagHelper2.IntDictionaryProperty["garlic"]);
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["grabber"] = 42;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-grabber", __InputTagHelper2.IntDictionaryProperty["grabber"]);
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper1);
|
||||||
|
__InputTagHelper1.IntDictionaryProperty = __InputTagHelper2.IntDictionaryProperty;
|
||||||
|
if (__InputTagHelper1.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-garlic", "InputTagHelper1", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["garlic"] = __InputTagHelper2.IntDictionaryProperty["garlic"];
|
||||||
|
__InputTagHelper1.IntProperty = __InputTagHelper2.IntDictionaryProperty["grabber"];
|
||||||
|
__tagHelperExecutionContext.AddHtmlAttribute("type", Html.Raw("password"));
|
||||||
|
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||||
|
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||||
|
Instrumentation.BeginContext(532, 6, true);
|
||||||
|
WriteLiteral("\r\n ");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||||
|
}
|
||||||
|
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper2);
|
||||||
|
if (__InputTagHelper2.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-grabber", "InputTagHelper2", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["grabber"] = 42;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-grabber", __InputTagHelper2.IntDictionaryProperty["grabber"]);
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["salt"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-salt", __InputTagHelper2.IntDictionaryProperty["salt"]);
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["pepper"] = 98;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-pepper", __InputTagHelper2.IntDictionaryProperty["pepper"]);
|
||||||
|
if (__InputTagHelper2.StringDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("string-prefix-grabber", "InputTagHelper2", "StringDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["grabber"] = "string";
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-grabber", __InputTagHelper2.StringDictionaryProperty["grabber"]);
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["paprika"] = "another string";
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-paprika", __InputTagHelper2.StringDictionaryProperty["paprika"]);
|
||||||
|
StartTagHelperWritingScope();
|
||||||
|
WriteLiteral("literate ");
|
||||||
|
#line 21 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
WriteLiteral(literate);
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
WriteLiteral("?");
|
||||||
|
__tagHelperStringValueBuffer = EndTagHelperWritingScope();
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["cumin"] = __tagHelperStringValueBuffer.ToString();
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-cumin", __InputTagHelper2.StringDictionaryProperty["cumin"]);
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper1);
|
||||||
|
__InputTagHelper1.IntProperty = __InputTagHelper2.IntDictionaryProperty["grabber"];
|
||||||
|
if (__InputTagHelper1.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-salt", "InputTagHelper1", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["salt"] = __InputTagHelper2.IntDictionaryProperty["salt"];
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["pepper"] = __InputTagHelper2.IntDictionaryProperty["pepper"];
|
||||||
|
__InputTagHelper1.StringProperty = __InputTagHelper2.StringDictionaryProperty["grabber"];
|
||||||
|
if (__InputTagHelper1.StringDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("string-prefix-paprika", "InputTagHelper1", "StringDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["paprika"] = __InputTagHelper2.StringDictionaryProperty["paprika"];
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["cumin"] = __InputTagHelper2.StringDictionaryProperty["cumin"];
|
||||||
|
__tagHelperExecutionContext.AddHtmlAttribute("type", Html.Raw("radio"));
|
||||||
|
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||||
|
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||||
|
Instrumentation.BeginContext(795, 6, true);
|
||||||
|
WriteLiteral("\r\n ");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||||
|
}
|
||||||
|
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper2);
|
||||||
|
if (__InputTagHelper2.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-value", "InputTagHelper2", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
#line 22 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["value"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-value", __InputTagHelper2.IntDictionaryProperty["value"]);
|
||||||
|
if (__InputTagHelper2.StringDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("string-prefix-thyme", "InputTagHelper2", "StringDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["thyme"] = "string";
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-thyme", __InputTagHelper2.StringDictionaryProperty["thyme"]);
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper1);
|
||||||
|
if (__InputTagHelper1.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-value", "InputTagHelper1", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["value"] = __InputTagHelper2.IntDictionaryProperty["value"];
|
||||||
|
if (__InputTagHelper1.StringDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("string-prefix-thyme", "InputTagHelper1", "StringDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["thyme"] = __InputTagHelper2.StringDictionaryProperty["thyme"];
|
||||||
|
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||||
|
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||||
|
Instrumentation.BeginContext(861, 8, true);
|
||||||
|
WriteLiteral("\r\n</div>");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
#pragma checksum "PrefixedAttributeTagHelpers.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "4e7fe9697b745af1a07d41f6a8532fdc288fa046"
|
||||||
|
namespace TestOutput
|
||||||
|
{
|
||||||
|
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class PrefixedAttributeTagHelpers
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
#pragma warning disable 0414
|
||||||
|
private TagHelperContent __tagHelperStringValueBuffer = null;
|
||||||
|
#pragma warning restore 0414
|
||||||
|
private TagHelperExecutionContext __tagHelperExecutionContext = null;
|
||||||
|
private TagHelperRunner __tagHelperRunner = null;
|
||||||
|
private TagHelperScopeManager __tagHelperScopeManager = new TagHelperScopeManager();
|
||||||
|
private InputTagHelper1 __InputTagHelper1 = null;
|
||||||
|
private InputTagHelper2 __InputTagHelper2 = null;
|
||||||
|
#line hidden
|
||||||
|
public PrefixedAttributeTagHelpers()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable 1998
|
||||||
|
public override async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
__tagHelperRunner = __tagHelperRunner ?? new TagHelperRunner();
|
||||||
|
Instrumentation.BeginContext(33, 2, true);
|
||||||
|
WriteLiteral("\r\n");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
#line 3 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
|
||||||
|
var literate = "or illiterate";
|
||||||
|
var intDictionary = new Dictionary<string, int>
|
||||||
|
{
|
||||||
|
{ "three", 3 },
|
||||||
|
};
|
||||||
|
var stringDictionary = new SortedDictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "name", "value" },
|
||||||
|
};
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
Instrumentation.BeginContext(280, 51, true);
|
||||||
|
WriteLiteral("\r\n\r\n<div class=\"randomNonTagHelperAttribute\">\r\n ");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||||
|
}
|
||||||
|
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper1);
|
||||||
|
#line 16 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty = intDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-dictionary", __InputTagHelper1.IntDictionaryProperty);
|
||||||
|
#line 16 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.StringDictionaryProperty = stringDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-dictionary", __InputTagHelper1.StringDictionaryProperty);
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper2);
|
||||||
|
__InputTagHelper2.IntDictionaryProperty = __InputTagHelper1.IntDictionaryProperty;
|
||||||
|
__InputTagHelper2.StringDictionaryProperty = __InputTagHelper1.StringDictionaryProperty;
|
||||||
|
__tagHelperExecutionContext.AddHtmlAttribute("type", Html.Raw("checkbox"));
|
||||||
|
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||||
|
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||||
|
Instrumentation.BeginContext(423, 6, true);
|
||||||
|
WriteLiteral("\r\n ");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||||
|
}
|
||||||
|
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper1);
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty = intDictionary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-dictionary", __InputTagHelper1.IntDictionaryProperty);
|
||||||
|
if (__InputTagHelper1.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-garlic", "InputTagHelper1", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["garlic"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-garlic", __InputTagHelper1.IntDictionaryProperty["garlic"]);
|
||||||
|
#line 17 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntProperty = 42;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-grabber", __InputTagHelper1.IntProperty);
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper2);
|
||||||
|
__InputTagHelper2.IntDictionaryProperty = __InputTagHelper1.IntDictionaryProperty;
|
||||||
|
if (__InputTagHelper2.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-garlic", "InputTagHelper2", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["garlic"] = __InputTagHelper1.IntDictionaryProperty["garlic"];
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["grabber"] = __InputTagHelper1.IntProperty;
|
||||||
|
__tagHelperExecutionContext.AddHtmlAttribute("type", Html.Raw("password"));
|
||||||
|
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||||
|
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||||
|
Instrumentation.BeginContext(532, 6, true);
|
||||||
|
WriteLiteral("\r\n ");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||||
|
}
|
||||||
|
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper1);
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntProperty = 42;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-grabber", __InputTagHelper1.IntProperty);
|
||||||
|
if (__InputTagHelper1.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-salt", "InputTagHelper1", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["salt"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-salt", __InputTagHelper1.IntDictionaryProperty["salt"]);
|
||||||
|
#line 19 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["pepper"] = 98;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-pepper", __InputTagHelper1.IntDictionaryProperty["pepper"]);
|
||||||
|
__InputTagHelper1.StringProperty = "string";
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-grabber", __InputTagHelper1.StringProperty);
|
||||||
|
if (__InputTagHelper1.StringDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("string-prefix-paprika", "InputTagHelper1", "StringDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["paprika"] = "another string";
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-paprika", __InputTagHelper1.StringDictionaryProperty["paprika"]);
|
||||||
|
StartTagHelperWritingScope();
|
||||||
|
WriteLiteral("literate ");
|
||||||
|
#line 21 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
WriteLiteral(literate);
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
WriteLiteral("?");
|
||||||
|
__tagHelperStringValueBuffer = EndTagHelperWritingScope();
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["cumin"] = __tagHelperStringValueBuffer.ToString();
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-cumin", __InputTagHelper1.StringDictionaryProperty["cumin"]);
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper2);
|
||||||
|
if (__InputTagHelper2.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-grabber", "InputTagHelper2", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["grabber"] = __InputTagHelper1.IntProperty;
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["salt"] = __InputTagHelper1.IntDictionaryProperty["salt"];
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["pepper"] = __InputTagHelper1.IntDictionaryProperty["pepper"];
|
||||||
|
if (__InputTagHelper2.StringDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("string-prefix-grabber", "InputTagHelper2", "StringDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["grabber"] = __InputTagHelper1.StringProperty;
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["paprika"] = __InputTagHelper1.StringDictionaryProperty["paprika"];
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["cumin"] = __InputTagHelper1.StringDictionaryProperty["cumin"];
|
||||||
|
__tagHelperExecutionContext.AddHtmlAttribute("type", Html.Raw("radio"));
|
||||||
|
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||||
|
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||||
|
Instrumentation.BeginContext(795, 6, true);
|
||||||
|
WriteLiteral("\r\n ");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||||
|
}
|
||||||
|
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||||
|
__InputTagHelper1 = CreateTagHelper<InputTagHelper1>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper1);
|
||||||
|
if (__InputTagHelper1.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-value", "InputTagHelper1", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
#line 22 "PrefixedAttributeTagHelpers.cshtml"
|
||||||
|
__InputTagHelper1.IntDictionaryProperty["value"] = 37;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-value", __InputTagHelper1.IntDictionaryProperty["value"]);
|
||||||
|
if (__InputTagHelper1.StringDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("string-prefix-thyme", "InputTagHelper1", "StringDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper1.StringDictionaryProperty["thyme"] = "string";
|
||||||
|
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-thyme", __InputTagHelper1.StringDictionaryProperty["thyme"]);
|
||||||
|
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
|
||||||
|
__tagHelperExecutionContext.Add(__InputTagHelper2);
|
||||||
|
if (__InputTagHelper2.IntDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("int-prefix-value", "InputTagHelper2", "IntDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper2.IntDictionaryProperty["value"] = __InputTagHelper1.IntDictionaryProperty["value"];
|
||||||
|
if (__InputTagHelper2.StringDictionaryProperty == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(FormatInvalidIndexerAssignment("string-prefix-thyme", "InputTagHelper2", "StringDictionaryProperty"));
|
||||||
|
}
|
||||||
|
__InputTagHelper2.StringDictionaryProperty["thyme"] = __InputTagHelper1.StringDictionaryProperty["thyme"];
|
||||||
|
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||||
|
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||||
|
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||||
|
Instrumentation.BeginContext(861, 8, true);
|
||||||
|
WriteLiteral("\r\n</div>");
|
||||||
|
Instrumentation.EndContext();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
@addTagHelper "something, nice"
|
||||||
|
|
||||||
|
@{
|
||||||
|
var literate = "or illiterate";
|
||||||
|
var intDictionary = new Dictionary<string, int>
|
||||||
|
{
|
||||||
|
{ "three", 3 },
|
||||||
|
};
|
||||||
|
var stringDictionary = new SortedDictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "name", "value" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="randomNonTagHelperAttribute">
|
||||||
|
<input type="checkbox" int-dictionary="intDictionary" string-dictionary="stringDictionary"/>
|
||||||
|
<input type="password" int-dictionary="intDictionary" int-prefix-garlic="37" int-prefix-grabber="42" />
|
||||||
|
<input type="radio"
|
||||||
|
int-prefix-grabber="42" int-prefix-salt="37" int-prefix-pepper="98" int-prefix-salt="8"
|
||||||
|
string-prefix-grabber="string" string-prefix-paprika="another string"
|
||||||
|
string-prefix-cumin="literate @literate?"/>
|
||||||
|
<input int-prefix-value="37" string-prefix-thyme="string" />
|
||||||
|
</div>
|
||||||
Loading…
Reference in New Issue