Interestingly, it worked great on IE and Safari, but failed on Chrome and Firefox. What was even stranger was that everything worked properly when debugging in Visual Studio, but the failure occurred when the site was deployed to IIS.
So the failure occurred only when running Firefox or Chrome against IIS. The same website with IE and Safari worked great, and the same website on Cassini worked great in any web browser.
I ended up using WinDiff to compare the HTML output differences between what was served up from Cassini (Visual Studio during debugging) and IIS. There was one line that was different:
<form name="aspnetForm" method="post" action="/default.aspx?" id="aspnetForm">
<form name="aspnetForm" method="post" action="/" id="aspnetForm">
That was the clue that I needed. Apparently Cassini was directing users to /default.aspx, whereas IIS was directing users to / (which loaded default.aspx as the default page). Sure enough, if I went on the site on IIS and manually typed in /default.aspx in the address bar and then clicked a button on the page, the POST worked properly.
Requiring users to type that in manually is not a great solution, so I added a default.htm page that redirects to default.aspx, solving the problem. Default.aspx is now explicitly shown in the address bar, and the problem is no more.