22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
44using System ;
5+ using System . Collections . Generic ;
56using System . Linq ;
67using Microsoft . AspNet . Mvc . Rendering ;
78using Microsoft . AspNet . Razor . Runtime . TagHelpers ;
@@ -16,7 +17,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1617 public class FormTagHelper : TagHelper
1718 {
1819 private const string RouteAttributePrefix = "route-" ;
19- private static readonly bool DefaultAntiForgeryBehavior = true ;
2020
2121 [ Activate ]
2222 private ViewContext ViewContext { get ; set ; }
@@ -43,7 +43,8 @@ public class FormTagHelper : TagHelper
4343 public string Method { get ; set ; }
4444
4545 /// <summary>
46- /// Whether the anti-forgery token should be generated. Defaults to <c>true</c>.
46+ /// Whether the anti-forgery token should be generated. Defaults to <c>true</c> if <see cref="Action"/> is not
47+ /// a URL, <c>false</c> otherwise.
4748 /// </summary>
4849 [ HtmlAttributeName ( "anti-forgery" ) ]
4950 public bool ? AntiForgery { get ; set ; }
@@ -52,64 +53,86 @@ public class FormTagHelper : TagHelper
5253 /// <remarks>Does nothing if <see cref="Action"/> contains a '/'.</remarks>
5354 public override void Process ( TagHelperContext context , TagHelperOutput output )
5455 {
56+ bool antiForgeryDefault = true ;
57+
58+ var routeValues = GetRouteValues ( output ) ;
59+
5560 // If Action contains a '/' it means the user is attempting to use the FormTagHelper as a normal form.
5661 if ( Action != null && Action . Contains ( '/' ) )
5762 {
58- if ( Controller != null )
63+ if ( Controller != null || routeValues != null )
5964 {
6065 // We don't know how to generate a form action since a Controller attribute was also provided.
6166 throw new InvalidOperationException (
6267 Resources . FormatFormTagHelper_CannotDetermineAction (
63- nameof ( FormTagHelper ) ,
68+ "<form>" ,
6469 nameof ( Action ) ,
65- nameof ( Controller ) ) ) ;
70+ nameof ( Controller ) ,
71+ RouteAttributePrefix ) ) ;
72+ }
73+
74+ // If the anti-forgery token was not specified then don't assume that it's on (user is using the
75+ // FormTagHelper like a normal <form> tag).
76+ antiForgeryDefault = false ;
77+
78+ // Restore Action, Method and Route HTML attributes if they were provided, user wants non-TagHelper <form>.
79+ output . RestoreBoundHtmlAttribute ( nameof ( Action ) , context ) ;
80+
81+ if ( Method != null )
82+ {
83+ output . RestoreBoundHtmlAttribute ( nameof ( Method ) , context ) ;
6684 }
6785
68- RestoreBoundHtmlAttributes ( context , output ) ;
86+ if ( routeValues != null )
87+ {
88+ foreach ( var routeKeys in routeValues . Keys )
89+ {
90+ output . RestoreBoundHtmlAttribute ( "route-" + routeKeys , context ) ;
91+ }
92+ }
6993 }
7094 else
7195 {
72- var prefixedValues = output . PullPrefixedAttributes ( RouteAttributePrefix ) ;
73-
74- // Generator.GenerateForm does not accept a Dictionary<string, string> for route values.
75- var routeValues = prefixedValues . ToDictionary (
76- attribute => attribute . Key . Substring ( RouteAttributePrefix . Length ) ,
77- attribute => ( object ) attribute . Value ) ;
78-
7996 var tagBuilder = Generator . GenerateForm ( ViewContext ,
8097 Action ,
8198 Controller ,
8299 routeValues ,
83100 Method ,
84101 htmlAttributes : null ) ;
85102
86- output . Merge ( tagBuilder ) ;
103+ if ( tagBuilder != null )
104+ {
105+ // We don't want to do a full merge because we want the TagHelper content to take presedence.
106+ output . MergeAttributes ( tagBuilder ) ;
107+ }
108+ }
87109
88- if ( AntiForgery ?? DefaultAntiForgeryBehavior )
110+ if ( AntiForgery ?? antiForgeryDefault )
111+ {
112+ var antiForgeryTag = Generator . GenerateAntiForgery ( ViewContext ) ;
113+
114+ if ( antiForgeryTag != null )
89115 {
90- var antiForgeryTag = Generator . GenerateAntiForgery ( ViewContext ) ;
91116 output . Content += antiForgeryTag . ToString ( TagRenderMode . SelfClosing ) ;
92117 }
93118 }
94119 }
95120
96- // Restores bound HTML attributes when we detect that a user is using the AnchorTagHelper as a normal <a> tag .
97- private void RestoreBoundHtmlAttributes ( TagHelperContext context , TagHelperOutput output )
121+ // TODO: We will not need this method once https://github.com/aspnet/Razor/issues/89 is completed .
122+ private static Dictionary < string , object > GetRouteValues ( TagHelperOutput output )
98123 {
99- if ( Action != null )
100- {
101- output . RestoreBoundHtmlAttribute ( nameof ( Action ) , context ) ;
102- }
124+ var prefixedValues = output . PullPrefixedAttributes ( RouteAttributePrefix ) ;
103125
104- if ( Controller != null )
126+ Dictionary < string , object > routeValues = null ;
127+ if ( prefixedValues . Any ( ) )
105128 {
106- output . RestoreBoundHtmlAttribute ( nameof ( Controller ) , context ) ;
129+ // Generator.GenerateForm does not accept a Dictionary<string, string> for route values.
130+ routeValues = prefixedValues . ToDictionary (
131+ attribute => attribute . Key . Substring ( RouteAttributePrefix . Length ) ,
132+ attribute => ( object ) attribute . Value ) ;
107133 }
108134
109- if ( Method != null )
110- {
111- output . RestoreBoundHtmlAttribute ( nameof ( Method ) , context ) ;
112- }
135+ return routeValues ;
113136 }
114137 }
115138}
0 commit comments