Response.Redirect with POST instead of Get? - asp.net

We have the requirement to take a form submission and save some data, then redirect the user to a page offsite, but in redirecting, we need to "submit" a form with POST, not GET.
I was hoping there was an easy way to accomplish this, but I'm starting to think there isn't. I think I must now create a simple other page, with just the form that I want, redirect to it, populate the form variables, then do a body.onload call to a script that merely calls document.forms[0].submit();
Can anyone tell me if there is an alternative? We might need to tweak this later in the project, and it might get sort of complicated, so if there was an easy we could do this all non-other page dependent that would be fantastic.
Anyway, thanks for any and all responses.

Doing this requires understanding how HTTP redirects work. When you use Response.Redirect(), you send a response (to the browser that made the request) with HTTP Status Code 302, which tells the browser where to go next. By definition, the browser will make that via a GET request, even if the original request was a POST.
Another option is to use HTTP Status Code 307, which specifies that the browser should make the redirect request in the same way as the original request, but to prompt the user with a security warning. To do that, you would write something like this:
public void PageLoad(object sender, EventArgs e)
{
// Process the post on your side
Response.Status = "307 Temporary Redirect";
Response.AddHeader("Location", "http://example.com/page/to/post.to");
}
Unfortunately, this won't always work. Different browsers implement this differently, since it is not a common status code.
Alas, unlike the Opera and FireFox developers, the IE developers have never read the spec, and even the latest, most secure IE7 will redirect the POST request from domain A to domain B without any warnings or confirmation dialogs! Safari also acts in an interesting manner, while it does not raise a confirmation dialog and performs the redirect, it throws away the POST data, effectively changing 307 redirect into the more common 302.
So, as far as I know, the only way to implement something like this would be to use Javascript. There are two options I can think of off the top of my head:
Create the form and have its action attribute point to the third-party server. Then, add a click event to the submit button that first executes an AJAX request to your server with the data, and then allows the form to be submitted to the third-party server.
Create the form to post to your server. When the form is submitted, show the user a page that has a form in it with all of the data you want to pass on, all in hidden inputs. Just show a message like "Redirecting...". Then, add a javascript event to the page that submits the form to the third-party server.
Of the two, I would choose the second, for two reasons. First, it is more reliable than the first because Javascript is not required for it to work; for those who don't have it enabled, you can always make the submit button for the hidden form visible, and instruct them to press it if it takes more than 5 seconds. Second, you can decide what data gets transmitted to the third-party server; if you use just process the form as it goes by, you will be passing along all of the post data, which is not always what you want. Same for the 307 solution, assuming it worked for all of your users.

You can use this aproach:
Response.Clear();
StringBuilder sb = new StringBuilder();
sb.Append("<html>");
sb.AppendFormat(#"<body onload='document.forms[""form""].submit()'>");
sb.AppendFormat("<form name='form' action='{0}' method='post'>",postbackUrl);
sb.AppendFormat("<input type='hidden' name='id' value='{0}'>", id);
// Other params go here
sb.Append("</form>");
sb.Append("</body>");
sb.Append("</html>");
Response.Write(sb.ToString());
Response.End();
As result right after client will get all html from server the event onload take place that triggers form submit and post all data to defined postbackUrl.

HttpWebRequest is used for this.
On postback, create a HttpWebRequest to your third party and post the form data, then once that is done, you can Response.Redirect wherever you want.
You get the added advantage that you don't have to name all of your server controls to make the 3rd parties form, you can do this translation when building the POST string.
string url = "3rd Party Url";
StringBuilder postData = new StringBuilder();
postData.Append("first_name=" + HttpUtility.UrlEncode(txtFirstName.Text) + "&");
postData.Append("last_name=" + HttpUtility.UrlEncode(txtLastName.Text));
//ETC for all Form Elements
// Now to Send Data.
StreamWriter writer = null;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = postData.ToString().Length;
try
{
writer = new StreamWriter(request.GetRequestStream());
writer.Write(postData.ToString());
}
finally
{
if (writer != null)
writer.Close();
}
Response.Redirect("NewPage");
However, if you need the user to see the response page from this form, your only option is to utilize Server.Transfer, and that may or may not work.

Something new in ASP.Net 3.5 is this "PostBackUrl" property of ASP buttons. You can set it to the address of the page you want to post directly to, and when that button is clicked, instead of posting back to the same page like normal, it instead posts to the page you've indicated. Handy. Be sure UseSubmitBehavior is also set to TRUE.

This should make life much easier.
You can simply use Response.RedirectWithData(...) method in your web application easily.
Imports System.Web
Imports System.Runtime.CompilerServices
Module WebExtensions
<Extension()> _
Public Sub RedirectWithData(ByRef aThis As HttpResponse, ByVal aDestination As String, _
ByVal aData As NameValueCollection)
aThis.Clear()
Dim sb As StringBuilder = New StringBuilder()
sb.Append("<html>")
sb.AppendFormat("<body onload='document.forms[""form""].submit()'>")
sb.AppendFormat("<form name='form' action='{0}' method='post'>", aDestination)
For Each key As String In aData
sb.AppendFormat("<input type='hidden' name='{0}' value='{1}' />", key, aData(key))
Next
sb.Append("</form>")
sb.Append("</body>")
sb.Append("</html>")
aThis.Write(sb.ToString())
aThis.End()
End Sub
End Module

Thought it might interesting to share that heroku does this with it's SSO to Add-on providers
An example of how it works can be seen in the source to the "kensa" tool:
https://github.com/heroku/kensa/blob/d4a56d50dcbebc2d26a4950081acda988937ee10/lib/heroku/kensa/post_proxy.rb
And can be seen in practice if you turn of javascript. Example page source:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Heroku Add-ons SSO</title>
</head>
<body>
<form method="POST" action="https://XXXXXXXX/sso/login">
<input type="hidden" name="email" value="XXXXXXXX" />
<input type="hidden" name="app" value="XXXXXXXXXX" />
<input type="hidden" name="id" value="XXXXXXXX" />
<input type="hidden" name="timestamp" value="1382728968" />
<input type="hidden" name="token" value="XXXXXXX" />
<input type="hidden" name="nav-data" value="XXXXXXXXX" />
</form>
<script type="text/javascript">
document.forms[0].submit();
</script>
</body>
</html>

PostbackUrl can be set on your asp button to post to a different page.
if you need to do it in codebehind, try Server.Transfer.

#Matt,
You can still use the HttpWebRequest, then direct the response you receive to the actual outputstream response, this would serve the response back to the user. The only issue is that any relative urls would be broken.
Still, that may work.

I suggest building an HttpWebRequest to programmatically execute your POST and then redirect after reading the Response if applicable.

Here's what I'd do :
Put the data in a standard form (with no runat="server" attribute) and set the action of the form to post to the target off-site page.
Before submitting I would submit the data to my server using an XmlHttpRequest and analyze the response. If the response means you should go ahead with the offsite POSTing then I (the JavaScript) would proceed with the post otherwise I would redirect to a page on my site

In PHP, you can send POST data with cURL. Is there something comparable for .NET?
Yes, HttpWebRequest, see my post below.

The GET (and HEAD) method should never be used to do anything that has side-effects. A side-effect might be updating the state of a web application, or it might be charging your credit card. If an action has side-effects another method (POST) should be used instead.
So, a user (or their browser) shouldn't be held accountable for something done by a GET. If some harmful or expensive side-effect occurred as the result of a GET, that would be the fault of the web application, not the user. According to the spec, a user agent must not automatically follow a redirect unless it is a response to a GET or HEAD request.
Of course, a lot of GET requests do have some side-effects, even if it's just appending to a log file. The important thing is that the application, not the user, should be held responsible for those effects.
The relevant sections of the HTTP spec are 9.1.1 and 9.1.2, and 10.3.

Typically, all you'll ever need is to carry some state between these two requests. There's actually a really funky way to do this which doesn't rely on JavaScript (think <noscript/>).
Set-Cookie: name=value; Max-Age=120; Path=/redirect.html
With that cookie there, you can in the following request to /redirect.html retrieve the name=value info, you can store any kind of information in this name/value pair string, up to say 4K of data (typical cookie limit). Of course you should avoid this and store status codes and flag bits instead.
Upon receiving this request you in return respond with a delete request for that status code.
Set-Cookie: name=value; Max-Age=0; Path=/redirect.html
My HTTP is a bit rusty I've been going trough RFC2109 and RFC2965 to figure how reliable this really is, preferably I would want the cookie to round trip exactly once but that doesn't seem to be possible, also, third-party cookies might be a problem for you if you are relocating to another domain. This is still possible but not as painless as when you're doing stuff within your own domain.
The problem here is concurrency, if a power user is using multiple tabs and manages to interleave a couple of requests belonging to the same session (this is very unlikely, but not impossible) this may lead to inconsistencies in your application.
It's the <noscript/> way of doing HTTP round trips without meaningless URLs and JavaScript
I provide this code as a prof of concept: If this code is run in a context that you are not familiar with I think you can work out what part is what.
The idea is that you call Relocate with some state when you redirect, and the URL which you relocated calls GetState to get the data (if any).
const string StateCookieName = "state";
static int StateCookieID;
protected void Relocate(string url, object state)
{
var key = "__" + StateCookieName + Interlocked
.Add(ref StateCookieID, 1).ToInvariantString();
var absoluteExpiration = DateTime.Now
.Add(new TimeSpan(120 * TimeSpan.TicksPerSecond));
Context.Cache.Insert(key, state, null, absoluteExpiration,
Cache.NoSlidingExpiration);
var path = Context.Response.ApplyAppPathModifier(url);
Context.Response.Cookies
.Add(new HttpCookie(StateCookieName, key)
{
Path = path,
Expires = absoluteExpiration
});
Context.Response.Redirect(path, false);
}
protected TData GetState<TData>()
where TData : class
{
var cookie = Context.Request.Cookies[StateCookieName];
if (cookie != null)
{
var key = cookie.Value;
if (key.IsNonEmpty())
{
var obj = Context.Cache.Remove(key);
Context.Response.Cookies
.Add(new HttpCookie(StateCookieName)
{
Path = cookie.Path,
Expires = new DateTime(1970, 1, 1)
});
return obj as TData;
}
}
return null;
}

Copy-pasteable code based on Pavlo Neyman's method
RedirectPost(string url, T bodyPayload) and GetPostData() are for those who just want to dump some strongly typed data in the source page and fetch it back in the target one.
The data must be serializeable by NewtonSoft Json.NET and you need to reference the library of course.
Just copy-paste into your page(s) or better yet base class for your pages and use it anywhere in you application.
My heart goes out to all of you who still have to use Web Forms in 2019 for whatever reason.
protected void RedirectPost(string url, IEnumerable<KeyValuePair<string,string>> fields)
{
Response.Clear();
const string template =
#"<html>
<body onload='document.forms[""form""].submit()'>
<form name='form' action='{0}' method='post'>
{1}
</form>
</body>
</html>";
var fieldsSection = string.Join(
Environment.NewLine,
fields.Select(x => $"<input type='hidden' name='{HttpUtility.UrlEncode(x.Key)}' value='{HttpUtility.UrlEncode(x.Value)}'>")
);
var html = string.Format(template, HttpUtility.UrlEncode(url), fieldsSection);
Response.Write(html);
Response.End();
}
private const string JsonDataFieldName = "_jsonData";
protected void RedirectPost<T>(string url, T bodyPayload)
{
var json = JsonConvert.SerializeObject(bodyPayload, Formatting.Indented);
//explicit type declaration to prevent recursion
IEnumerable<KeyValuePair<string, string>> postFields = new List<KeyValuePair<string, string>>()
{new KeyValuePair<string, string>(JsonDataFieldName, json)};
RedirectPost(url, postFields);
}
protected T GetPostData<T>() where T: class
{
var urlEncodedFieldData = Request.Params[JsonDataFieldName];
if (string.IsNullOrEmpty(urlEncodedFieldData))
{
return null;// default(T);
}
var fieldData = HttpUtility.UrlDecode(urlEncodedFieldData);
var result = JsonConvert.DeserializeObject<T>(fieldData);
return result;
}

Related

how to send user to another page (external url) with post method

i want to redirect user to payment page in asp.net core with post method.
i did it in asp.net like this:
public void Post()
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Write("<html><head>");
HttpContext.Current.Response.Write(string.Format("</head><body onload=\"document.{0}.submit()\">", m_FormName));
HttpContext.Current.Response.Write(string.Format("<form name=\"{0}\" method=\"{1}\" action=\"{2}\" >", m_FormName, m_Method, m_Url));
for (int i = 0; i < Inputs.Keys.Count; i++)
{
HttpContext.Current.Response.Write(string.Format("<input name=\"{0}\" type=\"hidden\" value=\"{1}\">", Inputs.Keys[i], Inputs[Inputs.Keys[i]]));
}
HttpContext.Current.Response.Write("</form>");
HttpContext.Current.Response.Write("</body></html>");
}
should i use:
HttpContext.Response.WriteAsync
if yes how it work if so many line write by await.
is there any better way to do that?
What you're wanting to do is not possible. There is no way to make the client issue a POST. You can't make the client do anything. You can suggest that they request another URL (a redirect), but it's 100% up to the client whether or not to actually do that, and when and if the client does, it will be requested via GET only.
The only way to start a flow like this is to present the user with an HTML form with a submit button and an action of where you want them to go to begin the flow. It is then on the client to click that button.
You can use redirect with status code 308, I think this answer is the best 'Redirect to URL with POST method in Asp.Net Core'

Unable to get property 'value' of undefined or null reference

I have registration form and button. OnClick - I call function on server side which make a validation of user's zip code at Database with Zipcodes. If validation passed successfully - user's data stored in Database (here I continue use server function). But if ZipCode does not match - I call Javascript function where I ask if user still wants to save his data to DB. and If yes - I save it using Ajax request. Problem is when I call Javascript function - firstly it should receive user's data on client side. But when reading data happens - I receive an error "Unable to get property 'value' of undefined or null reference". But user's data still exist at the form's fields. It seems that the data that read by the server from the form once - reset somewhere - and can not be read a second time on the client.
Here is my ASP Form
<body>
<form id="frmZipValidation" runat="server">
<div>
<asp:Label runat="server">Registration Form</asp:Label>
<asp:TextBox runat="server" ID="txtbxName"></asp:TextBox>
<asp:TextBox runat="server" ID="txtbxZipCode"></asp:TextBox>
<asp:DropDownList runat="server" ID="DDLCountry">
<asp:ListItem Text="Select country" Value="Select" Selected="True"></asp:ListItem>
<asp:ListItem Text="USA" Value="USA"></asp:ListItem>
<asp:ListItem Text="Canada" Value="Canada"></asp:ListItem>
</asp:DropDownList>
<asp:TextBox runat="server" ID="txtbxState"></asp:TextBox>
<asp:TextBox runat="server" ID="txtbxCity"></asp:TextBox>
<asp:Button runat="server" ID="btnSubmit" Text="Submit" OnClick="btnSubmit_Click"/>
</div>
</form>
</body>
Here is my Server Side
public partial class Default : System.Web.UI.Page
{
string Name;
string ZipCode;
string Country;
string State;
string City;
bool IsMatch;
Addresses dbAddresses = new Addresses();
User newUser;
protected void Page_Load(object sender, EventArgs e)
{
if (Request["Action"] != null && Request["Action"].Trim() != "")
{
if (Request["Action"] == "AddUser")
{
AddUser(Request["Name"], Request["ZipCode"], Request["Country"], Request["State"], Request["City"]);
}
}
}
private void AddUser(string UserName, string UserZip, string UserCountry, string UserState, string UserCity)
{
newUser = new User(UserName, UserZip, UserCountry, UserState, UserCity);
dbAddresses.Users.Add(newUser);
dbAddresses.SaveChanges();
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
if (IsValid)
{
ZipCode = txtbxZipCode.Text;
Country = DDLCountry.Text;
State = txtbxState.Text;
City = txtbxCity.Text;
Name = txtbxName.Text;
IsMatch = false;
List<ZipCode> ZipC = (from z in dbAddresses.Zips
where z.Zip == ZipCode
select z).ToList();
//If ZipCode entered by client do not exists at Database return false
if (!ZipC.Any())
{
IsMatch = false;
}
else
{
for (int i = 0; i < ZipC.Count; i++)
{
if (ZipC[i].Country.ToString() == Country)
{
if (ZipC[i].State.ToString() == State)
{
if (ZipC[i].City.ToString() == City)
{
AddUser(Name, ZipCode, Country, State, City);
//Message to the user that all saved successfully
Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "1", "<script>alert('Your data was saved successfully!');</script>");
IsMatch = true;
break;
}
else
{
IsMatch = false;
break;
}
}
else
{
IsMatch = false;
break;
}
}
else
{
IsMatch = false;
break;
}
}
}
//If user's data are not match, then go to JS client code where - If user wants in any case to save data - make it using AJAX request
if (!IsMatch)
{
string clientScript = "AjaxRequestSaveToDB();";
this.Page.ClientScript.RegisterStartupScript(this.GetType(), "MyClientScript", clientScript);
}
}
}
}
And here is Javascript:
function AjaxRequestSaveToDB()
{
var SaveData = confirm('Zip/Postal code doesn’t match region. Are you sure you want to save this data?');
if (SaveData)
{
var UserName = document.getElementById('txtbxName').value;
var UserZipCode = document.getElementById('txtbxZipCode').value;
var UserCountry = document.getElementById('DDLCountry').value;
var USerState = document.getElementById('txtbxState').value;
var UserCity = document.getElementById('txtbxCity').value;
SendDataToServer('AddUser', UserName, UserZipCode, UserCountry, USerState, UserCity);
alert("You data was saved successfully!");
}
else { alert('Not saved');
}
}
}
function SendDataToServer(RequestType, Name, ZipCode, Country, State, City)
{
var xmlHttp = getXmlHttp();
var Url = "Default.aspx?Action=" + escape(RequestType)
+ "&Name=" + escape(Name)
+ "&ZipCode=" + escape(ZipCode)
+ "&Country=" + escape(Country)
+ "&State=" + escape(State)
+ "&City=" + escape(City);
xmlHttp.open("GET", Url, true);
xmlHttp.send();
}
A short book about Client-Server Communications using "Custom" AJAX requests.
In ASP.net programming (almost) every time the client interacts with the server, the client sends all of its information to the server and then throws out its old content and replaces it with the response the client received from the server. So the problem you were running into is that your asp:button on the client machine was sending information to your .aspx page on the server and the server was interpreting the information, realizing something was wrong and telling the client it should ask the user for more information but throw out all the information that had been previously entered.
The best way that I have found to get around this problem is to use what I call "custom AJAX requests." Basically this means that we write a string of XML and send it to an ASP handler page which is set up to accept the XML string and do something with it. In my travels I have slimmed this down to basically 3 parts. The first is the user interface which contains all of the markup and CSS(and validation), the second is the JavaScript file that contains all of the data gathering and the actual AJAX request and lastly there is the ashx file that handles the request from the client.
So to start you will need to set up your user interface. Something along the lines of:
<body>
<form id="frmZipValidation" runat="server">
<div>
<div class="label">Registration Form<div>
<asp:TextBox ID="txtbxName" class="txtbxName" ClientIDMode="Static" runat="server"></asp:TextBox>
<asp:TextBox ID="txtbxZipCode" class="txtbxZipCode" ClientIDMode="Static" runat="server" ></asp:TextBox>
<asp:DropDownList ID="DDLCountry" class="DDLCountry" ClientIDMode="Static" runat="server" >
<asp:ListItem Text="Select country" Value="Select" Selected="True"></asp:ListItem>
<asp:ListItem Text="USA" Value="USA"></asp:ListItem>
<asp:ListItem Text="Canada" Value="Canada"></asp:ListItem>
</asp:DropDownList>
<asp:TextBox ID="txtbxState" class="txtbxState" ClientIDMode="Static" runat="server" ></asp:TextBox>
<asp:TextBox ID="txtbxCity" class="txtbxCity" ClientIDMode="Static" runat="server" ></asp:TextBox>
<input id="btnSubmit" class="btnSubmit" type="button" value="Save" onclick="SubmitForm()" />
</div>
</form>
</body>
Couple things to note with this:
The button to submit the form is NOT an ASP button but a HTML button.
All of the input controls are ASP controls but they have the ClientIDMode set to Static, this will only work in .NET 4.0 or higher.
We set the class to the same thing as the ID in case we aren't using .NET 4.0 or higher. Any CSS classes that you want to also add to the control can be added after the dummy ID class.(for my examples I'm assuming you are in .NET 4.0 but I can easily switch them to work without the ClientIDMode attribute if you need)
The second piece to the puzzle is the JavaScript. There are a couple ways that we can accomplish what we need. The first is by using vanilla JS without the help of any plugins or external libraries. This saves a very small amount of processing time, a marginal amount of loading time and can accomplish everything we ask of it. But, if we include an external library, JQuery, and plugin, JQuery Validation, then we can make our lives a whole heck of a lot easier during the programming phase by reducing the amount of code we have to write by a factor of about 10. And if we are really concerned about the load times then we can use the client cache to store the external libraries so that they only have to download them once. So whether or not you decide to use any external JavaScript libraries is up to what your project needs but since you are only concerned with validating that the zip code is not empty I will not use any JQuery but I just thought it would be worth mentioning because of how streamlined it makes the process.
Once you are ready to submit your form your first step will be to validate that the zipcode is valid. You can do this a couple ways depending on how in depth you want to get. The quickest check would just be to verify that the zip code text box is not empty when the button is clicked. So to do that we would just need to do:
function SubmitForm() { //This will be assigned as the click handler on your button in your HTML
if (document.getElementById('txtbxZipCode').value != null && document.getElementById('txtbxZipCode').value != '') {
Save('YourHandler', GetQueryString, GetXmlString, SuccessHandler, FailureHandler);
} else {
//Your user needs to know what went wrong...
}
}
So, down to the meat and potatoes of this whole situation. The AJAX request. I've come up with a reusable function that handles the entire AJAX request that looks like:
function Save(handlerName, GetQueryString, GetXmlString, SuccessHandler, FailureHandler) {
// Date.GetTime gets the number of milliseconds since 1 January 1970, so we divide by 1000 to get the seconds.
end = (new Date().getTime() / 1000) + 30;
//This variable is the actual AJAX request. This object works for IE8+ but if you want backwards compatability for earlier versions you will need a different object which I can dig up for you if you need.
var xmlhttp = new XMLHttpRequest();
//This is the function that fires everytime the status of the request changes.
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
//Get all the headers to determine whether or not the request was successful. This is a header you will need to add to the response manually.
var xx = xmlhttp.getResponseHeader("Success");
//the object xx will be a string that you designate. I chose to use True as the indicator that it was successful because it was intuitive.
var x1 = xx.trim();
if (x1 != undefined && x1 == 'True' && (new Date().getTime() / 1000) < end) {
//If the response was successful and the timeout hasn't elapsed then we get the XML from the response and call the success handler
var xmlResponse = xmlhttp.responseXML;
SuccessHandler(sender, xmlResponse);
} else if ((new Date().getTime() / 1000) < end) {
//If the response was not successful and the timeout hasn't elapsed then we get the XML from the response and call the failure handler
var xmlResponse = xmlhttp.responseXML;
FailureHandler(sender, xmlResponse);
} //If the request was successful
} //If the readystate is 4 and the status is 200
} //OnReadyStateChanged function
//This gets the query string to be added to the url
var varString = GetQueryString();
//Build XML string to send to the server
var xmlString = GetXmlString();
//Open the request using the handler name passed in and the querystring we got from the function passed in
xmlhttp.open("POST", "../RequestHandlers/" + handlerName + ".ashx" + varString, true);
//This tells the handler that the content of the request is XML
xmlhttp.setRequestHeader("Content-Type", "text/xml");
//Send the request using the XML we got from the other function passed in.
xmlhttp.send(xmlString);
}
This function has a built in timeout which makes it so that if the server takes more than 30 seconds to respond to a request then any response that the client receives is ignored. For my implementations this is combined with another function that displays something to the user to tell them that the website is working on their request and if the time out elapses it tells them that a time out occurred.
The second thing this function does is it assumes that all handlers will be in a folder next to the root of your website named RequestHandlers. I use this set up just to consolidate all of my handler files but you can really change where it is looking to wherever you want.
The function itself takes in a string and four function pointers. The string represents the name of the handler that will be waiting to interpret the request, the four function pointers all have very specific jobs.
The first function pointer is GetQueryString this represents a function you will have to write that will append any variables that you deem necessary to the end of the URL being posted back to. This site gives a pretty accurate explanation of what the query string should be used for. For me a common GetQueryString function looks something like:
function GetPaymentQueryString() {
var varString = '';
varString = "?CCPayment=True";
return varString;
}
The second function pointer, GetXMLString, is used to create the XML string(go figure...) that will be sent to the handler page that we are posting back to. This string will represent the bulk of the request. Everything that should not be shown to anyone snooping your requests should be sent as an XML string, if you are really paranoid you can send it as an encrypted XML string but that's not, strictly speaking, necessary. It all depends on what you are sending, if its complete credit card information then, yeah, maybe you would want to consider it, but if its first and last names then encrypting it would be overkill.
A common GetXMLString function might look like:
function GetPaymentXmlString() {
var xmlString = '';
xmlString = '<?xml version="1.0" encoding="UTF-8"?><Address><ZipCode>' + document.getElementById('txtbxZipCode').value + '</ZipCode></Address>';
return xmlString;
}
The important part of that function is to get your XML right. The first tag is pretty universal and should be fine to use in most situations and then after that its all just matching the tags up. I left out a lot of your fields to save space.
The last two function pointers are what you will want to call if everything goes as planned and if something fails respectively. The way that I normally handle successful requests is to hide the inputs as a whole(usually by putting them inside of their own div section) and displaying a confirmation message of some sort. Failed requests can be a bit trickier because you have to tell the user why they failed. The way that I do that is by having a dummy div section above everything else on the page with some sort of special CSS attached to it that makes the div stand out in some way and if the request fails then I send a string of text from the server with my best guess of why it failed and assign it to the be displayed in the div section. How you decide to display the results to the user is obviously all dictated by the project itself. Since what you do when it succeeds or fails is basically on a project by project basis I can't really give a good generic example of what you should do so for this part you are on your own.
Now that we have those pieces in place, the last piece to make is the handler.
Basically for all intents and purposes a handler is basically an ASPX webpage with nothing on it. So the HTML that makes up your handler pages, which have the extension .ashx, will look like:
<%# WebHandler Language="VB" CodeBehind="YourHandler.ashx.cs" Class="YourHandler" %>
And that's it. There should be no other markup in your actual .ashx file. Obviously the name of the handler will change depending on what you are doing.
The code behind when creating an ashx file by default will be a class that contains a single function named ProcessRequest. Basically you can treat this function as a sort of "request received" event. So in your case you would move the content of your btnSubmit_Click function to the ProcessRequest function in the ashx file. You can add any properties or other functions that you want but the ProcessRequest function must be present for the handler to work as far as I know.
One extra step that you will need to do is to get the information from the XML that was sent to your handler and also tell the response that you will be sending XML back to the client.
So to get the XML from the request you will need to do:
IO.StreamReader textReader = New IO.StreamReader(context.Request.InputStream);
context.Request.InputStream.Seek(0, IO.SeekOrigin.Begin);
textReader.DiscardBufferedData();
XDocument xml = XDocument.Load(textReader);
String zip = xml.Elements("Address").Elements("ZipCode").FirstOrDefault().Value;
In order to send XML back to the client you will need to add a couple headers to the response and you accomplish that by adding(I think this is the correct way to implement an interface in C# not positive on this point though):
class YourHandler : System.Web.IHttpHandler, System.Web.SessionState.IReadOnlySessionState
under your class definition and:
context.Response.ContentType = "text/xml";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.Cache.SetAllowResponseInBrowserHistory(True);
to the beginning of your ProcessRequest function. Those six lines tell the client it will be receiving XML and not to cache any of the response which will ensure that your clients always see the most up-to-date content.
So. There it is. You should now have the framework to validate user input, create an AJAX request, send the request to a custom handler, accept XML from the client, write XML to the client and display the res-...I knew I forgot something...
What is the client supposed to do with the XML it gets from the server? throw it at the wall and see what sticks? No that won't work. You'll need a way to interpret the XML on the client side. Luckily the XMLHttpRequest object has been written to make this task a lot easier than it sounds.
You may have noticed that I set up my success and failure handlers to take a sender object and an XML object. The sender is really overkill and can be ignored(or removed) for this example to work fine. The XML object is what we are concerned with for now. Before we even get into the client side I must mention that you will have to go through the same process on the server side as you did on the client side and manually write your XML string including all the values you want the client to know about. For this example I'm going to assume you want to display a FriendlyMessage to the user. To write the response to the client you will do something like:
using (System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(context.Response.Output)) {
context.Response.AddHeader("Success", true);
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml("<?xml version='1.0' encoding='UTF-8'?><Response><FriendlyMessage>" + Message + "</FriendlyMessage></Response>");
doc.WriteTo(writer);
writer.Flush();
writer.Close();
}
On the client side to get the FriendlyMessage from the XML you will need to do:
xml.getElementsByTagName("FriendlyMessage")[0].childNodes[0].nodeValue
Now this line makes a few assumptions. Like, you may want to add some checks in to make sure xml.getElementsByTagName("FriendlyMessage") actually has children before trying to evaluate them. Those sorts of checks are up to your discretion.
This time I think I've actually covered all the steps. I hope my "little" guide helps you and I didn't bore you too much. I apologize for the length but its sort of a process so getting it right takes a few steps. Once you get the base line in place and working it really lends itself to any situation. This layout also makes your user experience much better than having them wait for full trips to the server each time.
I sincerely hope this helps you get your project done and that I haven't skipped a step or something equally as embarrassing...

Retrieving HTML pages from a 3rd party log in website with ASP.NET

Our Situation:
Our team needs to retrieve log information from a 3rd party website (Specifically, this log
information is call logs -- our client rents an 866 number. When calls come in, they assist
people and need to make notes accordingly in our application that will correspond with the
current call). Our client has a web account with the 3rd party that allows them to view the
current call logs (date/time, phone number, amount of time on each call, etc).
I contacted the developer of their website and inquired about API or any other means of syncing
our database with their constantly updating database. They currently DO NOT support API. I
informed them of my situation and they are perfectly fine with any way we can retrieve the
information (bot/crawler). *The 3rd party said that they are working on API but could not give
us a general timeline as to when it will be up... and as with every client, they need to start
production ASAP.
I completely understand that if the 3rd party were to change their HTML layout, it may cause a
slight headache for us (sorting the data from the webpage). That being said, this is a temporary
solution to a long term issue. Once they implement their API, we will switch them over to it.
So my question is this:
What is the best way to log into the 3rd party website (see image: http://i903.photobucket.com/albums/ac239/jreedinc/customtf.jpg)
and retrieve certain HTML pages? We have reviewed source codes of webcrawlers, but none of them
have the capability of storing cookies and posting information back to the website (with log in information). We would prefer to do this in ASP.NET.
Is there another way to accomplish logging on to the website, then retrieving said information?
The classes you'll need to use are in the System.Net namespace. Below is some quick and dirty proof of concept code. To login in to a site that uses form login + cookies for security and then scrape the HTML output of a page.
In order to parse the HTML results you'll need to use an additional tool.
Possible HTML parsing tools.
SgmlReader, can convert HTML to XML. You then use .NET's XML features to extract data from the XML.
http://code.msdn.microsoft.com/SgmlReader
HTML Agility Pack, allows XPath queries against HTML documents.
http://htmlagilitypack.codeplex.com/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
class WebWorker {
/// <summary>
/// Cookies for use by web worker
/// </summary>
private System.Collections.Generic.List `<System.Net.Cookie` > cookies = new List < System.Net.Cookie > ();
public string GetWebPageContent(string url) {
System.Net.HttpWebRequest request = (System.Net.HttpWebRequest) System.Net.WebRequest.Create(url);
System.Net.CookieContainer cookieContainer = new System.Net.CookieContainer();
request.CookieContainer = cookieContainer;
request.Method = "GET";
//add cookies to maintain session state
foreach(System.Net.Cookie c in this.cookies) {
cookieContainer.Add(c);
}
System.Net.HttpWebResponse response = request.GetResponse() as System.Net.HttpWebResponse;
System.IO.Stream responseStream = response.GetResponseStream();
System.IO.StreamReader sReader = new System.IO.StreamReader(responseStream);
System.Diagnostics.Debug.WriteLine("Content:\n" + sReader.ReadToEnd());
return sReader.ReadToEnd();
}
public string Login(string url, string userIdFormFieldName, string userIdValue, string passwordFormFieldName, string passwordValue) {
System.Net.HttpWebRequest request = (System.Net.HttpWebRequest) System.Net.WebRequest.Create(url);
System.Net.CookieContainer cookieContainer = new System.Net.CookieContainer();
request.CookieContainer = cookieContainer;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
string postData = System.Web.HttpUtility.UrlEncode(userIdFormFieldName) + "=" + System.Web.HttpUtility.UrlEncode(userIdValue) +
"&" + System.Web.HttpUtility.UrlEncode(passwordFormFieldName) + "=" + System.Web.HttpUtility.UrlEncode(passwordValue);
request.ContentLength = postData.Length;
request.AllowAutoRedirect = false; //allowing redirect seems to loose cookies
byte[] postDataBytes = System.Text.Encoding.UTF8.GetBytes(postData);
System.IO.Stream requestStream = request.GetRequestStream();
requestStream.Write(postDataBytes, 0, postDataBytes.Length);
System.Net.HttpWebResponse response = request.GetResponse() as System.Net.HttpWebResponse;
// System.Diagnostics.Debug.Write(WriteLine(new StreamReader(response.GetResponseStream()).ReadToEnd());
System.IO.Stream responseStream = response.GetResponseStream();
System.IO.StreamReader sReader = new System.IO.StreamReader(responseStream);
System.Diagnostics.Debug.WriteLine("Content:\n" + sReader.ReadToEnd());
this.cookies.Clear();
if (response.Cookies.Count > 0) {
for (int i = 0; i < response.Cookies.Count; i++) {
this.cookies.Add(response.Cookies[i]);
}
}
return "OK";
}
} //end class
//sample to use class
WebWorker worker = new WebWorker();
worker.Login("http://localhost/test/default.aspx", "uid", "bob", "pwd", "secret");
worker.GetWebPageContent("http://localhost/test/default.aspx");
I used a tool recently called WebQL (its a web scraper tool that lets the developer use SQL like syntax to scrape information from web pages.
WebQL on Wikipedia
This is actually a relatively simple operation. What you need to do is get the page that the screenshot posts back to (something like login.php, etc) and then construct a webrequest to that page with the login data you have. You will most likely get back a cookiecontainer that will have your login cookie to use on all subsequent requests.
You can look at this MSDN article for the basics of how to do it, but their write-up is kind of confusing. Look at the community comments at the end for an example of how to post back page variables (like the username and password). You will need to make sure you pass the cookiecontainer around on subsequent requests.
Unfortunately .NET does not natively have something like WWW::Mechanize, but the Webclient does have an "upload value" which might make it easier. You will still have to manually parse the page to figure out what fields you need to pass.

Passing flash variables to asp.net

I don't know much about Flash but we are working on a site that has a flash form and when the users pick an option, like selecting a value from a drop down list, we need the value to be passed to asp.net server-side code. What's the easiest way to do this?
Flash can invoke server side service. So use GET or POST to pass data
You could explore these options:
1) Communicate between the SWF and the containing page through JavaScript
2) Communicate via asp.net webservices from the SWF directly to the webservice.
3) Not sure but could probably do a POST to a processing aspx page?
HTH
I think a good option is to use the XML class so consider this:
var xmlRequest = new XML();
xmlRequest.onLoad = parseXMLResponse;
xmlRequest.load("http://yourpathtoyourserver/file.aspx?listselectedvalue=something");
function parseXMLRequest(loaded)
{
trace("hi");
}
You can also have the page give you data back this way so it's not just one way communication.
Assuming you are using Action Script 2.
Read the important notes at the bottom of each codes pertain to sending and retrieving data from flash to .net page. Explanation of the code is in the comment inside the code.
Flash Part (Action Script 2)
//function to send collected form data to asp.net page
//use other control/button to call this function
//important: in order for the 'onLoad' event to work correctly, this function has to be 'Void'
function sendForm():Void
{
//create LoadVars object
var lv_in:LoadVars = new LoadVars();
var lv_out:LoadVars = new LoadVars();
//set onLoad event
lv_in.onLoad = function(success:Boolean)
{
//if success, meaning data has received from .net page, run this code
if (success)
{
//lv_in.status is use to get the posted data from .Net page
statusMsg.text = "Thank you for filling up the form!" + lv_in.status;
}
//if fail, run this code
else
{
statusMsg.text = "The form you are trying to fill up has an error!";
}
}
//this is the collected data from the form
lv_out.userName = txtUserName.text;
lv_out.userAddress = txtUserAddress.text;
lv_out.userBirthday = txtUserBirthday.text;
//begin invoke .net page
lv_out.sendAndLoad("ProcessDataForm.aspx", lv_in, "POST");
}
Important note:
The function that contain onLoad event, in this case sendForm function, has to be Void function, meaning it's not returning value. If this function return value, what happen is the function will be executed all the way without waiting for the returned data from .net page, thus the onLoad event will not be set properly.
.Net Part
public void ProcessData
{
//process the data here
Response.Write("status=processed&");
}
Important note:
To send data/message back to flash, you can use Response.Write. However, if you want Action Script to parse the posted message/data from .Net page keep in mind you have to include & symbol at the end of the message. When parsing data/message, Action Script will stop at & symbol, thus leave the rest of the message alone and only get the message under sent variable.

Getting HTML from a page behind a login

This question is a follow up to my previous question about getting the HTML from an ASPX page. I decided to try using the webclient object, but the problem is that I get the login page's HTML because login is required. I tried "logging in" using the webclient object:
WebClient ww = new WebClient();
ww.DownloadString("Login.aspx?UserName=&Password=");
string html = ww.DownloadString("Internal.aspx");
But I still get the login page all the time. I know that the username info is not stored in a cookie. I must be doing something wrong or leaving out an important part. Does anyone know what it could be?
Try setting the credentials property of the WebClient object
WebClient ww = new WebClient();
ww.Credentials = CredentialCache.DefaultCredentials;
ww.DownloadString("Login.aspx?UserName=&Password=");
string html = ww.DownloadString("Internal.aspx");
Just pass valid login parameters to a given URI. Should help you out.
If you don't have login information you shouldn't be trying to circumvent it.
public static string HttpPost( string URI, string Parameters )
{
System.Net.WebRequest req = System.Net.WebRequest.Create( URI );
req.ContentType = "application/x-www-form-urlencoded";
req.Method = "POST";
byte[] bytes = System.Text.Encoding.ASCII.GetBytes( Parameters );
req.ContentLength = bytes.Length;
System.IO.Stream os = req.GetRequestStream();
os.Write( bytes, 0, bytes.Length );
os.Close();
System.Net.WebResponse resp = req.GetResponse();
if ( resp == null ) return null;
System.IO.StreamReader sr = new System.IO.StreamReader( resp.GetResponseStream() );
return sr.ReadToEnd().Trim();
}
Well does opening the page in a brower with "Login.aspx?UserName=&Password=" normaly work?
Some pages may not allow login using data provided in the url, and that it must be entered in the login form on the page and then submitted.
The only other reason I can think of then is that the web page is intentionally blocking it from loggin in. If you have access to the code, take a look at the loggin system used to see if theres anything designed to block such logins.
Use Fiddler to see the HTTP requests and responses that happen when you do it manually through the browser.
#Fire Lancer: I asked myself that same question during my tests, so I checked, and it does work from a browser.
As the aspx page I was trying to get was in my own projct, I could use the Server.Execute method. More details in my answer to my original question
Use Firefox with the LiveHttpHeaders plugin.
This will allow you to login via an actual browser and see EXACTLY what is being sent to the server. My first question would be to verify that it isn't expecting a POST from the form. The example URL you are loading is sending the info via a querystring GET.

Resources