1
+ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2
+ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
+
4
+ using System ;
5
+ using System . Collections . Generic ;
6
+ using System . Linq ;
7
+ using Microsoft . AspNet . Mvc . Rendering ;
8
+ using Microsoft . AspNet . Razor . Runtime . TagHelpers ;
9
+ using Microsoft . AspNet . Razor . TagHelpers ;
10
+
11
+ namespace Microsoft . AspNet . Mvc . TagHelpers
12
+ {
13
+ /// <summary>
14
+ /// <see cref="ITagHelper"/> implementation targeting <form> elements.
15
+ /// </summary>
16
+ [ ContentBehavior ( ContentBehavior . Append ) ]
17
+ public class FormTagHelper : TagHelper
18
+ {
19
+ [ Activate ]
20
+ private ViewContext ViewContext { get ; set ; }
21
+
22
+ [ Activate ]
23
+ private AntiForgery AntiForgery { get ; set ; }
24
+
25
+ [ Activate ]
26
+ private IHtmlGenerator Generator { get ; set ; }
27
+
28
+ /// <summary>
29
+ /// The name of the action method.
30
+ /// </summary>
31
+ /// <remarks>
32
+ /// If value contains a '/' this <see cref="ITagHelper"/> will do nothing.
33
+ /// </remarks>
34
+ public string Action { get ; set ; }
35
+
36
+ /// <summary>
37
+ /// The name of the controller.
38
+ /// </summary>
39
+ public string Controller { get ; set ; }
40
+
41
+ /// <summary>
42
+ /// The HTTP method for processing the form, either GET or POST.
43
+ /// </summary>
44
+ public string Method { get ; set ; }
45
+
46
+ /// <inheritdoc />
47
+ /// <remarks>Does nothing if <see cref="Action"/> contains a '/'.</remarks>
48
+ public override void Process ( TagHelperContext context , TagHelperOutput output )
49
+ {
50
+ // If Action contains a '/' it means the user is attempting to use the FormTagHelper as a normal form.
51
+ if ( Action != null && Action . Contains ( '/' ) )
52
+ {
53
+ RestoreBoundHtmlAttributes ( context , output ) ;
54
+ }
55
+ else
56
+ {
57
+ // TODO: Make this behavior optional once https://github.com/aspnet/Razor/issues/121 is completed.
58
+ output . Content = AntiForgery . GetHtml ( ViewContext . HttpContext ) . ToString ( ) ;
59
+
60
+ var routeValues = PullRouteValues ( output . Attributes ) ;
61
+ var tagBuilder = Generator . GenerateForm ( ViewContext ,
62
+ Action ,
63
+ Controller ,
64
+ routeValues ,
65
+ Method ,
66
+ htmlAttributes : new Dictionary < string , object > ( ) ) ;
67
+
68
+ TagHelperOutputHelper . MergeAttributes ( output , tagBuilder ) ;
69
+ }
70
+ }
71
+
72
+ // TODO: We will not need this method once https://github.com/aspnet/Razor/issues/89 is completed.
73
+ private static Dictionary < string , object > PullRouteValues ( IDictionary < string , string > htmlAttributes )
74
+ {
75
+ var routeAttributePrefix = "route-" ;
76
+
77
+ // We're only interested in HTML attributes that have the desired routeAttributePrefix.
78
+ var routeAttributes = htmlAttributes . Where ( attribute =>
79
+ attribute . Key . StartsWith ( routeAttributePrefix , StringComparison . OrdinalIgnoreCase ) ) ;
80
+
81
+ // We need to remove any route based HTML attributes from the HTML attributes dictionary because
82
+ // they shouldn't be treated as HTML attributes, they're route values.
83
+ foreach ( var attribute in routeAttributes )
84
+ {
85
+ htmlAttributes . Remove ( attribute . Key ) ;
86
+ }
87
+
88
+ // We build a Dictionary<string, object> because Generator.GenerateForm does not accept a
89
+ // Dictionary <string, string>.
90
+ return routeAttributes . ToDictionary ( attribute => attribute . Key . Substring ( routeAttributePrefix . Length ) ,
91
+ attribute => ( object ) attribute . Value ) ;
92
+ }
93
+
94
+ private void RestoreBoundHtmlAttributes ( TagHelperContext context , TagHelperOutput output )
95
+ {
96
+ var attributesToRestore = new List < string > ( ) ;
97
+
98
+ if ( Action != null )
99
+ {
100
+ attributesToRestore . Add ( nameof ( Action ) ) ;
101
+ }
102
+
103
+ if ( Controller != null )
104
+ {
105
+ attributesToRestore . Add ( nameof ( Controller ) ) ;
106
+ }
107
+
108
+ if ( Method != null )
109
+ {
110
+ attributesToRestore . Add ( nameof ( Method ) ) ;
111
+ }
112
+
113
+ foreach ( var attributeName in attributesToRestore )
114
+ {
115
+ // We need to look for the KeyValuePair<string, object> attribute so we can ensure that the attribute
116
+ // that we re-add to the output object has the same attribute name as the one the user typed.
117
+ var entry = context . AllAttributes . Single ( attribute =>
118
+ attribute . Key . Equals ( attributeName , StringComparison . OrdinalIgnoreCase ) ) ;
119
+ var originalAttribute = new KeyValuePair < string , string > ( entry . Key , entry . Value . ToString ( ) ) ;
120
+
121
+ output . Attributes . Add ( originalAttribute ) ;
122
+ }
123
+ }
124
+ }
125
+ }
0 commit comments