- 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:
Doug Bunting 2015-05-04 11:13:37 -07:00
parent 337bbad51d
commit 517c013882
16 changed files with 225 additions and 149 deletions

View File

@ -72,6 +72,8 @@ namespace Microsoft.AspNet.Mvc.Razor
ExecutionContextAddTagHelperAttributeMethodName = ExecutionContextAddTagHelperAttributeMethodName =
nameof(TagHelperExecutionContext.AddTagHelperAttribute), nameof(TagHelperExecutionContext.AddTagHelperAttribute),
ExecutionContextAddHtmlAttributeMethodName = nameof(TagHelperExecutionContext.AddHtmlAttribute), ExecutionContextAddHtmlAttributeMethodName = nameof(TagHelperExecutionContext.AddHtmlAttribute),
ExecutionContextAddMinimizedHtmlAttributeMethodName =
nameof(TagHelperExecutionContext.AddMinimizedHtmlAttribute),
ExecutionContextOutputPropertyName = nameof(TagHelperExecutionContext.Output), ExecutionContextOutputPropertyName = nameof(TagHelperExecutionContext.Output),
RunnerTypeName = typeof(TagHelperRunner).FullName, RunnerTypeName = typeof(TagHelperRunner).FullName,
@ -85,6 +87,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// Can't use nameof because RazorPage is not accessible here. // Can't use nameof because RazorPage is not accessible here.
CreateTagHelperMethodName = "CreateTagHelper", CreateTagHelperMethodName = "CreateTagHelper",
FormatInvalidIndexerAssignmentMethodName = "InvalidTagHelperIndexerAssignment",
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope", StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope", EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
@ -211,7 +214,7 @@ namespace Microsoft.AspNet.Mvc.Razor
sourceFileName = _pathNormalizer.NormalizePath(sourceFileName); sourceFileName = _pathNormalizer.NormalizePath(sourceFileName);
var inheritedCodeTrees = ChunkInheritanceUtility.GetInheritedCodeTrees(sourceFileName); var inheritedCodeTrees = ChunkInheritanceUtility.GetInheritedCodeTrees(sourceFileName);
return new MvcRazorParser(razorParser, inheritedCodeTrees, DefaultInheritedChunks); return new MvcRazorParser(razorParser, inheritedCodeTrees, DefaultInheritedChunks, ModelExpressionType);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,8 +1,10 @@
// 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.Linq; using System.Linq;
using Microsoft.AspNet.Mvc.Razor.Host;
using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Parser;
@ -20,6 +22,7 @@ namespace Microsoft.AspNet.Mvc.Razor
public class MvcRazorParser : RazorParser public class MvcRazorParser : RazorParser
{ {
private readonly IEnumerable<TagHelperDirectiveDescriptor> _globalImportDirectiveDescriptors; private readonly IEnumerable<TagHelperDirectiveDescriptor> _globalImportDirectiveDescriptors;
private readonly string _modelExpressionTypeName;
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="MvcRazorParser"/>. /// Initializes a new instance of <see cref="MvcRazorParser"/>.
@ -32,13 +35,16 @@ namespace Microsoft.AspNet.Mvc.Razor
public MvcRazorParser( public MvcRazorParser(
[NotNull] RazorParser parser, [NotNull] RazorParser parser,
[NotNull] IReadOnlyList<CodeTree> inheritedCodeTrees, [NotNull] IReadOnlyList<CodeTree> inheritedCodeTrees,
[NotNull] IReadOnlyList<Chunk> defaultInheritedChunks) [NotNull] IReadOnlyList<Chunk> defaultInheritedChunks,
[NotNull] string modelExpressionTypeName)
: base(parser) : base(parser)
{ {
// Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks // Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks
_globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors( _globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors(
inheritedCodeTrees, inheritedCodeTrees,
defaultInheritedChunks); defaultInheritedChunks);
_modelExpressionTypeName = modelExpressionTypeName;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -50,7 +56,27 @@ namespace Microsoft.AspNet.Mvc.Razor
TagHelperDescriptorResolver, TagHelperDescriptorResolver,
_globalImportDirectiveDescriptors, _globalImportDirectiveDescriptors,
errorSink); 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( private static IEnumerable<TagHelperDirectiveDescriptor> GetTagHelperDirectiveDescriptors(

View File

@ -106,6 +106,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Host
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0); 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) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -135,4 +135,7 @@
<data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve"> <data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve">
<value>Only one '{0}' statement is allowed in a file.</value> <value>Only one '{0}' statement is allowed in a file.</value>
</data> </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> </root>

View File

@ -410,6 +410,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return GetString("GeneratedCodeFileName"); 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) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -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> /// <summary>
/// Creates and activates a <see cref="ITagHelper"/>. /// Creates and activates a <see cref="ITagHelper"/>.
/// </summary> /// </summary>

View File

@ -192,4 +192,7 @@
<data name="GeneratedCodeFileName" xml:space="preserve"> <data name="GeneratedCodeFileName" xml:space="preserve">
<value>Generated Code</value> <value>Generated Code</value>
</data> </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> </root>

View File

@ -21,7 +21,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
private const string HostAttributeName = "asp-host"; private const string HostAttributeName = "asp-host";
private const string ProtocolAttributeName = "asp-protocol"; private const string ProtocolAttributeName = "asp-protocol";
private const string RouteAttributeName = "asp-route"; 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"; private const string Href = "href";
[Activate] [Activate]
@ -69,6 +70,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[HtmlAttributeName(RouteAttributeName)] [HtmlAttributeName(RouteAttributeName)]
public string Route { get; set; } 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 /> /// <inheritdoc />
/// <remarks>Does nothing if user provides an <c>href</c> attribute.</remarks> /// <remarks>Does nothing if user provides an <c>href</c> attribute.</remarks>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
@ -79,8 +87,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// </exception> /// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output) 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 "href" is already set, it means the user is attempting to use a normal anchor.
if (output.Attributes.ContainsName(Href)) if (output.Attributes.ContainsName(Href))
{ {
@ -90,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Protocol != null || Protocol != null ||
Host != null || Host != null ||
Fragment != null || Fragment != null ||
routePrefixedAttributes.Any()) RouteValues.Count != 0)
{ {
// User specified an href and one of the bound attributes; can't determine the href attribute. // User specified an href and one of the bound attributes; can't determine the href attribute.
throw new InvalidOperationException( throw new InvalidOperationException(
@ -102,15 +108,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
ProtocolAttributeName, ProtocolAttributeName,
HostAttributeName, HostAttributeName,
FragmentAttributeName, FragmentAttributeName,
RouteAttributePrefix, RouteValuesPrefix,
Href)); Href));
} }
} }
else else
{ {
TagBuilder tagBuilder; // Convert from Dictionary<string, string> to Dictionary<string, object>.
var routeValues = GetRouteValues(output, routePrefixedAttributes); var routeValues = RouteValues.ToDictionary(
kvp => kvp.Key,
kvp => (object)kvp.Value,
StringComparer.OrdinalIgnoreCase);
TagBuilder tagBuilder;
if (Route == null) if (Route == null)
{ {
tagBuilder = Generator.GenerateActionLink(linkText: string.Empty, 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;
}
} }
} }

View File

@ -18,7 +18,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
private const string AntiForgeryAttributeName = "asp-anti-forgery"; private const string AntiForgeryAttributeName = "asp-anti-forgery";
private const string ControllerAttributeName = "asp-controller"; private const string ControllerAttributeName = "asp-controller";
private const string RouteAttributeName = "asp-route"; 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"; private const string HtmlActionAttributeName = "action";
[Activate] [Activate]
@ -57,6 +58,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[HtmlAttributeName(RouteAttributeName)] [HtmlAttributeName(RouteAttributeName)]
public string Route { get; set; } 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 /> /// <inheritdoc />
/// <remarks> /// <remarks>
/// Does nothing if user provides an <c>action</c> attribute and <see cref="AntiForgery"/> is <c>null</c> or /// 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) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
var antiForgeryDefault = true; 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 "action" is already set, it means the user is attempting to use a normal <form>.
if (output.Attributes.ContainsName(HtmlActionAttributeName)) 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. // User also specified bound attributes we cannot use.
throw new InvalidOperationException( throw new InvalidOperationException(
@ -84,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
ActionAttributeName, ActionAttributeName,
ControllerAttributeName, ControllerAttributeName,
RouteAttributeName, RouteAttributeName,
RouteAttributePrefix)); RouteValuesPrefix));
} }
// User is using the FormTagHelper like a normal <form> tag. Anti-forgery default should be false to // 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 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; TagBuilder tagBuilder;
var routeValues = GetRouteValues(output, routePrefixedAttributes);
if (Route == null) if (Route == null)
{ {
tagBuilder = Generator.GenerateForm( 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;
}
} }
} }

View File

@ -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> /// <summary>
/// Merges the given <paramref name="tagBuilder"/>'s <see cref="TagBuilder.Attributes"/> into the /// Merges the given <paramref name="tagBuilder"/>'s <see cref="TagBuilder.Attributes"/> into the
/// <paramref name="tagHelperOutput"/>. /// <paramref name="tagHelperOutput"/>.

View File

@ -1071,9 +1071,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
var testableBinder = new TestableMutableObjectModelBinder(); var testableBinder = new TestableMutableObjectModelBinder();
// The [DefaultValue] on ValueTypeRequiredWithDefaultValue is ignored by model binding.
var expectedValue = 0;
// Make ValueTypeRequired invalid. // Make ValueTypeRequired invalid.
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired)); var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired));
dto.Results[propertyMetadata] = new ModelBindingResult( dto.Results[propertyMetadata] = new ModelBindingResult(

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Parser;
@ -230,7 +231,7 @@ namespace Microsoft.AspNet.Mvc.Razor
public TestableMvcRazorParser(RazorParser parser, public TestableMvcRazorParser(RazorParser parser,
IReadOnlyList<CodeTree> codeTrees, IReadOnlyList<CodeTree> codeTrees,
IReadOnlyList<Chunk> defaultInheritedChunks) IReadOnlyList<Chunk> defaultInheritedChunks)
: base(parser, codeTrees, defaultInheritedChunks) : base(parser, codeTrees, defaultInheritedChunks, typeof(ModelExpression).FullName)
{ {
} }

View File

@ -25,7 +25,11 @@ namespace Microsoft.AspNet.Mvc.Razor
ModelExpressionTypeName = modelExpressionType, ModelExpressionTypeName = modelExpressionType,
CreateModelExpressionMethodName = "SomeMethod" 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 writer = new CSharpCodeWriter();
var generatorContext = new CodeGeneratorContext(host: null, var generatorContext = new CodeGeneratorContext(host: null,
className: string.Empty, className: string.Empty,

View File

@ -8,7 +8,6 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.Framework.WebEncoders;
using Microsoft.Framework.WebEncoders.Testing; using Microsoft.Framework.WebEncoders.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -28,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
allAttributes: new TagHelperAttributeList allAttributes: new TagHelperAttributeList
{ {
{ "id", "myanchor" }, { "id", "myanchor" },
{ "asp-route-foo", "bar" }, { "asp-route-name", "value" },
{ "asp-action", "index" }, { "asp-action", "index" },
{ "asp-controller", "home" }, { "asp-controller", "home" },
{ "asp-fragment", "hello=world" }, { "asp-fragment", "hello=world" },
@ -48,7 +47,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attributes: new TagHelperAttributeList attributes: new TagHelperAttributeList
{ {
{ "id", "myanchor" }, { "id", "myanchor" },
{ "asp-route-foo", "bar" },
}); });
output.Content.SetContent("Something"); output.Content.SetContent("Something");
@ -68,6 +66,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Generator = htmlGenerator, Generator = htmlGenerator,
Host = "contoso.com", Host = "contoso.com",
Protocol = "http", Protocol = "http",
RouteValues =
{
{ "name", "value" },
},
}; };
// Act // Act
@ -106,7 +108,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict); var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
generator generator
.Setup(mock => mock.GenerateRouteLink( .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())) .Returns(new TagBuilder("a", new CommonTestEncoder()))
.Verifiable(); .Verifiable();
var anchorTagHelper = new AnchorTagHelper var anchorTagHelper = new AnchorTagHelper
@ -149,7 +157,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var generator = new Mock<IHtmlGenerator>(); var generator = new Mock<IHtmlGenerator>();
generator generator
.Setup(mock => mock.GenerateActionLink( .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())) .Returns(new TagBuilder("a", new CommonTestEncoder()))
.Verifiable(); .Verifiable();
var anchorTagHelper = new AnchorTagHelper var anchorTagHelper = new AnchorTagHelper
@ -190,7 +205,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}); });
if (propertyName == "asp-route-") if (propertyName == "asp-route-")
{ {
output.Attributes.Add("asp-route-foo", "bar"); anchorTagHelper.RouteValues.Add("name", "value");
} }
else else
{ {

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
allAttributes: new TagHelperAttributeList allAttributes: new TagHelperAttributeList
{ {
{ "id", "myform" }, { "id", "myform" },
{ "asp-route-foo", "bar" }, { "asp-route-name", "value" },
{ "asp-action", "index" }, { "asp-action", "index" },
{ "asp-controller", "home" }, { "asp-controller", "home" },
{ "method", "post" }, { "method", "post" },
@ -48,7 +48,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attributes: new TagHelperAttributeList attributes: new TagHelperAttributeList
{ {
{ "id", "myform" }, { "id", "myform" },
{ "asp-route-foo", "bar" },
}); });
output.PostContent.SetContent("Something"); output.PostContent.SetContent("Something");
var urlHelper = new Mock<IUrlHelper>(); var urlHelper = new Mock<IUrlHelper>();
@ -68,6 +67,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Controller = "home", Controller = "home",
Generator = htmlGenerator, Generator = htmlGenerator,
ViewContext = viewContext, ViewContext = viewContext,
RouteValues =
{
{ "name", "value" },
},
}; };
// Act // Act
@ -145,7 +148,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
} }
[Fact] [Fact]
public async Task ProcessAsync_BindsRouteValuesFromTagHelperOutput() public async Task ProcessAsync_BindsRouteValues()
{ {
// Arrange // Arrange
var testViewContext = CreateViewContext(); var testViewContext = CreateViewContext();
@ -163,11 +166,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var expectedAttribute = new TagHelperAttribute("asp-ROUTEE-NotRoute", "something"); var expectedAttribute = new TagHelperAttribute("asp-ROUTEE-NotRoute", "something");
var output = new TagHelperOutput( var output = new TagHelperOutput(
"form", "form",
attributes: new TagHelperAttributeList() attributes: new TagHelperAttributeList());
{
{ "asp-route-val", "hello" },
{ "asp-roUte--Foo", "bar" }
});
output.Attributes.Add(expectedAttribute); output.Attributes.Add(expectedAttribute);
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict); var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
@ -190,8 +189,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal(2, routeValueDictionary.Count); Assert.Equal(2, routeValueDictionary.Count);
var routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("val")); var routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("val"));
Assert.Equal("hello", routeValue.Value); Assert.Equal("hello", routeValue.Value);
routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("-Foo")); routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("-Name"));
Assert.Equal("bar", routeValue.Value); Assert.Equal("Value", routeValue.Value);
}) })
.Returns(new TagBuilder("form", new CommonTestEncoder())) .Returns(new TagBuilder("form", new CommonTestEncoder()))
.Verifiable(); .Verifiable();
@ -201,6 +200,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
AntiForgery = false, AntiForgery = false,
Generator = generator.Object, Generator = generator.Object,
ViewContext = testViewContext, ViewContext = testViewContext,
RouteValues =
{
{ "val", "hello" },
{ "-Name", "Value" },
},
}; };
// Act & Assert // Act & Assert
@ -237,7 +241,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attributes: new TagHelperAttributeList()); attributes: new TagHelperAttributeList());
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict); var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
generator 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())) .Returns(new TagBuilder("form", new CommonTestEncoder()))
.Verifiable(); .Verifiable();
var formTagHelper = new FormTagHelper var formTagHelper = new FormTagHelper
@ -280,16 +290,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}); });
var output = new TagHelperOutput( var output = new TagHelperOutput(
"form", "form",
attributes: new TagHelperAttributeList attributes: new TagHelperAttributeList());
{
{ "asp-route-foo", "bar" }
});
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict); var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
generator generator
.Setup(mock => mock.GenerateRouteForm( .Setup(mock => mock.GenerateRouteForm(
viewContext, viewContext,
"Default", "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,
null)) null))
.Returns(new TagBuilder("form", new CommonTestEncoder())) .Returns(new TagBuilder("form", new CommonTestEncoder()))
@ -300,6 +307,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Route = "Default", Route = "Default",
Generator = generator.Object, Generator = generator.Object,
ViewContext = viewContext, ViewContext = viewContext,
RouteValues =
{
{ "name", "value" },
},
}; };
// Act & Assert // Act & Assert
@ -384,7 +395,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}); });
if (propertyName == "asp-route-") if (propertyName == "asp-route-")
{ {
tagHelperOutput.Attributes.Add("asp-route-foo", "bar"); formTagHelper.RouteValues.Add("name", "value");
} }
else else
{ {

View File

@ -222,7 +222,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}); });
var expectedAttribute = new TagHelperAttribute("type", "btn"); var expectedAttribute = new TagHelperAttribute("type", "btn");
tagHelperOutput.Attributes.Add(expectedAttribute); tagHelperOutput.Attributes.Add(expectedAttribute);
var attributes = tagHelperOutput.FindPrefixedAttributes("route-");
var attributes = tagHelperOutput.Attributes
.Where(item => item.Name.StartsWith("route-", StringComparison.OrdinalIgnoreCase))
.ToList();
// Act // Act
tagHelperOutput.RemoveRange(attributes); tagHelperOutput.RemoveRange(attributes);
@ -264,19 +267,21 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
"p", "p",
attributes: new TagHelperAttributeList() attributes: new TagHelperAttributeList()
{ {
{ "routeHello", "World" }, { "route-Hello", "World" },
{ "Routee-I", "Am" } { "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 // Act
var attributes = tagHelperOutput.FindPrefixedAttributes("route-"); tagHelperOutput.RemoveRange(attributes);
// Assert // Assert
Assert.Empty(attributes); var attribute = Assert.Single(tagHelperOutput.Attributes);
var attribute = Assert.Single(tagHelperOutput.Attributes, attr => attr.Name.Equals("routeHello")); Assert.Equal(expectedAttribute, attribute);
Assert.Equal(attribute.Value, "World");
attribute = Assert.Single(tagHelperOutput.Attributes, attr => attr.Name.Equals("Routee-I"));
Assert.Equal(attribute.Value, "Am");
} }
public static TheoryData MultipleAttributeSameNameData public static TheoryData MultipleAttributeSameNameData