1 //------------------------------------------------------------------------------ 2 // <copyright file="HtmlForm.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 // HtmlForm.cs 8 // 9 10 namespace System.Web.UI.HtmlControls { 11 using System.ComponentModel; 12 using System; 13 using System.Collections; 14 using System.Globalization; 15 using System.IO; 16 using System.Text; 17 using System.Web.Configuration; 18 using System.Web.Util; 19 using System.Web.UI; 20 using System.Web.UI.WebControls; 21 using System.Web.Security; 22 using System.Security.Permissions; 23 24 25 /// <devdoc> 26 /// <para> 27 /// The <see langword='HtmlForm'/> class defines the methods, properties, and 28 /// events for the HtmlForm control. This class provides programmatic access to the 29 /// HTML <form> element on the server. 30 /// </para> 31 /// </devdoc> 32 public class HtmlForm : HtmlContainerControl { 33 private string _defaultFocus; 34 private string _defaultButton; 35 private bool _submitDisabledControls; 36 private const string _aspnetFormID = "aspnetForm"; 37 38 39 /// <devdoc> 40 /// </devdoc> HtmlForm()41 public HtmlForm() 42 : base("form") { 43 } 44 45 [ 46 WebCategory("Behavior"), 47 DefaultValue(""), 48 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 49 ] 50 public string Action { 51 get { 52 string s = Attributes["action"]; 53 return ((s != null) ? s : String.Empty); 54 } 55 set { 56 Attributes["action"] = MapStringAttributeToString(value); 57 } 58 } 59 60 /// <devdoc> 61 /// Gets or sets default button for the form 62 /// </devdoc> 63 [ 64 WebCategory("Behavior"), 65 DefaultValue(""), 66 ] 67 public string DefaultButton { 68 get { 69 if (_defaultButton == null) { 70 return String.Empty; 71 } 72 return _defaultButton; 73 } 74 set { 75 _defaultButton = value; 76 } 77 } 78 79 80 /// <devdoc> 81 /// Gets or sets default focused control for the form 82 /// </devdoc> 83 [ 84 WebCategory("Behavior"), 85 DefaultValue(""), 86 ] 87 public string DefaultFocus { 88 get { 89 if (_defaultFocus == null) { 90 return String.Empty; 91 } 92 return _defaultFocus; 93 } 94 set { 95 _defaultFocus = value; 96 } 97 } 98 99 /* 100 * Encode Type property. 101 */ 102 103 /// <devdoc> 104 /// <para> 105 /// Gets or sets the Enctype attribute of the form. This is 106 /// the encoding type that browsers 107 /// use when posting the form's data to the server. 108 /// </para> 109 /// </devdoc> 110 [ 111 WebCategory("Behavior"), 112 DefaultValue(""), 113 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 114 ] 115 public string Enctype { 116 get { 117 string s = Attributes["enctype"]; 118 return ((s != null) ? s : String.Empty); 119 } 120 set { 121 Attributes["enctype"] = MapStringAttributeToString(value); 122 } 123 } 124 125 /* 126 * Method property. 127 */ 128 129 /// <devdoc> 130 /// <para> 131 /// Gets or sets the Method attribute for the form. This defines how a browser 132 /// posts form data to the server for processing. The two common methods supported 133 /// by all browsers are GET and POST. 134 /// </para> 135 /// </devdoc> 136 [ 137 WebCategory("Behavior"), 138 DefaultValue(""), 139 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 140 ] 141 public string Method { 142 get { 143 string s = Attributes["method"]; 144 return ((s != null) ? s : "post"); 145 } 146 set { 147 Attributes["method"] = MapStringAttributeToString(value); 148 } 149 } 150 151 /* 152 * Name property. 153 */ 154 155 /// <devdoc> 156 /// <para> 157 /// Gets the value of the HTML Name attribute that will be rendered to the 158 /// browser. 159 /// </para> 160 /// </devdoc> 161 [ 162 WebCategory("Appearance"), 163 DefaultValue(""), 164 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 165 ] 166 public virtual string Name { 167 get { 168 return UniqueID; 169 } 170 set { 171 // no-op setter to prevent the name from being set 172 } 173 } 174 175 176 /// <devdov> 177 /// If true, forces controls disabled on the client to submit their values (thus preserving their previous postback state) 178 /// </devdoc> 179 [ 180 WebCategory("Behavior"), 181 DefaultValue(false) 182 ] 183 public virtual bool SubmitDisabledControls { 184 get { 185 return _submitDisabledControls; 186 } 187 set { 188 _submitDisabledControls = value; 189 } 190 } 191 192 /* 193 * Target property. 194 */ 195 196 /// <devdoc> 197 /// <para> 198 /// Gets or sets the Uri of the frame or window to render the results of a Form 199 /// POST request. Developers can use this property to redirect these results to 200 /// another browser window or frame. 201 /// </para> 202 /// </devdoc> 203 [ 204 WebCategory("Behavior"), 205 DefaultValue(""), 206 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 207 ] 208 public string Target { 209 get { 210 string s = Attributes["target"]; 211 return ((s != null) ? s : String.Empty); 212 } 213 214 set { 215 Attributes["target"] = MapStringAttributeToString(value); 216 } 217 } 218 219 220 /// <devdoc> 221 /// Overridden to return a constant value or tack the ID onto the same constant value. 222 /// This fixes a bug in PocketPC which doesn't allow the name and ID of a form to be different 223 /// </devdoc> 224 public override string UniqueID { 225 get { 226 if (NamingContainer == Page) { 227 return base.UniqueID; 228 } 229 else if (this.EffectiveClientIDMode != ClientIDMode.AutoID) { 230 return ID ?? _aspnetFormID; 231 } 232 233 return _aspnetFormID; 234 } 235 } 236 237 public override string ClientID { 238 get { 239 if (this.EffectiveClientIDMode != ClientIDMode.AutoID) { 240 return ID; 241 } 242 return base.ClientID; 243 } 244 } 245 Render(HtmlTextWriter output)246 protected internal override void Render(HtmlTextWriter output) { 247 Page p = Page; 248 if (p == null) 249 throw new HttpException(SR.GetString(SR.Form_Needs_Page)); 250 251 #pragma warning disable 0618 // To avoid deprecation warning 252 if (p.SmartNavigation) { 253 #pragma warning restore 0618 254 ((IAttributeAccessor)this).SetAttribute("__smartNavEnabled", "true"); 255 256 // Output the IFrame 257 StringBuilder sb = new StringBuilder("<IFRAME id=\"__hifSmartNav\" name=\"__hifSmartNav\" style=\"display:none\" src=\""); 258 sb.Append(HttpEncoderUtility.UrlEncodeSpaces(HttpUtility.HtmlAttributeEncode(Page.ClientScript.GetWebResourceUrl(typeof(HtmlForm), "SmartNav.htm")))); 259 sb.Append("\"></IFRAME>"); 260 output.WriteLine(sb.ToString()); 261 } 262 263 base.Render(output); 264 } 265 GetActionAttribute()266 private string GetActionAttribute() { 267 // If the Action property is nonempty, we use it instead of the current page. This allows the developer 268 // to support scenarios like PathInfo, UrlMapping, etc. (DevDiv Bugs 164390) 269 string actionProperty = Action; 270 if (!String.IsNullOrEmpty(actionProperty)) { 271 return actionProperty; 272 } 273 274 string action; 275 VirtualPath clientFilePath = Context.Request.ClientFilePath; 276 277 // ASURT 15075/11054/59970: always set the action to the current page. 278 // DevDiv Servicing 215795/Dev10 567580: The IIS URL Rewrite module and other rewrite 279 // scenarios need the postback action to be the original URL. Note however, if Server.Transfer/Execute 280 // is used, the action will be set to the transferred/executed page, that is, the value of 281 // CurrentExecutionFilePathObject. This is because of ASURT 59970 and the document attached to 282 // that bug, which indirectly states that things should behave this way when Transfer/Execute is used. 283 if (Context.ServerExecuteDepth == 0) { 284 // There hasn't been any Server.Transfer or RewritePath. 285 // ASURT 15979: need to use a relative path, not absolute 286 action = clientFilePath.VirtualPathString; 287 int iPos = action.LastIndexOf('/'); 288 if (iPos >= 0) { 289 // Ensure the segment is always a relative path, so prepend a dot-segment 290 // (RFC section 4.2 Relative Reference) 291 action = "./" + action.Substring(iPos + 1); 292 } 293 } 294 else { 295 VirtualPath currentFilePath = Context.Request.CurrentExecutionFilePathObject; 296 // Server.Transfer or RewritePath case. We need to make the form action relative 297 // to the original ClientFilePath (since that's where the browser thinks we are). 298 currentFilePath = clientFilePath.MakeRelative(currentFilePath); 299 action = currentFilePath.VirtualPathString; 300 } 301 302 // VSWhidbey 202380: If cookieless is on, we need to add the app path modifier to the form action 303 bool cookieless = CookielessHelperClass.UseCookieless(Context, false, FormsAuthentication.CookieMode); 304 if (cookieless && Context.Request != null && Context.Response != null) { 305 action = Context.Response.ApplyAppPathModifier(action); 306 } 307 308 // Dev11 406986: <form> elements must have non-empty 'action' attributes to pass W3 validation. 309 // The only time this might happen is that the current file path is "", which meant that the 310 // incoming URL ended in a slash, so we can just point 'action' back to the current directory. 311 if (String.IsNullOrEmpty(action) && RenderingCompatibility >= VersionUtil.Framework45) { 312 action = "./"; 313 } 314 315 // Dev11 177096: The action may be empty if the RawUrl does not point to a file (for e.g. http://localhost:8080/) but is never null. 316 // Empty action values work fine since the form does not emit the action attribute. 317 Debug.Assert(action != null); 318 319 string queryString = Page.ClientQueryString; 320 // ASURT 15355: Don't lose the query string if there is one. 321 // In scriptless mobile HTML, we prepend __EVENTTARGET, et. al. to the query string. These have to be 322 // removed from the form action. Use new HttpValueCollection to leverage ToString(bool encoded). 323 if (!String.IsNullOrEmpty(queryString)) { 324 action += "?" + queryString; 325 } 326 327 return action; 328 } 329 330 331 /// <internalonly/> 332 /// <devdoc> 333 /// <para> Call RegisterViewStateHandler().</para> 334 /// </devdoc> OnInit(EventArgs e)335 protected internal override void OnInit(EventArgs e) { 336 base.OnInit(e); 337 338 Page.SetForm(this); 339 340 // Make sure view state is calculated (see ASURT 73020) 341 Page.RegisterViewStateHandler(); 342 } 343 344 345 /// <devdoc> 346 /// Overridden to handle focus stuff 347 /// </devdoc> OnPreRender(EventArgs e)348 protected internal override void OnPreRender(EventArgs e) { 349 base.OnPreRender(e); 350 351 #pragma warning disable 0618 // To avoid deprecation warning 352 if (Page.SmartNavigation) { 353 #pragma warning restore 0618 354 // Register the smartnav script file reference so it gets rendered 355 Page.ClientScript.RegisterClientScriptResource(typeof(HtmlForm), "SmartNav.js"); 356 } 357 } 358 359 360 /// <internalonly/> 361 /// <devdoc> 362 /// </devdoc> RenderAttributes(HtmlTextWriter writer)363 protected override void RenderAttributes(HtmlTextWriter writer) { 364 ArrayList invalidAttributes = new ArrayList(); 365 foreach (String key in Attributes.Keys) { 366 if (!writer.IsValidFormAttribute(key)) { 367 invalidAttributes.Add(key); 368 } 369 } 370 371 foreach (String key in invalidAttributes) { 372 Attributes.Remove(key); 373 } 374 375 bool enableLegacyRendering = EnableLegacyRendering; 376 377 Page page = Page; 378 if (writer.IsValidFormAttribute("name")) { 379 // DevDiv 27328 Do not render name attribute for uplevel browser 380 if (page != null && page.RequestInternal != null && 381 RenderingCompatibility < VersionUtil.Framework40 && 382 (page.RequestInternal.Browser.W3CDomVersion.Major == 0 || 383 page.XhtmlConformanceMode != XhtmlConformanceMode.Strict)) { 384 writer.WriteAttribute("name", Name); 385 } 386 Attributes.Remove("name"); 387 } 388 389 writer.WriteAttribute("method", Method); 390 Attributes.Remove("method"); 391 392 // Encode the action attribute - ASURT 66784 393 writer.WriteAttribute("action", GetActionAttribute(), true /*encode*/); 394 Attributes.Remove("action"); 395 396 // see if the page has a submit event 397 if (page != null) { 398 string onSubmit = page.ClientOnSubmitEvent; 399 if (!String.IsNullOrEmpty(onSubmit)) { 400 if (Attributes["onsubmit"] != null) { 401 // If there was an onsubmit on the form, register it as an onsubmit statement and remove it from the attribute collection 402 string formOnSubmit = Attributes["onsubmit"]; 403 if (formOnSubmit.Length > 0) { 404 if (!StringUtil.StringEndsWith(formOnSubmit, ';')) { 405 formOnSubmit += ";"; 406 } 407 if (page.ClientSupportsJavaScript || !formOnSubmit.ToLower(CultureInfo.CurrentCulture).Contains("javascript")) { 408 page.ClientScript.RegisterOnSubmitStatement(typeof(HtmlForm), "OnSubmitScript", formOnSubmit); 409 } 410 Attributes.Remove("onsubmit"); 411 } 412 } 413 414 // Don't render the on submit if it contains javascript and the page doesn't support it 415 if (page.ClientSupportsJavaScript || !onSubmit.ToLower(CultureInfo.CurrentCulture).Contains("javascript")) { 416 if (enableLegacyRendering) { 417 writer.WriteAttribute("language", "javascript", false); 418 } 419 writer.WriteAttribute("onsubmit", onSubmit); 420 } 421 } 422 423 if ((page.RequestInternal != null) && 424 (page.RequestInternal.Browser.EcmaScriptVersion.Major > 0) && 425 (page.RequestInternal.Browser.W3CDomVersion.Major > 0)) { 426 if (DefaultButton.Length > 0) { 427 // Find control from the page if it's a hierarchical ID. 428 // Dev11 bug 19915 429 Control c = FindControlFromPageIfNecessary(DefaultButton); 430 431 if (c is IButtonControl) { 432 page.ClientScript.RegisterDefaultButtonScript(c, writer, false /* UseAddAttribute */); 433 } 434 else { 435 throw new InvalidOperationException(SR.GetString(SR.HtmlForm_OnlyIButtonControlCanBeDefaultButton, ID)); 436 } 437 } 438 } 439 } 440 441 442 // We always want the form to have an id on the client 443 // base.RenderAttributes takes care of actually rendering it. 444 EnsureID(); 445 446 base.RenderAttributes(writer); 447 } 448 449 450 /// <internalonly/> 451 /// <devdoc> 452 /// </devdoc> RenderChildren(HtmlTextWriter writer)453 protected internal override void RenderChildren(HtmlTextWriter writer) { 454 // We need to register the script here since other controls might register 455 // for focus during PreRender 456 Page page = Page; 457 if (page != null) { 458 page.OnFormRender(); 459 page.BeginFormRender(writer, UniqueID); 460 } 461 462 // DevDiv Bugs 154630: move custom hidden fields to the begining of the form 463 HttpWriter httpWriter = writer.InnerWriter as HttpWriter; 464 if (page != null && httpWriter != null && RuntimeConfig.GetConfig(Context).Pages.RenderAllHiddenFieldsAtTopOfForm) { 465 // If the response is flushed or cleared during render, we won't be able 466 // to move the hidden fields. Set HasBeenClearedRecently to false and 467 // then check again when we're ready to move the fields. 468 httpWriter.HasBeenClearedRecently = false; 469 470 // Remember the index where the form begins 471 int formBeginIndex = httpWriter.GetResponseBufferCountAfterFlush(); 472 473 base.RenderChildren(writer); 474 475 // Remember the index where the custom hidden fields begin 476 int fieldsBeginIndex = httpWriter.GetResponseBufferCountAfterFlush(); 477 478 page.EndFormRenderHiddenFields(writer, UniqueID); 479 480 // we can only move the hidden fields if the response has not been flushed or cleared 481 if (!httpWriter.HasBeenClearedRecently) { 482 int fieldsEndIndex = httpWriter.GetResponseBufferCountAfterFlush(); 483 httpWriter.MoveResponseBufferRangeForward(fieldsBeginIndex, fieldsEndIndex - fieldsBeginIndex, formBeginIndex); 484 } 485 486 page.EndFormRenderArrayAndExpandoAttribute(writer, UniqueID); 487 page.EndFormRenderPostBackAndWebFormsScript(writer, UniqueID); 488 page.OnFormPostRender(writer); 489 } 490 else { 491 base.RenderChildren(writer); 492 493 if (page != null) { 494 page.EndFormRender(writer, UniqueID); 495 page.OnFormPostRender(writer); 496 } 497 } 498 } 499 RenderControl(HtmlTextWriter writer)500 public override void RenderControl(HtmlTextWriter writer) { 501 if (DesignMode) { 502 // User Control Designer scenario 503 base.RenderChildren(writer); 504 } 505 else { 506 base.RenderControl(writer); 507 } 508 } 509 CreateControlCollection()510 protected override ControlCollection CreateControlCollection() { 511 return new ControlCollection(this, 100, 2); 512 } 513 } 514 } 515