My wife Susie recently started a new membership organization called the National Association of Pet Rescue Professionals (NAPRP). The idea behind NAPRP is to make resources available to shelters and other animal rescue organizations so they can save the lives of more animals.
Members gain access to resources and meet each other virtually through the member web site (www.naprp.com), which we developed with DotNetNuke (DNN). DNN may not be the greatest platform for developing a membership site, but it has proven to be a decent starting point for our new organization. Despite various installation and configuration challenges, using it saved me from a whole lot of ASP.NET development work.
AWeber vs. DotNetNuke
The biggest problem we ran into was giving our members a way to subscribe to the NAPRP newsletter. We use AWeber to manage the subscriptions and to send an auto-responder: 101 Fundraising Ideas for Rescues, Shelters, and Humane Organizations.
AWeber conveniently gives you a chunk of HTML to insert into your page. Susie initially attempted to insert that chunk into a text/html module. That didn’t work because you are effectively creating a form within a form. All DNN pages, like all normal ASPX pages, are wrapped with a surrounding FORM tag that is essential to their operation. You can’t just yank the form tag out of the AWeber code either, because then you are creating a bunch of INPUT tags that are not "sited" on the page (i.e. they aren’t hooked to an event handler). Even if you did post back the form, the underlying page would have no idea what to do with it.
Form Master to the Rescue — Almost
We figured that we weren’t alone with having this problem so Susie did some research into the issue on the DNN forum and the AWeber site. One proposed solution was to use the Form Master module by Code 5 Systems. This is a $50 module that lets you create forms within a DNN page. One of its features is that it can do a "silent post," which means you can give it a URL and it will generate a post for you upon submit.
The silent post option sounded good, so we ran with it. It worked, too, but only for a while. We recently heard from a subscriber who wondered why she never got her confirmation email. AWeber uses a double-opt in system that requires subscribers to verify their email address by clicking a link in a confirmation message. It turns out that, for about the past month, our subscribers have not been getting their confirmation messages, which is why we have had no subscribes. We just thought no one was subscribing yet because the site was so new.
Server-Side Posting Forbidden
We contacted AWeber technical support to find out why the form was working and then stopped working. Eventually, the dirty secret was revealed: AWeber limits how many subscribe requests can come from a specific IP address. Presumably, this is to prevent DOS attacks and mass automated subscribes.
Form Master failed us in this situation because the silent post operation takes place on the server. The server, not the client, is posting the form, so AWeber sees the same IP address over and over. Eventually they stopped accepting the requests. Unfortunately, AWeber does not give the list owner or the subscriber an indication that this has happened, which is why we remained ignorant for so long. The subscriber gets redirected to the thank you page as if everything went fine.
Of course, you would think that our subscribers would eventually notice that they did not get their confirmation email and that they weren’t getting the auto-responder. And so someone eventually did…the day before we were supposed to launch.
Disappointing HTML Solution
With no time to spare, we had to come up with a solution fast. We knew that the subscribe form would work fine if we put it on a plain HTML Web page, so that’s what we did for the short term. We linked to the HTML page from the membership site and linked back into the site from the HTML page. It was a real hack, but it worked.
The next morning, the hacked solution was really eating at my soul. Now that I knew the full nature of the problem, I figured there just had to be a way to get around it. I put on my Determined Programmer hat and started digging into DNN internals, ASP.NET internals, and page navigation issues. Everyone, including the dogs, knows to stay out of my hair while I’m working a problem like this.
Rolling Up My Sleeves
What I needed to do was conceptually very simple: I had to submit the subscriber form input fields to the AWeber URL, but I had to do it from the client, not the server.
My first step was to create a testing sandbox so I could isolate as many variables as possible. I started with a dead simple ASP.NET page with the AWeber form inside it. Rather than post to the AWeber site, I created a page that did nothing but dump the contents of Request.Form so I could see what was getting passed from my test page. Armed with these tools, I started tweaking.
Cross-Page Posting in DNN
The first problem to overcome was getting the form to post to the right target page. I needed a way to override the "action" of the page’s form tag.
ASP.NET 2.0 introduced the PostBackUrl property on the Button control that lets you do this. Microsoft calls it a cross-page post. I knew it would work fine in my test project, but it would fail in DNN because you can’t use ASP.NET server controls within a text/HTML module. There’s no server code to emit the corresponding HTML tag.
The next best thing was to go ahead and put a button form into my test project, and then do a view source on the resulting HTML page to see how the control was rendered. It turns out that the button gets rendered as a submit input tag. The tag uses the __doPostbackWithOptions function to submit the form. Now I was getting somewhere.
I put the submit input tag into the DNN site and tried it out. The cross-page post seemed to work fine, but the target page got an empty Form collection. That problem made sense when I looked at the raw http headers and discovered that the DNN form is encoded as multipart/form-data, which packages your form variables along with any files you may be uploading through your Web page. The page receiving the post has to be expecting this encoding in order to handle it properly. In the old days of classic ASP, you needed a "posting acceptor" component to disassemble the data. I know because I wrote one in VB6 several years ago.
Form Encoding Override
First, I verified that AWeber would not accept multipart/form-data, and that turned out to be the case. So, back to the drawing board, as they say.
I finally decided that the built-in ASP.NET submit methods were just not going to do the trick for me. I needed to override not only the form action, but also the encoding, and __doPostbackWithOptions doesn’t let you do the latter. In the end, I created my own function and popped it into the DNN text/HTML module just above my AWeber form:
<script type="text/javascript"> /* <![CDATA[ */ function __doExternalPost(targetUrl) { theForm.encoding = "application/x-www-form-urlencoded"; theForm.action = targetUrl; theForm.submit(); } /* ]]> */ </script>
The function first changes the form encoding back to the ASP.NET default of "application/x-www-form-urlencoded." It then overrides the form action to use the passed target URL. Finally, it submits the form. In case you are wondering, the "theForm" object is initialized by standard ASP.NET JavaScript early in the page. If you do a View Source on any ASP.NET page, you’ll see the initialization code in a SCRIPT tag that also defines the __doPostBack function.
I changed the input tag to use my new function:
<input type="submit" name="btnSubmit" value="Subscribe" onclick="javascript:__doExternalPost('http://www.aweber.com/scripts/addlead.pl');" id="btnSubmit" />
I discovered that I could also use an anchor tag (i.e. a rendered Hyperlink or LinkButton control):
<a id="newsletterSubscribe" title="Subscribe" class="CommandButton" href="javascript:__doExternalPost('http://www.aweber.com/scripts/addlead.pl');"> Subscribe</a>
Success at last! Our subscribe submissions now get to AWeber successfully.
A Few Final Tweaks
Of course, when you post the form, you pass more than just the AWeber form fields. You also pass every other field in the ASP.NET form. Susie didn’t like the extra junk being passed through on her notification emails, so I cleared VIEWSTATE, the largest offender (__dnnVariable is another candidate). The updated __doExternalPost looks like this:
<script type="text/javascript"> /* <![CDATA[ */ function __doExternalPost(targetUrl) { theForm.__VIEWSTATE.value = ""; theForm.encoding = "application/x-www-form-urlencoded"; theForm.action = targetUrl; theForm.submit(); } /* ]]> */ </script>
Here’s what the full AWeber form code finally looked like:
<input type="hidden" name="meta_web_form_id" value="1234567890"> <input type="hidden" name="meta_split_id" value=""> <input type="hidden" name="unit" value="naprp"> <input type="hidden" name="redirect" value="http://www.naprp.com/Thankyou/tabid/55/Default.aspx"> <input type="hidden" name="meta_redirect_onlist" value=""> <input type="hidden" name="meta_adtracking" value=""> <input type="hidden" name="meta_message" value="1"> <input type="hidden" name="meta_required" value="from"> <input type="hidden" name="meta_forward_vars" value="0"> <table> <tr> <td class="Normal"><b>* Email</b></td> <td><input type="text" name="from" value="" size="30"></td> </tr> <tr> <td class="Normal"><b>* Name</b></td> <td><input type="text" name="name" value="" size="30"></td> </tr> <tr> <td align="center" colspan="2"> <a id="newsletterSubscribe" title="Subscribe" class="CommandButton" href="javascript:__doExternalPost('http://www.aweber.com/scripts/addlead.pl');"> Subscribe</a></td> </tr> </table>
A Solution for ASP.NET Cross-Site Posting
Although I needed my __doExternalPost function to solve the AWeber problem, you could use the same technique for any situation where you needed to post information from a DotNetNuke page to an off-site page. For example, you could use the same technique for PayPal buttons.
Points of Failure
After sharing emails with several visitors who had troubles using the technique I described here, I discovered that there are two main reasons why you might have problems.
The first is that the button script was designed to work with DotNetNuke, so it includes the following statement in the href JavaScript:
theForm.__dnnVariable.value='';
You need to take that out if you are NOT hosting the AWeber form on a DotNetNuke site.
The second is that not all ASP.NET pages inject the script at the top of the page that defines "theForm". This usually happens if your page does not include any ASP.NET controls that cause a postback. You can tell if that is what’s going on by doing a "view source" on the page from your browser. You should see the block of code shown below. If not, you can insert it yourself (put it right after the opening form tag):
<script type="text/javascript"> //<![CDATA[ var theForm = document.forms['aspnetForm']; if (!theForm) { theForm = document.aspnetForm; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } //]]> </script>
Keep in mind that you will need to remove this block of code if you later add posting controls to the page, because ASP.NET will want to put it in for itself.