[Fixes #1489] Support for named route in form tag helper
This commit is contained in:
parent
37e819ce85
commit
a80a333fea
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
private const string ActionAttributeName = "asp-action";
|
private const string ActionAttributeName = "asp-action";
|
||||||
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 RouteAttributePrefix = "asp-route-";
|
private const string RouteAttributePrefix = "asp-route-";
|
||||||
private const string HtmlActionAttributeName = "action";
|
private const string HtmlActionAttributeName = "action";
|
||||||
|
|
||||||
|
|
@ -47,6 +48,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
[HtmlAttributeName(AntiForgeryAttributeName)]
|
[HtmlAttributeName(AntiForgeryAttributeName)]
|
||||||
public bool? AntiForgery { get; set; }
|
public bool? AntiForgery { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the route.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>.
|
||||||
|
/// </remarks>
|
||||||
|
[HtmlAttributeName(RouteAttributeName)]
|
||||||
|
public string Route { get; set; }
|
||||||
|
|
||||||
/// <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
|
||||||
|
|
@ -64,7 +74,7 @@ namespace Microsoft.AspNet.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.ContainsKey(HtmlActionAttributeName))
|
if (output.Attributes.ContainsKey(HtmlActionAttributeName))
|
||||||
{
|
{
|
||||||
if (Action != null || Controller != null || routePrefixedAttributes.Any())
|
if (Action != null || Controller != null || Route != null || routePrefixedAttributes.Any())
|
||||||
{
|
{
|
||||||
// User also specified bound attributes we cannot use.
|
// User also specified bound attributes we cannot use.
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
|
|
@ -73,6 +83,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
HtmlActionAttributeName,
|
HtmlActionAttributeName,
|
||||||
ActionAttributeName,
|
ActionAttributeName,
|
||||||
ControllerAttributeName,
|
ControllerAttributeName,
|
||||||
|
RouteAttributeName,
|
||||||
RouteAttributePrefix));
|
RouteAttributePrefix));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,13 +93,38 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
TagBuilder tagBuilder;
|
||||||
var routeValues = GetRouteValues(output, routePrefixedAttributes);
|
var routeValues = GetRouteValues(output, routePrefixedAttributes);
|
||||||
var tagBuilder = Generator.GenerateForm(ViewContext,
|
if (Route == null)
|
||||||
Action,
|
{
|
||||||
Controller,
|
tagBuilder = Generator.GenerateForm(
|
||||||
routeValues,
|
ViewContext,
|
||||||
method: null,
|
Action,
|
||||||
htmlAttributes: null);
|
Controller,
|
||||||
|
routeValues,
|
||||||
|
method: null,
|
||||||
|
htmlAttributes: null);
|
||||||
|
}
|
||||||
|
else if (Action != null || Controller != null)
|
||||||
|
{
|
||||||
|
// Route and Action or Controller were specified. Can't determine the action attribute.
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatFormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified(
|
||||||
|
"<form>",
|
||||||
|
RouteAttributeName,
|
||||||
|
ActionAttributeName,
|
||||||
|
ControllerAttributeName,
|
||||||
|
HtmlActionAttributeName));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tagBuilder = Generator.GenerateRouteForm(
|
||||||
|
ViewContext,
|
||||||
|
Route,
|
||||||
|
routeValues,
|
||||||
|
method: null,
|
||||||
|
htmlAttributes: null);
|
||||||
|
}
|
||||||
|
|
||||||
if (tagBuilder != null)
|
if (tagBuilder != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{4}' or an '{2}' or '{3}' attribute.
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormTagHelper_CannotOverrideAction
|
internal static string FormTagHelper_CannotOverrideAction
|
||||||
{
|
{
|
||||||
|
|
@ -51,11 +51,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{4}' or an '{2}' or '{3}' attribute.
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4)
|
internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4, object p5)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4);
|
return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4, p5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -138,6 +138,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperOutput_AttributeDoesNotExist"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperOutput_AttributeDoesNotExist"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified
|
||||||
|
{
|
||||||
|
get { return GetString("FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatFormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified"), p0, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
<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 '{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>
|
||||||
</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 '{4}' or an '{2}' or '{3}' attribute.</value>
|
<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>
|
||||||
</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>
|
||||||
|
|
@ -141,4 +141,7 @@
|
||||||
<data name="TagHelperOutput_AttributeDoesNotExist" xml:space="preserve">
|
<data name="TagHelperOutput_AttributeDoesNotExist" xml:space="preserve">
|
||||||
<value>The attribute '{0}' does not exist in the {1}.</value>
|
<value>The attribute '{0}' does not exist in the {1}.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified" xml:space="preserve">
|
||||||
|
<value>Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -252,11 +252,65 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
Assert.Equal("form", output.TagName);
|
Assert.Equal("form", output.TagName);
|
||||||
Assert.False(output.SelfClosing);
|
Assert.False(output.SelfClosing);
|
||||||
Assert.Empty(output.Attributes);
|
Assert.Empty(output.Attributes);
|
||||||
|
Assert.Empty(output.PreElement.GetContent());
|
||||||
Assert.Empty(output.PreContent.GetContent());
|
Assert.Empty(output.PreContent.GetContent());
|
||||||
Assert.True(output.Content.IsEmpty);
|
Assert.True(output.Content.IsEmpty);
|
||||||
Assert.Empty(output.PostContent.GetContent());
|
Assert.Empty(output.PostContent.GetContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ProcessAsync_CallsIntoGenerateRouteFormWithExpectedParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var viewContext = CreateViewContext();
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new Dictionary<string, object>(),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test",
|
||||||
|
getChildContentAsync: () =>
|
||||||
|
{
|
||||||
|
var tagHelperContent = new DefaultTagHelperContent();
|
||||||
|
tagHelperContent.SetContent("Something");
|
||||||
|
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
||||||
|
});
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
"form",
|
||||||
|
attributes: new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "asp-route-foo", "bar" }
|
||||||
|
});
|
||||||
|
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")),
|
||||||
|
null,
|
||||||
|
null))
|
||||||
|
.Returns(new TagBuilder("form", new CommonTestEncoder()))
|
||||||
|
.Verifiable();
|
||||||
|
var formTagHelper = new FormTagHelper
|
||||||
|
{
|
||||||
|
AntiForgery = false,
|
||||||
|
Route = "Default",
|
||||||
|
Generator = generator.Object,
|
||||||
|
ViewContext = viewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await formTagHelper.ProcessAsync(context, output);
|
||||||
|
generator.Verify();
|
||||||
|
|
||||||
|
Assert.Equal("form", output.TagName);
|
||||||
|
Assert.False(output.SelfClosing);
|
||||||
|
Assert.Empty(output.Attributes);
|
||||||
|
Assert.Empty(output.PreElement.GetContent());
|
||||||
|
Assert.Empty(output.PreContent.GetContent());
|
||||||
|
Assert.True(output.Content.IsEmpty);
|
||||||
|
Assert.Empty(output.PostContent.GetContent());
|
||||||
|
Assert.Empty(output.PostElement.GetContent());
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true, "<input />")]
|
[InlineData(true, "<input />")]
|
||||||
[InlineData(false, "")]
|
[InlineData(false, "")]
|
||||||
|
|
@ -333,7 +387,7 @@ namespace Microsoft.AspNet.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' attribute.";
|
"'asp-action' or 'asp-controller' or 'asp-route' attribute.";
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
|
@ -342,6 +396,30 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
Assert.Equal(expectedErrorMessage, ex.Message);
|
Assert.Equal(expectedErrorMessage, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Action")]
|
||||||
|
[InlineData("Controller")]
|
||||||
|
public async Task ProcessAsync_ThrowsIfRouteAndActionOrControllerProvided(string propertyName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formTagHelper = new FormTagHelper
|
||||||
|
{
|
||||||
|
Route = "Default",
|
||||||
|
};
|
||||||
|
typeof(FormTagHelper).GetProperty(propertyName).SetValue(formTagHelper, "Home");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
"form",
|
||||||
|
attributes: new Dictionary<string, object>());
|
||||||
|
var expectedErrorMessage = "Cannot determine an 'action' attribute for <form>. A <form> with a specified " +
|
||||||
|
"'asp-route' must not have an 'asp-action' or 'asp-controller' attribute.";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
() => formTagHelper.ProcessAsync(context: null, output: output));
|
||||||
|
|
||||||
|
Assert.Equal(expectedErrorMessage, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
private static ViewContext CreateViewContext()
|
private static ViewContext CreateViewContext()
|
||||||
{
|
{
|
||||||
var actionContext = new ActionContext(
|
var actionContext = new ActionContext(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue