React to aspnet/Razor#89 fix
- use `IDictionary<string, TValue>` support in `<a/>` and `<form/>` tag helpers - make `RouteValues` dictionaries `IDictionary<string, string>` for ease of use - remove `TagHelperOutputExtensions.FindPrefixedAttributes()` - set all `GeneratedTagHelperContext` properties - add error for tag helper dictionary properties where `TValue` is `ModelExpression` - add new `RazorPage.InvalidTagHelperIndexerAssignment()` method and resource tests - use new `isIndexer` argument when creating `TagHelperAttributeDescriptor` - arrange `AnchorTagHelper` and `FormTagHelper` correctly - also expect `routeValues != null` in calls to `IHtmlGenerator` nits: - get rid of some `foo` and `bar` gunk in tests - remove unused variable to cleanup a test compilation warning
This commit is contained in:
parent
337bbad51d
commit
517c013882
|
|
@ -72,6 +72,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
ExecutionContextAddTagHelperAttributeMethodName =
|
||||
nameof(TagHelperExecutionContext.AddTagHelperAttribute),
|
||||
ExecutionContextAddHtmlAttributeMethodName = nameof(TagHelperExecutionContext.AddHtmlAttribute),
|
||||
ExecutionContextAddMinimizedHtmlAttributeMethodName =
|
||||
nameof(TagHelperExecutionContext.AddMinimizedHtmlAttribute),
|
||||
ExecutionContextOutputPropertyName = nameof(TagHelperExecutionContext.Output),
|
||||
|
||||
RunnerTypeName = typeof(TagHelperRunner).FullName,
|
||||
|
|
@ -85,6 +87,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
// Can't use nameof because RazorPage is not accessible here.
|
||||
CreateTagHelperMethodName = "CreateTagHelper",
|
||||
FormatInvalidIndexerAssignmentMethodName = "InvalidTagHelperIndexerAssignment",
|
||||
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
|
||||
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
|
||||
|
||||
|
|
@ -211,7 +214,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
sourceFileName = _pathNormalizer.NormalizePath(sourceFileName);
|
||||
|
||||
var inheritedCodeTrees = ChunkInheritanceUtility.GetInheritedCodeTrees(sourceFileName);
|
||||
return new MvcRazorParser(razorParser, inheritedCodeTrees, DefaultInheritedChunks);
|
||||
return new MvcRazorParser(razorParser, inheritedCodeTrees, DefaultInheritedChunks, ModelExpressionType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -224,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public override CodeBuilder DecorateCodeBuilder([NotNull] CodeBuilder incomingBuilder,
|
||||
[NotNull] CodeBuilderContext context)
|
||||
{
|
||||
// Need the normalized path to resolve inherited chunks only. Full paths are needed for generated Razor
|
||||
// Need the normalized path to resolve inherited chunks only. Full paths are needed for generated Razor
|
||||
// files checksum and line pragmas to enable DesignTime debugging.
|
||||
var normalizedPath = _pathNormalizer.NormalizePath(context.SourceFile);
|
||||
var inheritedChunks = ChunkInheritanceUtility.GetInheritedCodeTrees(normalizedPath);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Razor.Host;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
|
|
@ -20,6 +22,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public class MvcRazorParser : RazorParser
|
||||
{
|
||||
private readonly IEnumerable<TagHelperDirectiveDescriptor> _globalImportDirectiveDescriptors;
|
||||
private readonly string _modelExpressionTypeName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MvcRazorParser"/>.
|
||||
|
|
@ -32,13 +35,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public MvcRazorParser(
|
||||
[NotNull] RazorParser parser,
|
||||
[NotNull] IReadOnlyList<CodeTree> inheritedCodeTrees,
|
||||
[NotNull] IReadOnlyList<Chunk> defaultInheritedChunks)
|
||||
[NotNull] IReadOnlyList<Chunk> defaultInheritedChunks,
|
||||
[NotNull] string modelExpressionTypeName)
|
||||
: base(parser)
|
||||
{
|
||||
// Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks
|
||||
_globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors(
|
||||
inheritedCodeTrees,
|
||||
defaultInheritedChunks);
|
||||
|
||||
_modelExpressionTypeName = modelExpressionTypeName;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -50,7 +56,27 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
TagHelperDescriptorResolver,
|
||||
_globalImportDirectiveDescriptors,
|
||||
errorSink);
|
||||
return visitor.GetDescriptors(documentRoot);
|
||||
|
||||
var descriptors = visitor.GetDescriptors(documentRoot);
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
foreach (var attributeDescriptor in descriptor.Attributes)
|
||||
{
|
||||
if (attributeDescriptor.IsIndexer &&
|
||||
string.Equals(
|
||||
attributeDescriptor.TypeName,
|
||||
_modelExpressionTypeName,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
errorSink.OnError(SourceLocation.Undefined, Resources.FormatMvcRazorParser_InvalidPropertyType(
|
||||
descriptor.TypeName,
|
||||
attributeDescriptor.Name,
|
||||
_modelExpressionTypeName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private static IEnumerable<TagHelperDirectiveDescriptor> GetTagHelperDirectiveDescriptors(
|
||||
|
|
@ -69,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
var chunksInOrder = defaultInheritedChunks.Concat(chunksFromGlobalImports);
|
||||
foreach (var chunk in chunksInOrder)
|
||||
{
|
||||
// All TagHelperDirectiveDescriptors created here have undefined source locations because the source
|
||||
// All TagHelperDirectiveDescriptors created here have undefined source locations because the source
|
||||
// that created them is not in the same file.
|
||||
var addTagHelperChunk = chunk as AddTagHelperChunk;
|
||||
if (addTagHelperChunk != null)
|
||||
|
|
@ -154,8 +180,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it
|
||||
// takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the
|
||||
// We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it
|
||||
// takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the
|
||||
// inherited @tagHelperPrefix directive back into the merged list.
|
||||
if (prefixDirectiveDescriptor != null &&
|
||||
!descriptors.Any(descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix))
|
||||
|
|
|
|||
|
|
@ -106,6 +106,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Host
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
|
||||
/// </summary>
|
||||
internal static string MvcRazorParser_InvalidPropertyType
|
||||
{
|
||||
get { return GetString("MvcRazorParser_InvalidPropertyType"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatMvcRazorParser_InvalidPropertyType(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorParser_InvalidPropertyType"), p0, p1, p2);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -135,4 +135,7 @@
|
|||
<data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve">
|
||||
<value>Only one '{0}' statement is allowed in a file.</value>
|
||||
</data>
|
||||
<data name="MvcRazorParser_InvalidPropertyType" xml:space="preserve">
|
||||
<value>Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -410,6 +410,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return GetString("GeneratedCodeFileName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null.
|
||||
/// </summary>
|
||||
internal static string RazorPage_InvalidTagHelperIndexerAssignment
|
||||
{
|
||||
get { return GetString("RazorPage_InvalidTagHelperIndexerAssignment"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null.
|
||||
/// </summary>
|
||||
internal static string FormatRazorPage_InvalidTagHelperIndexerAssignment(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_InvalidTagHelperIndexerAssignment"), p0, p1, p2);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -150,6 +150,24 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format an error message about using an indexer when the tag helper property is <c>null</c>.
|
||||
/// </summary>
|
||||
/// <param name="attributeName">Name of the HTML attribute associated with the indexer.</param>
|
||||
/// <param name="tagHelperTypeName">Full name of the tag helper <see cref="Type"/>.</param>
|
||||
/// <param name="propertyName">Dictionary property in the tag helper.</param>
|
||||
/// <returns>An error message about using an indexer when the tag helper property is <c>null</c>.</returns>
|
||||
public static string InvalidTagHelperIndexerAssignment(
|
||||
string attributeName,
|
||||
string tagHelperTypeName,
|
||||
string propertyName)
|
||||
{
|
||||
return Resources.FormatRazorPage_InvalidTagHelperIndexerAssignment(
|
||||
attributeName,
|
||||
tagHelperTypeName,
|
||||
propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and activates a <see cref="ITagHelper"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</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>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
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
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -192,4 +192,7 @@
|
|||
<data name="GeneratedCodeFileName" xml:space="preserve">
|
||||
<value>Generated Code</value>
|
||||
</data>
|
||||
<data name="RazorPage_InvalidTagHelperIndexerAssignment" xml:space="preserve">
|
||||
<value>Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -21,7 +21,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
private const string HostAttributeName = "asp-host";
|
||||
private const string ProtocolAttributeName = "asp-protocol";
|
||||
private const string RouteAttributeName = "asp-route";
|
||||
private const string RouteAttributePrefix = "asp-route-";
|
||||
private const string RouteValuesDictionaryName = "asp-all-route-data";
|
||||
private const string RouteValuesPrefix = "asp-route-";
|
||||
private const string Href = "href";
|
||||
|
||||
[Activate]
|
||||
|
|
@ -69,6 +70,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
[HtmlAttributeName(RouteAttributeName)]
|
||||
public string Route { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters for the route.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)]
|
||||
public IDictionary<string, string> RouteValues { get; set; } =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Does nothing if user provides an <c>href</c> attribute.</remarks>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
|
|
@ -79,8 +87,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// </exception>
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix);
|
||||
|
||||
// If "href" is already set, it means the user is attempting to use a normal anchor.
|
||||
if (output.Attributes.ContainsName(Href))
|
||||
{
|
||||
|
|
@ -90,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Protocol != null ||
|
||||
Host != null ||
|
||||
Fragment != null ||
|
||||
routePrefixedAttributes.Any())
|
||||
RouteValues.Count != 0)
|
||||
{
|
||||
// User specified an href and one of the bound attributes; can't determine the href attribute.
|
||||
throw new InvalidOperationException(
|
||||
|
|
@ -102,15 +108,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
ProtocolAttributeName,
|
||||
HostAttributeName,
|
||||
FragmentAttributeName,
|
||||
RouteAttributePrefix,
|
||||
RouteValuesPrefix,
|
||||
Href));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TagBuilder tagBuilder;
|
||||
var routeValues = GetRouteValues(output, routePrefixedAttributes);
|
||||
// Convert from Dictionary<string, string> to Dictionary<string, object>.
|
||||
var routeValues = RouteValues.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => (object)kvp.Value,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
TagBuilder tagBuilder;
|
||||
if (Route == null)
|
||||
{
|
||||
tagBuilder = Generator.GenerateActionLink(linkText: string.Empty,
|
||||
|
|
@ -150,26 +160,5 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: https://github.com/aspnet/Razor/issues/89 - We will not need this method once #89 is completed.
|
||||
private static Dictionary<string, object> GetRouteValues(
|
||||
TagHelperOutput output,
|
||||
IEnumerable<TagHelperAttribute> routePrefixedAttributes)
|
||||
{
|
||||
Dictionary<string, object> routeValues = null;
|
||||
if (routePrefixedAttributes.Any())
|
||||
{
|
||||
// Prefixed values should be treated as bound attributes, remove them from the output.
|
||||
output.RemoveRange(routePrefixedAttributes);
|
||||
|
||||
// Remove prefix from keys and convert all values to strings. HtmlString and similar classes are not
|
||||
// meaningful to routing.
|
||||
routeValues = routePrefixedAttributes.ToDictionary(
|
||||
attribute => attribute.Name.Substring(RouteAttributePrefix.Length),
|
||||
attribute => (object)attribute.Value.ToString());
|
||||
}
|
||||
|
||||
return routeValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
private const string AntiForgeryAttributeName = "asp-anti-forgery";
|
||||
private const string ControllerAttributeName = "asp-controller";
|
||||
private const string RouteAttributeName = "asp-route";
|
||||
private const string RouteAttributePrefix = "asp-route-";
|
||||
private const string RouteValuesDictionaryName = "asp-all-route-data";
|
||||
private const string RouteValuesPrefix = "asp-route-";
|
||||
private const string HtmlActionAttributeName = "action";
|
||||
|
||||
[Activate]
|
||||
|
|
@ -57,6 +58,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
[HtmlAttributeName(RouteAttributeName)]
|
||||
public string Route { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional parameters for the route.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)]
|
||||
public IDictionary<string, string> RouteValues { get; set; } =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Does nothing if user provides an <c>action</c> attribute and <see cref="AntiForgery"/> is <c>null</c> or
|
||||
|
|
@ -69,12 +77,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
var antiForgeryDefault = true;
|
||||
var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix);
|
||||
|
||||
// If "action" is already set, it means the user is attempting to use a normal <form>.
|
||||
if (output.Attributes.ContainsName(HtmlActionAttributeName))
|
||||
{
|
||||
if (Action != null || Controller != null || Route != null || routePrefixedAttributes.Any())
|
||||
if (Action != null || Controller != null || Route != null || RouteValues.Count != 0)
|
||||
{
|
||||
// User also specified bound attributes we cannot use.
|
||||
throw new InvalidOperationException(
|
||||
|
|
@ -84,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
ActionAttributeName,
|
||||
ControllerAttributeName,
|
||||
RouteAttributeName,
|
||||
RouteAttributePrefix));
|
||||
RouteValuesPrefix));
|
||||
}
|
||||
|
||||
// User is using the FormTagHelper like a normal <form> tag. Anti-forgery default should be false to
|
||||
|
|
@ -93,8 +100,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
else
|
||||
{
|
||||
// Convert from Dictionary<string, string> to Dictionary<string, object>.
|
||||
var routeValues = RouteValues.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => (object)kvp.Value,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
TagBuilder tagBuilder;
|
||||
var routeValues = GetRouteValues(output, routePrefixedAttributes);
|
||||
if (Route == null)
|
||||
{
|
||||
tagBuilder = Generator.GenerateForm(
|
||||
|
|
@ -142,26 +154,5 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: https://github.com/aspnet/Razor/issues/89 - We will not need this method once #89 is completed.
|
||||
private static Dictionary<string, object> GetRouteValues(
|
||||
TagHelperOutput output,
|
||||
IEnumerable<TagHelperAttribute> routePrefixedAttributes)
|
||||
{
|
||||
Dictionary<string, object> routeValues = null;
|
||||
if (routePrefixedAttributes.Any())
|
||||
{
|
||||
// Prefixed values should be treated as bound attributes, remove them from the output.
|
||||
output.RemoveRange(routePrefixedAttributes);
|
||||
|
||||
// Remove prefix from keys and convert all values to strings. HtmlString and similar classes are not
|
||||
// meaningful to routing.
|
||||
routeValues = routePrefixedAttributes.ToDictionary(
|
||||
attribute => attribute.Name.Substring(RouteAttributePrefix.Length),
|
||||
attribute => (object)attribute.Value.ToString());
|
||||
}
|
||||
|
||||
return routeValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,28 +51,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all attributes from <paramref name="tagHelperOutput"/>'s
|
||||
/// <see cref="TagHelperOutput.Attributes"/> that have the given <paramref name="prefix"/>.
|
||||
/// </summary>
|
||||
/// <param name="tagHelperOutput">The <see cref="TagHelperOutput"/> this method extends.</param>
|
||||
/// <param name="prefix">A prefix to look for.</param>
|
||||
/// <returns><see cref="KeyValuePair{string, string}"/>s with <see cref="KeyValuePair{string, string}.Key"/>
|
||||
/// starting with the given <paramref name="prefix"/>.</returns>
|
||||
public static IEnumerable<TagHelperAttribute> FindPrefixedAttributes(
|
||||
[NotNull] this TagHelperOutput tagHelperOutput,
|
||||
[NotNull] string prefix)
|
||||
{
|
||||
// TODO: https://github.com/aspnet/Razor/issues/89 - We will not need this method once #89 is completed.
|
||||
|
||||
// We're only interested in HTML attributes that have the desired prefix.
|
||||
var prefixedAttributes = tagHelperOutput.Attributes
|
||||
.Where(attribute => attribute.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
.ToArray();
|
||||
|
||||
return prefixedAttributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the given <paramref name="tagBuilder"/>'s <see cref="TagBuilder.Attributes"/> into the
|
||||
/// <paramref name="tagHelperOutput"/>.
|
||||
|
|
|
|||
|
|
@ -1071,9 +1071,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// The [DefaultValue] on ValueTypeRequiredWithDefaultValue is ignored by model binding.
|
||||
var expectedValue = 0;
|
||||
|
||||
// Make ValueTypeRequired invalid.
|
||||
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired));
|
||||
dto.Results[propertyMetadata] = new ModelBindingResult(
|
||||
|
|
@ -1294,7 +1291,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.Equal(dob, model.DateOfBirth);
|
||||
Assert.True(bindingContext.ModelState.IsValid);
|
||||
|
||||
// Ensure that we add child nodes for all the nodes which have a result (irrespective of if they
|
||||
// Ensure that we add child nodes for all the nodes which have a result (irrespective of if they
|
||||
// are bound or not).
|
||||
Assert.Equal(2, modelValidationNode.ChildNodes.Count());
|
||||
|
||||
|
|
@ -1631,7 +1628,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
bindingContext.ModelState["foo"].Errors[0].Exception.Message);
|
||||
}
|
||||
|
||||
// This can only really be done by writing an invalid model binder and returning 'isModelSet: true'
|
||||
// This can only really be done by writing an invalid model binder and returning 'isModelSet: true'
|
||||
// with a null model for a value type.
|
||||
[Fact]
|
||||
public void SetProperty_SettingNonNullableValueTypeToNull_CapturesException()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
|
|
@ -230,7 +231,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public TestableMvcRazorParser(RazorParser parser,
|
||||
IReadOnlyList<CodeTree> codeTrees,
|
||||
IReadOnlyList<Chunk> defaultInheritedChunks)
|
||||
: base(parser, codeTrees, defaultInheritedChunks)
|
||||
: base(parser, codeTrees, defaultInheritedChunks, typeof(ModelExpression).FullName)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
ModelExpressionTypeName = modelExpressionType,
|
||||
CreateModelExpressionMethodName = "SomeMethod"
|
||||
});
|
||||
var attributeDescriptor = new TagHelperAttributeDescriptor("MyAttribute", "SomeProperty", propertyType);
|
||||
var attributeDescriptor = new TagHelperAttributeDescriptor(
|
||||
name: "MyAttribute",
|
||||
propertyName: "SomeProperty",
|
||||
typeName: propertyType,
|
||||
isIndexer: false);
|
||||
var writer = new CSharpCodeWriter();
|
||||
var generatorContext = new CodeGeneratorContext(host: null,
|
||||
className: string.Empty,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
using Microsoft.Framework.WebEncoders.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -28,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
allAttributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "id", "myanchor" },
|
||||
{ "asp-route-foo", "bar" },
|
||||
{ "asp-route-name", "value" },
|
||||
{ "asp-action", "index" },
|
||||
{ "asp-controller", "home" },
|
||||
{ "asp-fragment", "hello=world" },
|
||||
|
|
@ -48,7 +47,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "id", "myanchor" },
|
||||
{ "asp-route-foo", "bar" },
|
||||
});
|
||||
output.Content.SetContent("Something");
|
||||
|
||||
|
|
@ -68,6 +66,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Generator = htmlGenerator,
|
||||
Host = "contoso.com",
|
||||
Protocol = "http",
|
||||
RouteValues =
|
||||
{
|
||||
{ "name", "value" },
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
|
|
@ -106,7 +108,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||
generator
|
||||
.Setup(mock => mock.GenerateRouteLink(
|
||||
string.Empty, "Default", "http", "contoso.com", "hello=world", null, null))
|
||||
string.Empty,
|
||||
"Default",
|
||||
"http",
|
||||
"contoso.com",
|
||||
"hello=world",
|
||||
It.IsAny<IDictionary<string, object>>(),
|
||||
null))
|
||||
.Returns(new TagBuilder("a", new CommonTestEncoder()))
|
||||
.Verifiable();
|
||||
var anchorTagHelper = new AnchorTagHelper
|
||||
|
|
@ -149,7 +157,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var generator = new Mock<IHtmlGenerator>();
|
||||
generator
|
||||
.Setup(mock => mock.GenerateActionLink(
|
||||
string.Empty, "Index", "Home", "http", "contoso.com", "hello=world", null, null))
|
||||
string.Empty,
|
||||
"Index",
|
||||
"Home",
|
||||
"http",
|
||||
"contoso.com",
|
||||
"hello=world",
|
||||
It.IsAny<IDictionary<string, object>>(),
|
||||
null))
|
||||
.Returns(new TagBuilder("a", new CommonTestEncoder()))
|
||||
.Verifiable();
|
||||
var anchorTagHelper = new AnchorTagHelper
|
||||
|
|
@ -190,7 +205,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
});
|
||||
if (propertyName == "asp-route-")
|
||||
{
|
||||
output.Attributes.Add("asp-route-foo", "bar");
|
||||
anchorTagHelper.RouteValues.Add("name", "value");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
allAttributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "id", "myform" },
|
||||
{ "asp-route-foo", "bar" },
|
||||
{ "asp-route-name", "value" },
|
||||
{ "asp-action", "index" },
|
||||
{ "asp-controller", "home" },
|
||||
{ "method", "post" },
|
||||
|
|
@ -48,7 +48,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "id", "myform" },
|
||||
{ "asp-route-foo", "bar" },
|
||||
});
|
||||
output.PostContent.SetContent("Something");
|
||||
var urlHelper = new Mock<IUrlHelper>();
|
||||
|
|
@ -68,6 +67,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Controller = "home",
|
||||
Generator = htmlGenerator,
|
||||
ViewContext = viewContext,
|
||||
RouteValues =
|
||||
{
|
||||
{ "name", "value" },
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
|
|
@ -145,7 +148,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_BindsRouteValuesFromTagHelperOutput()
|
||||
public async Task ProcessAsync_BindsRouteValues()
|
||||
{
|
||||
// Arrange
|
||||
var testViewContext = CreateViewContext();
|
||||
|
|
@ -163,11 +166,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var expectedAttribute = new TagHelperAttribute("asp-ROUTEE-NotRoute", "something");
|
||||
var output = new TagHelperOutput(
|
||||
"form",
|
||||
attributes: new TagHelperAttributeList()
|
||||
{
|
||||
{ "asp-route-val", "hello" },
|
||||
{ "asp-roUte--Foo", "bar" }
|
||||
});
|
||||
attributes: new TagHelperAttributeList());
|
||||
output.Attributes.Add(expectedAttribute);
|
||||
|
||||
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||
|
|
@ -190,8 +189,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Assert.Equal(2, routeValueDictionary.Count);
|
||||
var routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("val"));
|
||||
Assert.Equal("hello", routeValue.Value);
|
||||
routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("-Foo"));
|
||||
Assert.Equal("bar", routeValue.Value);
|
||||
routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("-Name"));
|
||||
Assert.Equal("Value", routeValue.Value);
|
||||
})
|
||||
.Returns(new TagBuilder("form", new CommonTestEncoder()))
|
||||
.Verifiable();
|
||||
|
|
@ -201,6 +200,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
AntiForgery = false,
|
||||
Generator = generator.Object,
|
||||
ViewContext = testViewContext,
|
||||
RouteValues =
|
||||
{
|
||||
{ "val", "hello" },
|
||||
{ "-Name", "Value" },
|
||||
},
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
|
|
@ -237,7 +241,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
attributes: new TagHelperAttributeList());
|
||||
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||
generator
|
||||
.Setup(mock => mock.GenerateForm(viewContext, "Index", "Home", null, null, null))
|
||||
.Setup(mock => mock.GenerateForm(
|
||||
viewContext,
|
||||
"Index",
|
||||
"Home",
|
||||
It.IsAny<IDictionary<string, object>>(),
|
||||
null,
|
||||
null))
|
||||
.Returns(new TagBuilder("form", new CommonTestEncoder()))
|
||||
.Verifiable();
|
||||
var formTagHelper = new FormTagHelper
|
||||
|
|
@ -280,16 +290,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
});
|
||||
var output = new TagHelperOutput(
|
||||
"form",
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "asp-route-foo", "bar" }
|
||||
});
|
||||
attributes: new TagHelperAttributeList());
|
||||
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||
generator
|
||||
.Setup(mock => mock.GenerateRouteForm(
|
||||
viewContext,
|
||||
"Default",
|
||||
It.Is<Dictionary<string, object>>(m => string.Equals(m["foo"], "bar")),
|
||||
It.Is<Dictionary<string, object>>(m => string.Equals(m["name"], "value")),
|
||||
null,
|
||||
null))
|
||||
.Returns(new TagBuilder("form", new CommonTestEncoder()))
|
||||
|
|
@ -300,6 +307,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Route = "Default",
|
||||
Generator = generator.Object,
|
||||
ViewContext = viewContext,
|
||||
RouteValues =
|
||||
{
|
||||
{ "name", "value" },
|
||||
},
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
|
|
@ -384,7 +395,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
});
|
||||
if (propertyName == "asp-route-")
|
||||
{
|
||||
tagHelperOutput.Attributes.Add("asp-route-foo", "bar");
|
||||
formTagHelper.RouteValues.Add("name", "value");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -222,7 +222,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
});
|
||||
var expectedAttribute = new TagHelperAttribute("type", "btn");
|
||||
tagHelperOutput.Attributes.Add(expectedAttribute);
|
||||
var attributes = tagHelperOutput.FindPrefixedAttributes("route-");
|
||||
|
||||
var attributes = tagHelperOutput.Attributes
|
||||
.Where(item => item.Name.StartsWith("route-", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
// Act
|
||||
tagHelperOutput.RemoveRange(attributes);
|
||||
|
|
@ -264,19 +267,21 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
"p",
|
||||
attributes: new TagHelperAttributeList()
|
||||
{
|
||||
{ "routeHello", "World" },
|
||||
{ "Routee-I", "Am" }
|
||||
{ "route-Hello", "World" },
|
||||
{ "Route-I", "Am" }
|
||||
});
|
||||
var expectedAttribute = new TagHelperAttribute("type", "btn");
|
||||
tagHelperOutput.Attributes.Add(expectedAttribute);
|
||||
|
||||
var attributes = tagHelperOutput.Attributes
|
||||
.Where(item => item.Name.StartsWith("route-", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Act
|
||||
var attributes = tagHelperOutput.FindPrefixedAttributes("route-");
|
||||
tagHelperOutput.RemoveRange(attributes);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(attributes);
|
||||
var attribute = Assert.Single(tagHelperOutput.Attributes, attr => attr.Name.Equals("routeHello"));
|
||||
Assert.Equal(attribute.Value, "World");
|
||||
attribute = Assert.Single(tagHelperOutput.Attributes, attr => attr.Name.Equals("Routee-I"));
|
||||
Assert.Equal(attribute.Value, "Am");
|
||||
var attribute = Assert.Single(tagHelperOutput.Attributes);
|
||||
Assert.Equal(expectedAttribute, attribute);
|
||||
}
|
||||
|
||||
public static TheoryData MultipleAttributeSameNameData
|
||||
|
|
|
|||
Loading…
Reference in New Issue