Wednesday, March 28, 2007

Hide form tag, but leave content

I often run into an issue in ASP.NET when I want to include a <form> on a page that posts to a 3rd party website. For example, I could have a PayPal button that posts directly to PayPal (as opposed to doing a "post-back") to the current page.

The problem is that a common practice in ASP.NET is to have a <form runat="server"> tag "high up" in the HTML, usually in the Master page. HTML doesn't allow overlapping <form> tags, so my PayPal button won't work. Since the higher level <form> is in the Master page, it makes removing it pretty messy. As far as I know, there's no built-in "hide this tag, but render the tag's contents" feature for controls. In the past I've maintained two master pages: the normal one and a "no form" version, but I've since discovered a more elegant solution.

I created a class called GhostForm that has a property RenderFormTag. When RenderFormTag is set to false, it doesn't render the <form> tag or closing tag, but does render all of the contents. This works perfectly so I can have my <form runat="server"> high up for most pages, but I can fine-tune it when I need forms posting to other sites or multiple forms.

Here's the code for GhostForm:
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;

/// <summary>
/// This is a special form that can _not_ render the actual form tag, but always render the contents
/// </summary>
public class GhostForm : System.Web.UI.HtmlControls.HtmlForm
{
  protected bool _render;

  public bool RenderFormTag
  {
      get { return _render; }
      set { _render = value; }
  }

  public GhostForm()
  {
      //By default, show the form tag
      _render = true;
  }

  protected override void RenderBeginTag(HtmlTextWriter writer)
  {
      //Only render the tag when _render is set to true
      if (_render)
          base.RenderBeginTag(writer);
  }

  protected override void RenderEndTag(HtmlTextWriter writer)
  {
      //Only render the tag when _render is set to true
      if (_render)
          base.RenderEndTag(writer);
  }
}

48 comments:

Anthony said...

Hi Jeremy,

I'm having the well-known PayPal button/form problem. Would you have a code sample of how to use your class?

Jeremy Schneider said...

Sure... Just throw that GhostForm.cs in your App_Code folder (if this is .NET 2.0+) then in your master page files you can do something like this:

...

<%@ Register TagPrefix="ns" Namespace="MyNameSpace" %>
...

<ns:GhostForm id="_mainForm" runat="server">

Content is king!

</ns:GhostForm>


Then in your codebehind you can do this:
_mainForm.RenderFormTag = false;

Anthony said...

Thanks a million... now I won't have to mess with nested master pages :-)

Jeffrey McManus said...

Is there a step missing here? How does the page know how to get to what's in GhostForm.cs after you put it in App_Code? When I try to build a test project I am getting an "unknown server tag" error.

Jeremy Schneider said...

Hey Jeff... Anything in App_Code is accessible to all the pages in your site. Just put your GhostForm.cs in a namespace (for example, you can call it MyNameSpace) then this line at the top of your ASPX page will register the namespace so you can use the GhostForm tag:

<%@ Register TagPrefix="ns" Namespace="MyNameSpace" %>

Will Kriski said...

I found a simpler way (no coding files, etc). Make the button invoke some javascript.
Check out this link
http://www.acxede.net/hack_12_add_a_paypal_button_to_your_office_live_basics_page

I just removed the form tag and made the submit button call the javacript that submits to paypal.

Jeffrey McManus said...

Ah, got it working, thanks. I was thrown by the fact that the ns:GhostForm tag got the red underline of doom in the VS editor.

Send2moran said...

if you remove the FORM tags of paypal how does the method of the 'buy now' buttons still working , how does it knows to post to paypal servers?

Harry said...

I'm using latest version of VS2005. Recently I had problems using the App_Code folder in situations like this before and hacked it:

http://www.codersbarn.com/post/ASPNET-20-Guest-Book-Application.aspx

This should not be necessary. App_Code is supposed to work with Web projects as well as websites. Create a non-system folder, put the class in there and reference it your code-behind. If that doesn't work, create a new library project with the custom class, and add new project to your solution. I'd be curious to know if this is a VS bug...

Claire said...

Thanks, this is great.

Anonymous said...

Its not working here. I get this message:

The name 'mainForm' does not exist in the current context

mainForm is the form ID in my MasterPage. This message occours when i call the following:
mainForm.RenderFormTag = false;

Any help? Can someone post an entire source code, please?

Anonymous said...

I'll create a sample over the weekend using Jeremy's GhostForm class.
Anthony,
CodersBarn.com

Anonymous said...

i cant wait! it will be really helpful!

Jano Petras said...

Great stuff Jeremy. Extremely elegant solution - all great things are usually simple but require a genius. Cheers mate.

Ben Curnow said...

Hey, simple solution. Don't remove the form from the master page. Just put an empty form tag just prior to the Paypal button. Don't know why it works, but it does!

Jeremy said...

Anyone have a VB.Net example of how to use this??

I'm using a Web Application, not a Web Project.

Thanks

David said...

Ok wait - I found this solution using javascript that is SOOOOOOO brilliantly simple compared to any of this MasterPage and Ghostform lunacy that I can't believe this isn't the top search returned on Google for this issue:
http://www.nerdymusings.com/LPMArticle.asp?ID=29

Basically you just need to remove the form tags from the Paypal HTML and then replace the image button with an A link around an image. In the A link you have javascript that resets "theForm" which is ASP.NET's main form to point to Paypal instead of back to your page.



David

Anthony said...

David,

The reason we didn't do this initially is because of the browser's need to have JavaScript enabled for this to work. There are all types of JavaScript hacks out there but the challenge was to find an elegant way around the form problem with ASP.NET...

Anthony :-)

Chad said...

This was extremely useful! Thanks! At least they say that .NET 3.5 supports programmatically setting the action.

Chad said...

Just so I'm clear you are using the

ns:GhostForm id="_mainForm" runat="server"

in place of where your
form id="form1" runat="server"

would normally be? Right?

Jeremy Schneider said...

Chad: Yes, That's correct!

onesimus_az said...

I am trying to use your class. First I converted it to VB.Net then I compiled it to a DLL. These were two necessary steps for the project I am working on. Next I added the DLL to the target web project. Finally, I added the following code to the web control I am working with...


<gf:GhostForm id="mainForm" runat="server" RenderFormTag="False">

</gf:GhostForm>

I also added the

Dim mainForm As New GhostForm.GhostForm
mainForm.RenderFormTag = False

Per your suggestion in the Page_Load Event. However I get the following error when trying render the page...

InnerException: A page can have only one server-side Form tag.

so apparently it is recognizing the control, but not setting the property.

Any suggestions?

Jonathan said...

Wow thanks, You saved me from so much trouble, A customer asked me for a paypal button and do somework with the DB at the same time and he asked for it as quick as possible. Now its possible :-D

Thank you.

Andrew said...

Whats your license on this? Is it free for all? Public Domain and all that? This looks liek a really nice elegant solution.

Jeremy Schneider said...

Andrew: There is no license. It's free for all! It's only like 2 lines of code, so feel free to use it as you wish for whatever you wish! If you want to give someone money, donate it to a local animal shelter :)

Anonymous said...

Hey Andrew,

Don't be giving Jeremy ideas!!

Anthony ;-)

Andrew said...

Works great! Thanks for the nice solution.

Anonymous said...

Great work Jeremey. You're an asset to the community.

For all those using the Javascript work around, be mindful of Anti-Virus applications. AV may thing it's a phishing form.

Kyle said...

Works great for me in all browsers except IE8. Has anyone else encountered problems in IE8? I wonder if it is related to the removal of the form tag or if I have something else wrong.

Jeremy Schneider said...

This is a totally "server side" implementation, so I can't imagine this has anything to do with your IE8 flog. I'd look at the generated HTML to track down the problem.

Anonymous said...

Hi guys,

Seems like a very ekegent solution. However when I put the register tag and then form tag code in my Master Page, it stops going into Design mode. Here is the exact Error message:

Error Creating the control _mainForm
Unable to cast object of type 'System.Web.UI.Design.HtmlIntrinsicControlDesigner' to type 'System.Web.UI.Design.ControlDesigner'.

However I have tested it and does run and there is no form tag on the page in Browser.

Jeremy please Help or anyone please.....................

Kris Kilton said...

This works for me like a charm, just get rid of the [form] junk paypal gives you and put in the code instead...

You can even change the button id for different subscriptions in code...

protected void Page_Load(object sender, EventArgs e)
{
this.Form.Action = "https://www.sandbox.paypal.com/cgi-bin/webscr";
this.Form.Method = "post";
}

Anonymous said...

Do you have a vb version of it? :)

Jeremy Schneider said...

I don't have a VB version, but it's like 4 lines of code, so I'm sure you could translate it without much trouble.

Anonymous said...

I don't understand why people don't spend the extra 5 minutes and post the entire source code?

I put the GhostForm class in app_code, then put %@ Register TagPrefix="ns" Namespace="GhostForm" % in the masterpage, then put ns:GhostForm id="form1" runat="server" /ns:GhostForm in place of the form tags.

I keep getting Parser Error Message: Unknown server tag 'ns:GhostForm'.

What am I missing?

Anthony said...

Use a Web application and add the GhostForm class and reference it with a Using statement:

protected void Page_Load(object sender, EventArgs e)
{
...
// Workaround for PayPal form problem
GhostForm mainForm = new GhostForm();
mainForm.RenderFormTag = false;
}

Anonymous said...

This works for me too.

protected void Page_Load(object sender, EventArgs e)
{
this.Form.Action = "https://www.sandbox.paypal.com/cgi-bin/webscr";
this.Form.Method = "post";
}

Craig said...

Just a note for those thinking of using this method:

protected void Page_Load(object sender, EventArgs e)
{
this.Form.Action = "https://www.sandbox.paypal.com/cgi-bin/webscr";
this.Form.Method = "post";
}

BE CAREFUL: because every control that does a postback on your page will then post to the URL you set as the form action, not just your PayPal button.

Ivan said...

AS someone said, adding an empty "form /form" tag before the actual form did the trick :)

Universitas Curator said...

Outstanding, 10x

Braden said...

Hi,

I am trying to do this and followed all of the comments in this thread and I am getting this error:

The base class includes the field 'mainForm', but its type (NameSpace.GhostForm) is not compatible with the type of control (NameSpace.GhostForm).

Any ideas?

RajC said...

Hi,
I am trying to use the GhostForm but got the same error that Braden got.
Did anyone solve it??

Nouman Umar said...

I have added GhostForm to my page but it's still saying that an aspx page can have only one form..
Can anyone tell me what's the problem

Anonymous said...

1.
Add GhostForm class to your App_code folder.
2. add <%@ Register TagPrefix="ns" Namespace="MyNameSpace" %> to your master page
3. Replace your form tag on master page with the following code
<ns:GhostForm id="_mainForm" runat="server">
3. on the actual page (default.aspx?) find the form control from code behind eg.
GhostForm mainForm = Page.Form as GhostForm;
mainForm.RenderFormTag = false;
Now you can send the request to paypal.

Anonymous said...

I am having the same problem as Braden, has anyone come up with a solution?

Liam Galvin said...

Thanks for this, it works perfectly - exactly what I was looking for!

Anonymous said...

The empty form trick solved my problem too...

Bill Harris said...

This solution works very well, but there is a limitation it seems. When I need to do a postback, there is no form tag to support it, so it fails.

However, you can successfully render a dynamically-generated page using data-bound controls as long as you do not need a postback.