diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs index 1a241046b0..797006ca08 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs @@ -17,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers private const string ActionAttributeName = "asp-action"; 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 HtmlActionAttributeName = "action"; @@ -47,6 +48,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers [HtmlAttributeName(AntiForgeryAttributeName)] public bool? AntiForgery { get; set; } + /// + /// Name of the route. + /// + /// + /// Must be null if or is non-null. + /// + [HtmlAttributeName(RouteAttributeName)] + public string Route { get; set; } + /// /// /// Does nothing if user provides an action attribute and is null 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
. 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. throw new InvalidOperationException( @@ -73,6 +83,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers HtmlActionAttributeName, ActionAttributeName, ControllerAttributeName, + RouteAttributeName, RouteAttributePrefix)); } @@ -82,13 +93,38 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } else { + TagBuilder tagBuilder; var routeValues = GetRouteValues(output, routePrefixedAttributes); - var tagBuilder = Generator.GenerateForm(ViewContext, - Action, - Controller, - routeValues, - method: null, - htmlAttributes: null); + if (Route == null) + { + tagBuilder = Generator.GenerateForm( + ViewContext, + Action, + 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( + "", + RouteAttributeName, + ActionAttributeName, + ControllerAttributeName, + HtmlActionAttributeName)); + } + else + { + tagBuilder = Generator.GenerateRouteForm( + ViewContext, + Route, + routeValues, + method: null, + htmlAttributes: null); + } if (tagBuilder != null) { diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs index d0cae73f5e..bab88bbcb2 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } /// - /// 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. /// internal static string FormTagHelper_CannotOverrideAction { @@ -51,11 +51,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } /// - /// 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. /// - 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); } /// @@ -138,6 +138,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperOutput_AttributeDoesNotExist"), p0, p1); } + /// + /// Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute. + /// + internal static string FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified + { + get { return GetString("FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified"); } + } + + /// + /// Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute. + /// + 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) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx b/src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx index 8d7adbdb7d..26528c6813 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx @@ -1,17 +1,17 @@  - @@ -124,7 +124,7 @@ 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 '{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. Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'. @@ -141,4 +141,7 @@ The attribute '{0}' does not exist in the {1}. + + Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute. + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs index 33b64208c8..c7b239acef 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs @@ -252,11 +252,65 @@ namespace Microsoft.AspNet.Mvc.TagHelpers 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()); } + [Fact] + public async Task ProcessAsync_CallsIntoGenerateRouteFormWithExpectedParameters() + { + // Arrange + var viewContext = CreateViewContext(); + var context = new TagHelperContext( + allAttributes: new Dictionary(), + items: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => + { + var tagHelperContent = new DefaultTagHelperContent(); + tagHelperContent.SetContent("Something"); + return Task.FromResult(tagHelperContent); + }); + var output = new TagHelperOutput( + "form", + attributes: new Dictionary + { + { "asp-route-foo", "bar" } + }); + var generator = new Mock(MockBehavior.Strict); + generator + .Setup(mock => mock.GenerateRouteForm( + viewContext, + "Default", + It.Is>(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] [InlineData(true, "")] [InlineData(false, "")] @@ -333,7 +387,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedErrorMessage = "Cannot override the 'action' attribute for . A with a specified " + "'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 var ex = await Assert.ThrowsAsync( @@ -342,6 +396,30 @@ namespace Microsoft.AspNet.Mvc.TagHelpers 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()); + var expectedErrorMessage = "Cannot determine an 'action' attribute for . A with a specified " + + "'asp-route' must not have an 'asp-action' or 'asp-controller' attribute."; + + // Act & Assert + var ex = await Assert.ThrowsAsync( + () => formTagHelper.ProcessAsync(context: null, output: output)); + + Assert.Equal(expectedErrorMessage, ex.Message); + } + private static ViewContext CreateViewContext() { var actionContext = new ActionContext(