[Fixes #4013] Added support for areas in 'a' and 'form' tag helpers

This commit is contained in:
jacalvar 2016-02-12 12:47:58 -08:00
parent 65858b8d8b
commit ac23e5aec6
6 changed files with 396 additions and 13 deletions

View File

@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// </summary> /// </summary>
[HtmlTargetElement("a", Attributes = ActionAttributeName)] [HtmlTargetElement("a", Attributes = ActionAttributeName)]
[HtmlTargetElement("a", Attributes = ControllerAttributeName)] [HtmlTargetElement("a", Attributes = ControllerAttributeName)]
[HtmlTargetElement("a", Attributes = AreaAttributeName)]
[HtmlTargetElement("a", Attributes = FragmentAttributeName)] [HtmlTargetElement("a", Attributes = FragmentAttributeName)]
[HtmlTargetElement("a", Attributes = HostAttributeName)] [HtmlTargetElement("a", Attributes = HostAttributeName)]
[HtmlTargetElement("a", Attributes = ProtocolAttributeName)] [HtmlTargetElement("a", Attributes = ProtocolAttributeName)]
@ -24,6 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ {
private const string ActionAttributeName = "asp-action"; private const string ActionAttributeName = "asp-action";
private const string ControllerAttributeName = "asp-controller"; private const string ControllerAttributeName = "asp-controller";
private const string AreaAttributeName = "asp-area";
private const string FragmentAttributeName = "asp-fragment"; private const string FragmentAttributeName = "asp-fragment";
private const string HostAttributeName = "asp-host"; private const string HostAttributeName = "asp-host";
private const string ProtocolAttributeName = "asp-protocol"; private const string ProtocolAttributeName = "asp-protocol";
@ -67,6 +69,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlAttributeName(ControllerAttributeName)] [HtmlAttributeName(ControllerAttributeName)]
public string Controller { get; set; } public string Controller { get; set; }
/// <summary>
/// The name of the area.
/// </summary>
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
[HtmlAttributeName(AreaAttributeName)]
public string Area { get; set; }
/// <summary> /// <summary>
/// The protocol for the URL, such as &quot;http&quot; or &quot;https&quot;. /// The protocol for the URL, such as &quot;http&quot; or &quot;https&quot;.
/// </summary> /// </summary>
@ -147,6 +156,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ {
if (Action != null || if (Action != null ||
Controller != null || Controller != null ||
Area != null ||
Route != null || Route != null ||
Protocol != null || Protocol != null ||
Host != null || Host != null ||
@ -159,6 +169,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
"<a>", "<a>",
ActionAttributeName, ActionAttributeName,
ControllerAttributeName, ControllerAttributeName,
AreaAttributeName,
RouteAttributeName, RouteAttributeName,
ProtocolAttributeName, ProtocolAttributeName,
HostAttributeName, HostAttributeName,
@ -180,6 +191,17 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
} }
} }
if (Area != null)
{
if (routeValues == null)
{
routeValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
// Unconditionally replace any value from asp-route-area.
routeValues["area"] = Area;
}
TagBuilder tagBuilder; TagBuilder tagBuilder;
if (Route == null) if (Route == null)
{ {

View File

@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// </summary> /// </summary>
[HtmlTargetElement("form", Attributes = ActionAttributeName)] [HtmlTargetElement("form", Attributes = ActionAttributeName)]
[HtmlTargetElement("form", Attributes = AntiforgeryAttributeName)] [HtmlTargetElement("form", Attributes = AntiforgeryAttributeName)]
[HtmlTargetElement("form", Attributes = AreaAttributeName)]
[HtmlTargetElement("form", Attributes = ControllerAttributeName)] [HtmlTargetElement("form", Attributes = ControllerAttributeName)]
[HtmlTargetElement("form", Attributes = RouteAttributeName)] [HtmlTargetElement("form", Attributes = RouteAttributeName)]
[HtmlTargetElement("form", Attributes = RouteValuesDictionaryName)] [HtmlTargetElement("form", Attributes = RouteValuesDictionaryName)]
@ -23,6 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ {
private const string ActionAttributeName = "asp-action"; private const string ActionAttributeName = "asp-action";
private const string AntiforgeryAttributeName = "asp-antiforgery"; private const string AntiforgeryAttributeName = "asp-antiforgery";
private const string AreaAttributeName = "asp-area";
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 RouteValuesDictionaryName = "asp-all-route-data"; private const string RouteValuesDictionaryName = "asp-all-route-data";
@ -66,6 +68,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlAttributeName(ControllerAttributeName)] [HtmlAttributeName(ControllerAttributeName)]
public string Controller { get; set; } public string Controller { get; set; }
/// <summary>
/// The name of the area.
/// </summary>
[HtmlAttributeName(AreaAttributeName)]
public string Area { get; set; }
/// <summary> /// <summary>
/// Whether the antiforgery token should be generated. /// Whether the antiforgery token should be generated.
/// </summary> /// </summary>
@ -141,7 +149,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// 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 || RouteValues.Count != 0) if (Action != null || Controller != null || Area != 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(
@ -150,6 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
HtmlActionAttributeName, HtmlActionAttributeName,
ActionAttributeName, ActionAttributeName,
ControllerAttributeName, ControllerAttributeName,
AreaAttributeName,
RouteAttributeName, RouteAttributeName,
RouteValuesPrefix)); RouteValuesPrefix));
} }
@ -171,6 +180,17 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
} }
} }
if (Area != null)
{
if (routeValues == null)
{
routeValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
// Unconditionally replace any value from asp-route-area.
routeValues["area"] = Area;
}
TagBuilder tagBuilder; TagBuilder tagBuilder;
if (Route == null) if (Route == null)
{ {

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
} }
/// <summary> /// <summary>
/// Cannot override the '{8}' attribute for {0}. An {0} with a specified '{8}' must not have attributes starting with '{7}' or an '{1}', '{2}', '{3}', '{4}', '{5}', or '{6}' attribute. /// Cannot override the '{9}' attribute for {0}. An {0} with a specified '{9}' must not have attributes starting with '{8}' or an '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', or '{7}' attribute.
/// </summary> /// </summary>
internal static string AnchorTagHelper_CannotOverrideHref internal static string AnchorTagHelper_CannotOverrideHref
{ {
@ -35,15 +35,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
} }
/// <summary> /// <summary>
/// Cannot override the '{8}' attribute for {0}. An {0} with a specified '{8}' must not have attributes starting with '{7}' or an '{1}', '{2}', '{3}', '{4}', '{5}', or '{6}' attribute. /// Cannot override the '{9}' attribute for {0}. An {0} with a specified '{9}' must not have attributes starting with '{8}' or an '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', or '{7}' attribute.
/// </summary> /// </summary>
internal static string FormatAnchorTagHelper_CannotOverrideHref(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8) internal static string FormatAnchorTagHelper_CannotOverrideHref(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8, object p9)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotOverrideHref"), p0, p1, p2, p3, p4, p5, p6, p7, p8); return string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotOverrideHref"), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
} }
/// <summary> /// <summary>
/// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{5}' or an '{2}' or '{3}' or '{4}' attribute. /// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{6}' or an '{2}' or '{3}' or '{4}' or '{5}' attribute.
/// </summary> /// </summary>
internal static string FormTagHelper_CannotOverrideAction internal static string FormTagHelper_CannotOverrideAction
{ {
@ -51,11 +51,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
} }
/// <summary> /// <summary>
/// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{5}' or an '{2}' or '{3}' or '{4}' attribute. /// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{6}' or an '{2}' or '{3}' or '{4}' or '{5}' attribute.
/// </summary> /// </summary>
internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4, object p5) internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4, p5); return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4, p5, p6);
} }
/// <summary> /// <summary>

View File

@ -121,10 +121,10 @@
<value>Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value> <value>Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
</data> </data>
<data name="AnchorTagHelper_CannotOverrideHref" xml:space="preserve"> <data name="AnchorTagHelper_CannotOverrideHref" xml:space="preserve">
<value>Cannot override the '{8}' attribute for {0}. An {0} with a specified '{8}' must not have attributes starting with '{7}' or an '{1}', '{2}', '{3}', '{4}', '{5}', or '{6}' attribute.</value> <value>Cannot override the '{9}' attribute for {0}. An {0} with a specified '{9}' must not have attributes starting with '{8}' or an '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', or '{7}' attribute.</value>
</data> </data>
<data name="FormTagHelper_CannotOverrideAction" xml:space="preserve"> <data name="FormTagHelper_CannotOverrideAction" xml:space="preserve">
<value>Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{5}' or an '{2}' or '{3}' or '{4}' attribute.</value> <value>Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{6}' or an '{2}' or '{3}' or '{4}' or '{5}' attribute.</value>
</data> </data>
<data name="InputTagHelper_InvalidExpressionResult" xml:space="preserve"> <data name="InputTagHelper_InvalidExpressionResult" xml:space="preserve">
<value>Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'.</value> <value>Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'.</value>

View File

@ -187,6 +187,178 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.True(output.Content.IsEmpty); Assert.True(output.Content.IsEmpty);
} }
[Fact]
public async Task ProcessAsync_AddsAreaToRouteValuesAndCallsIntoActionLinkWithExpectedParameters()
{
// Arrange
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"a",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
output.Content.SetContent(string.Empty);
var generator = new Mock<IHtmlGenerator>();
var expectedRouteValues = new Dictionary<string, object> { { "area", "Admin" } };
generator
.Setup(mock => mock.GenerateActionLink(
It.IsAny<ViewContext>(),
string.Empty,
"Index",
"Home",
"http",
"contoso.com",
"hello=world",
expectedRouteValues,
null))
.Returns(new TagBuilder("a"))
.Verifiable();
var anchorTagHelper = new AnchorTagHelper(generator.Object)
{
Action = "Index",
Controller = "Home",
Area = "Admin",
Fragment = "hello=world",
Host = "contoso.com",
Protocol = "http",
};
// Act
await anchorTagHelper.ProcessAsync(context, output);
// Assert
generator.Verify();
Assert.Equal("a", output.TagName);
Assert.Empty(output.Attributes);
Assert.True(output.Content.IsEmpty);
}
[Fact]
public async Task ProcessAsync_AspAreaOverridesAspRouteArea()
{
// Arrange
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"a",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
output.Content.SetContent(string.Empty);
var generator = new Mock<IHtmlGenerator>();
var expectedRouteValues = new Dictionary<string, object> { { "area", "Admin" } };
generator
.Setup(mock => mock.GenerateActionLink(
It.IsAny<ViewContext>(),
string.Empty,
"Index",
"Home",
"http",
"contoso.com",
"hello=world",
expectedRouteValues,
null))
.Returns(new TagBuilder("a"))
.Verifiable();
var anchorTagHelper = new AnchorTagHelper(generator.Object)
{
Action = "Index",
Controller = "Home",
Area = "Admin",
Fragment = "hello=world",
Host = "contoso.com",
Protocol = "http",
RouteValues = new Dictionary<string, string> { { "area", "Home" } }
};
// Act
await anchorTagHelper.ProcessAsync(context, output);
// Assert
generator.Verify();
Assert.Equal("a", output.TagName);
Assert.Empty(output.Attributes);
Assert.True(output.Content.IsEmpty);
}
[Fact]
public async Task ProcessAsync_EmptyStringOnAspAreaIsPassedThroughToRouteValues()
{
// Arrange
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"a",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
output.Content.SetContent(string.Empty);
var generator = new Mock<IHtmlGenerator>();
var expectedRouteValues = new Dictionary<string, object> { { "area", string.Empty } };
generator
.Setup(mock => mock.GenerateActionLink(
It.IsAny<ViewContext>(),
string.Empty,
"Index",
"Home",
"http",
"contoso.com",
"hello=world",
expectedRouteValues,
null))
.Returns(new TagBuilder("a"))
.Verifiable();
var anchorTagHelper = new AnchorTagHelper(generator.Object)
{
Action = "Index",
Controller = "Home",
Area = string.Empty,
Fragment = "hello=world",
Host = "contoso.com",
Protocol = "http"
};
// Act
await anchorTagHelper.ProcessAsync(context, output);
// Assert
generator.Verify();
Assert.Equal("a", output.TagName);
Assert.Empty(output.Attributes);
Assert.True(output.Content.IsEmpty);
}
[Theory] [Theory]
[InlineData("Action")] [InlineData("Action")]
[InlineData("Controller")] [InlineData("Controller")]
@ -221,7 +393,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var expectedErrorMessage = "Cannot override the 'href' attribute for <a>. An <a> with a specified " + var expectedErrorMessage = "Cannot override the 'href' attribute for <a>. An <a> with a specified " +
"'href' must not have attributes starting with 'asp-route-' or an " + "'href' must not have attributes starting with 'asp-route-' or an " +
"'asp-action', 'asp-controller', 'asp-route', 'asp-protocol', 'asp-host', or " + "'asp-action', 'asp-controller', 'asp-area', 'asp-route', 'asp-protocol', 'asp-host', or " +
"'asp-fragment' attribute."; "'asp-fragment' attribute.";
var context = new TagHelperContext( var context = new TagHelperContext(

View File

@ -283,6 +283,175 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.Empty(output.PostContent.GetContent()); Assert.Empty(output.PostContent.GetContent());
} }
[Fact]
public async Task ProcessAsync_AspAreaAddsAreaToRouteValues()
{
// Arrange
var viewContext = CreateViewContext();
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"form",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
var expectedRouteValues = new Dictionary<string, object> { { "area", "Admin" } };
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
generator
.Setup(mock => mock.GenerateForm(
viewContext,
"Index",
"Home",
expectedRouteValues,
null,
null))
.Returns(new TagBuilder("form"))
.Verifiable();
var formTagHelper = new FormTagHelper(generator.Object)
{
Action = "Index",
Antiforgery = false,
Controller = "Home",
Area = "Admin",
ViewContext = viewContext,
};
// Act
await formTagHelper.ProcessAsync(context, output);
// Assert
generator.Verify();
Assert.Equal("form", output.TagName);
Assert.Equal(TagMode.StartTagAndEndTag, output.TagMode);
Assert.Empty(output.Attributes);
Assert.Empty(output.PreElement.GetContent());
Assert.Empty(output.PreContent.GetContent());
Assert.True(output.Content.IsEmpty);
Assert.Empty(output.PostContent.GetContent());
}
[Fact]
public async Task ProcessAsync_EmptyStringOnAspAreaIsPassedThroughToRouteValues()
{
// Arrange
var viewContext = CreateViewContext();
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"form",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
var expectedRouteValues = new Dictionary<string, object> { { "area", string.Empty } };
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
generator
.Setup(mock => mock.GenerateForm(
viewContext,
"Index",
"Home",
expectedRouteValues,
null,
null))
.Returns(new TagBuilder("form"))
.Verifiable();
var formTagHelper = new FormTagHelper(generator.Object)
{
Action = "Index",
Antiforgery = false,
Controller = "Home",
Area = string.Empty,
ViewContext = viewContext,
};
// Act
await formTagHelper.ProcessAsync(context, output);
// Assert
generator.Verify();
Assert.Equal("form", output.TagName);
Assert.Equal(TagMode.StartTagAndEndTag, output.TagMode);
Assert.Empty(output.Attributes);
Assert.Empty(output.PreElement.GetContent());
Assert.Empty(output.PreContent.GetContent());
Assert.True(output.Content.IsEmpty);
Assert.Empty(output.PostContent.GetContent());
}
[Fact]
public async Task ProcessAsync_AspAreaOverridesAspRouteArea()
{
// Arrange
var viewContext = CreateViewContext();
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"form",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
var expectedRouteValues = new Dictionary<string, object> { { "area", "Admin" } };
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
generator
.Setup(mock => mock.GenerateForm(
viewContext,
"Index",
"Home",
expectedRouteValues,
null,
null))
.Returns(new TagBuilder("form"))
.Verifiable();
var formTagHelper = new FormTagHelper(generator.Object)
{
Action = "Index",
Antiforgery = false,
Controller = "Home",
Area = "Admin",
RouteValues = new Dictionary<string, string> { { "area", "Client" } },
ViewContext = viewContext,
};
// Act
await formTagHelper.ProcessAsync(context, output);
// Assert
generator.Verify();
Assert.Equal("form", output.TagName);
Assert.Equal(TagMode.StartTagAndEndTag, output.TagMode);
Assert.Empty(output.Attributes);
Assert.Empty(output.PreElement.GetContent());
Assert.Empty(output.PreContent.GetContent());
Assert.True(output.Content.IsEmpty);
Assert.Empty(output.PostContent.GetContent());
}
[Fact] [Fact]
public async Task ProcessAsync_CallsIntoGenerateRouteFormWithExpectedParameters() public async Task ProcessAsync_CallsIntoGenerateRouteFormWithExpectedParameters()
{ {
@ -415,7 +584,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var expectedErrorMessage = "Cannot override the 'action' attribute for <form>. A <form> with a specified " + var expectedErrorMessage = "Cannot override the 'action' attribute for <form>. A <form> with a specified " +
"'action' must not have attributes starting with 'asp-route-' or an " + "'action' must not have attributes starting with 'asp-route-' or an " +
"'asp-action' or 'asp-controller' or 'asp-route' attribute."; "'asp-action' or 'asp-controller' or 'asp-area' or 'asp-route' attribute.";
var context = new TagHelperContext( var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList( allAttributes: new TagHelperAttributeList(