Inconsistent LinkButton PageMethod behavior in different browsers - c#

I have a LinkButton on a page that performs a post-back, but also has an onClientClick event. The idea is to set some session variables in the background from client-side data (don't ask).
I've placed a break point in the web method to step through our code, and what we're experiencing is that depending on the browser, the PageMethods may return a success message, failure message, or no message at all. Additionally, the web method may or may not get called, regardless of the PageMethods result.
Here's a handy little chart of our results:
Browser PageMethods WebMethod
-------------- ------------- --------------------
IE 8, 9, 10 Success Called successfully
Safari 5.1.7 Failure *Never called*
Firefox 25.0.1 *Neither* Called successfully
Chrome v31 Failure Called successfully
That's four different browsers, and four different results.
I've tried generating the link button in both server-side and client-side code with the same effect, and without even setting the session variables in the WebMethod, with the same results.
The code can be reproduced with the following simple code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script type="text/javascript">
function doStuff() {
var a = 'a';
var b = 'b';
PageMethods.doStuffWebMethod(a, b, doStuffSuccess, doStuffFail);
}
function doStuffSuccess() {
alert(Success!');
}
function doStuffFail() {
alert(Failure!');
}
</script>
<html>
<body style="background-color:#f3f4f6;" >
<form runat="server" name="mainForm" id="mainForm" action="Test.aspx">
<asp:ScriptManager ID="ScriptManager1" EnablePageMethods="true" runat="server"></asp:ScriptManager>
<asp:LinkButton runat="server" CausesValidation="false" OnClientClick="doStuff();">Do stuff!</asp:LinkButton>
</form>
</body>
</html>
and
protected void Page_Load(object sender, EventArgs e)
{
LinkButton lbAdd = new LinkButton();
lbAdd.Text = "Web method test";
lbAdd.CausesValidation = false;
lbAdd.OnClientClick = "doStuff();";
mainForm.Controls.Add(lbAdd);
}
[WebMethod]
public static void doStuffWebMethod(string a, string b)
{
try
{
//System.Web.HttpContext.Current.Session["a"] = a;
//System.Web.HttpContext.Current.Session["b"] = b;
string x = a + b;
}
catch (Exception ex)
{
//
}
}
The question:
Why is my web method failing in Safari, and giving me one of three different return messages in three other browsers?
How can I change this code to get it to work in the browsers mentioned?

The reason the call to your web method is failing is because
your are not canceling the postback of your LinkButton.
You have to return false from the OnClientClick event to cancel
the postback. The code below should fix it:
function doStuff() {
var a = 'a';
var b = 'b';
PageMethods.doStuffWebMethod(a, b, doStuffSuccess, doStuffFail);
return false; // Cancel postback.
}
function doStuffSuccess() {
alert('Success!');
}
function doStuffFail() {
alert('Failure!');
}
<asp:LinkButton ID="mybutton" runat="server" CausesValidation="false" OnClientClick="return doStuff();">Do stuff!</asp:LinkButton>
For a more sophisticated solution for canceling the default behaviour of a browser (postback) please have a look on the following stackoverflow question and answer.
The reason you are getting those different results for the different browsers is maybe due to different implementations of the browser vendors. But I am not sure about this.
You can also verify this behaviour by creating a network protocol trace
(press F12 in Google Chrome browser and switch to the network tab).
The protocol in case you do not return false from the doStuff() method:
The page method doStuffWebMethod is called. Then you get the JavaScript message box. (HTTP POST)
Your WebForm.aspx is requested. (HTTP POST)
Then WebResource.axd and ScriptResource.axd is requested. (HTTP GET)
Number 2. and 3. shows that a postback is executed after the request of your page method.
The protocol in case you do return false from the doStuff() method:
Only the page method doStuffWebMethod is called. (HTTP POST)
The second trace clearly shows that there is no postback executed.
If you want the postback to happen for your LinkButton then you could manually trigger the postback in the JavaScript success handler using the __doPostBack() method after the page method comes back:
function doStuffSuccess() {
alert('Success!');
__doPostBack('<%= mybutton.UniqueID %>', ''); // trigger the postback.
}

Related

Update UI from code behind in real time

I've created a socket listener, and I need to display a div (keep it hidden, then make it visible), when the server detects a certain socket data.
I've tried to use a thread, but it doesn't update the UI in realtime, only if the page is reloaded or if you do a post back.
Here is an example of what I want to do, in this case I only want to update a textbox with codebehind data, I would like to do it without ajax, javascript or jQuery, if possible.
Code Behind:
protected void Page_Load(object sender, EventArgs e)
{
Thread t = new Thread(TestThread);
t.Start();
}
private void TestThread()
{
for (int i = 0; i <= 1000000000; i++)
{
myTextbox.Text += "1";
}
}
Webpage:
<asp:UpdatePanel runat="server" ID="myPanel" >
<ContentTemplate>
<asp:TextBox runat="server" ID="myTextbox" />
</ContentTemplate>
</asp:UpdatePanel>
what you are trying to do is change the UI (modify client components) from server code (code behind) in asp.net when the server already sent the data to the client.
you have to use client side scripting/coding.
As far as I know, this is not possible without using ajax or JavaScript.
ASP.NET is only responsible for generating the html that is sent from your web-server to the client. Once that html is rendered in the browser, the only way to update the html is via JavaScript.
You can do a Partial Render in WebForms using their AJAX handler without having to write jQuery by enabling a partialRenderingEnabled attribute in the ScriptManager tag. There's no way to do this without scripting or AJAX, the content has to get back to the server to run your code-behind somehow, but at least this way you don't have to write any JavaScript yourself.
For real-time web functionality check out :
SignalR
In a web enviornment, there is no way for the server side to reach out for the client except in the request-response scenario, where the client has to send a request to the server side, the server side handles the request and replies with a response.
EDIT :
SignalR uses the best available technique, websockets when available, if not, AJAX long polling technique in which the client polls(sends a request) to the server, and the server replies only when a certain change to the observed data happens, else SignalR tries repetitve AJAX requests, polling the server over and over until the server replies with the change on the observed data (Worst scenario !).
Also, HTML 5 contains web-sockets, i don't have enough information about it, but it maybe interesting to check out.
With minimal JavaScript you could utilise Server Sent Events for simplex data (from server to client) It doesn't have the overhead of Websockets which has a bit of cost in terms of establishing a connection. However I don't think all browsers are supported but there might be polyfill available for it (library to provide functionality in absence of native support)
Checkout:
http://www.html5rocks.com/en/tutorials/eventsource/basics/
In ASP.NET Threads gets aborted as soon as the page unloads, so this is not possibler you way, sorry.
Your approach could be using AJAX to poll the status from the server on regular intervals. Server on the other hand, will reply according to change in data.
in your ASP.NET Page, add the following script
<head>
<script type="text/javascript" language="javascript">
function poll()
{
var ajax;
if(window.XMLHttpRequest)
ajax = new XMLHttpRequest();
else
ajax = new ActiveXObject("Microsoft.XMLHTTP");
url = "yourPageOrHandler.aspx";
ajax.onreadystatechange = function()
{
if (ajax.readyState == 4 && ajax.status == 200)
{
// success
var receivedText = ajax.responseText;
}
else
{
// error, do the needful
}
}
ajax.open("GET", url, true);
ajax.send();
setTimeout("poll()", 1000); //polls every 1 second, you can change the duration here.
}
</script>
</head>
Start the polling before page finishes
<body>
.
.
.
.
<script type="text/javascript" language="javascript">
poll();
</script>
</body>
Alternatively, you can use the ASP.NET AJAX controls, but that will be heavier compared to JavaScript AJAX.
Glad to help! Please remember to accept the answer if you found it helpful.

Execute javascript code at the end of event handler

I'm building a web application on ASP.NET with C#.
On a button click I show a loading image, while the database query is being executed. Then I dynamically create an Excel file and send it to the client like this:
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("content-disposition", "attachment;filename=" + filename + ".xlsx");
HttpContext.Current.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.Unicode;
HttpContext.Current.Response.BinaryWrite(p.GetAsByteArray());
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
I get the dialog box, and the loading image stays there.
I've tried placing call to a javascript function (with ClientScript.RegisterStartupScript function) before the code above, it didn't work. As I understand all javascript code is run after all the code-behind has executed, but in this case it doesn't execute at all once the file is sent to the client.
I've also tried creating a separate thread, and removing the loading image there. I put the breakpoint to trace it, code in the thread does execute, but the image still stays there.
Does anyone have an idea how this can be handled? Thank you!
You can only send or transmit 1 mime type in one request/response cycle. (My knowledge in this area is debatable).
That said, you can design a hack around this. Use an iframe on the client to "download the file". You can point its src to an ashx file that does the same.
You need to wire the iframe's onload event, so your web page has someway of knowing that download is done; thats where you can execute your logic.
Solution Update:
Well, after digging around, I've discovered my answer is half-baked!
The issue is that iframes don't trigger their onload event after they download something. The onload event will trigger iff there the url pointed to by src actually navigates to a different page. This is by design I suppose. And I learn that today!
So what then is the work-around?!
Fortunately, you can transmit cookies to the client. On the client your web page has to keep polling for the presence of this cookie. So once your web page is able to detect the presence of the cookie, it means that the browser has completed with the download request. This has been discussed in great detail in the following post:
http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx
I'll just show you some code relating to the handler file (which simulates a download), and the client (which has an iframe doing the job). This should pretty much give you the gist:
Webform1.aspx:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApp.FileDownload.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>iFrame Download</title>
<script type="text/javascript" src="Scripts/jquery-2.1.0.min.js"></script>
<script type="text/javascript" src="Scripts/jquery.cookie.js"></script>
<script type="text/javascript">
function foo() {
console.log('foo');
//execute post-download logic here
}
$(function () {
$('input').click(function () {
//make sure we get rid of the
//cookie before download
$.removeCookie('downloaded');
var intrvl = setTimeout(function () { //this function polls for the cookie through which we track that the file has indeed been downloaded
console.log('timer');
var value = $.cookie('downloaded');
if (value == 'true') {
clearTimeout(intrvl);
foo();
}
}, 1000);
//this initiates the download
$('iframe').attr({
'src': 'download.ashx?id=' + $('#tbxRandomNumber').val()
});
});
});
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="tbxRandomNumber" runat="server"></asp:TextBox>
<input type="button" value="Download" />
<iframe src="about:blank" style="display:none"></iframe>
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Next Random Number" />
</div>
</form>
</body>
</html>
I've made used of jquery cookies plugin to help me with handling cookies.
download.ashx:
using System;
using System.Web;
namespace WebApp.FileDownload
{
/// <summary>
/// Summary description for download
/// </summary>
public class download : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.SetCookie(new HttpCookie("downloaded","true")); //setting cookie in the response
string id = context.Request.QueryString["id"] == null ? "NULL" : context.Request.QueryString["id"];
string str = string.Format("Content with id {0} was generated at {1}", id, DateTime.Now.ToLongTimeString());
context.Response.AddHeader("Content-Disposition", "attachment; filename=test.txt");
context.Response.AddHeader("Content-Length", str.Length.ToString());
context.Response.Write(str);
context.Response.End();
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
It looks like you have a couple of misunderstandings here. You only have one request, and one response from the server. Making new threads is something that only happens on the server, and won't create additional responses.
When you're sending the Excel file, you're using:
HttpContext.Current.Response.Clear();
By clearing the response, you're losing the JavaScript that you added previously. It will never get to the client.
If the processing is fairly trivial (always just a couple of seconds), I'd just set the loading animation to run for a couple of seconds and stop, by setting a timeout on the initial onclick event. It's not perfect, but it'll give the user some immediate feedback.
If the processing is going to take a long or very variable amount of time, then the animation is more important to get right. You can try loading your Excel file in a hidden <iframe>, and attaching an onload event to remove the loading animation.
You would need to create a separate page to handle generating the Excel file, rather than doing it in a server-side OnClick handler. However, I seem to remember that support for onload events on <iframe> can be spotty with older IE versions.
javascript run in the client when page is loading in the browser. You may have a hidden textbox, at the end of you event yo can put a value into that textbox:
txtHidden.Text = "Hola Mundo"
You have to check the value on page load:
<script type="text/javascript">
$(document).ready(function(){
if($("#txtHidden").length > 0 && $("#txtHidden").val() != '')
{
alert($("#txtHidden").val());
}
});
</script>
You can put this in a web user control.
Another solution:
<div class='button' id='btnGenerateDownload' onClick='GenerateDownload(this)'>
Click here <div id='loadingImage' class='loadingImage'></div>
</div>
JQuery:
function GenerateDownload(caller)
{
//add loading gif:
var $loagingGIF = $(caller).children('#loadingImage').eq(0);
$loagingGIF.addClass('loadingImage');
var fileGeneratorUrl = 'ghFileGenerator.ashx';
var downloadHandlerUrl = 'ghDownloadHandler.ashx';
$.post({data: "File1"}, function(response){
//remove gif
$loagingGIF.removeClass('loadingImage');
if(response != '') //file key
{
downloadHandlerUrl += '?key=' + response;
var $link = $("<a />").attr('href', downloadHandlerUrl).html('download');
$link.appendTo($(caller));
}
});
}
css:
.loadingImage{background: transparent url(images/loading.gif);}
.ashx:
string filekey = context.Current.Request.Form("key");

How to redirect browser during an Async Postback?

The scenario
I have an ASP.NET project which uses a custom authorization/authentication method (vs. using forms/windows authentication, etc.). On each secure page load, the following code is executed:
protected void Page_Load(Object sender, EventArgs e)
{
if (!IsLoggedIn)
{
HttpContext.Current.Response.Redirect("~/Login/", true);
}
}
This code basically checks whether the user is still logged in (no expired ASP.NET session, not logged out, etc.); if the user is not logged in, a Response.Redirect() happens, sending them to the login page.
This scenario works perfectly fine when the user requests a full page (through a link, or direct URL). The issue arises when using an async postback!
I have a button nested inside of an <asp:UpdatePanel>, which cause async postbacks when clicked. This button updates an <asp:Label />. For example:
<!-- the button -->
<asp:LinkButton ID="MyButton" CausesValidation="false" Text="My Button" OnClick="MyButton_Click" runat="server" />
<!-- the label -->
<asp:Label ID="MyLabel" runat="server" />
protected void MyButton_Click(Object sender, EventArgs e)
{
MyLabel.Text = DateTime.Now.ToString();
}
The issue
When an async postback is executing, and IsLoggedIn is false, the request is redirected to the login page. Now, the ASP.NET Framework expects a specific response (rather than an HTML page); thus, throwing the following error:
The question
How can I solve this issue? How can I force the whole page redirect to a specific address from the code-behind during an async postback?
While Kenneth's answer is the appropriate method of redirecting, I needed something a little more custom.
During an async postback, I needed to simulate Response.Redirect("path", true) - the true parameter (Indicates whether execution of the current page should terminate) is the important thing I needed to replicate! Simply using Response.End() after ScriptManager.RegisterClientScriptBlock() would not work because then there would be no response sent back to the browser.
By analyzing the server response to an async postback, I resorted to using the following hack (simulating a response by using Response.Write):
String jsRedirect = String.Format("window.location.pathname = '{0}';", VirtualPathUtility.ToAbsolute(url));
Response.Write(
// required parameters!
"0|asyncPostBackControlIDs|||" +
"0|postBackControlIDs|||" +
"0|updatePanelIDs|||" +
"0|childUpdatePanelIDs|||" +
"0|panelsToRefreshIDs|||" +
// your custom JavaScript
String.Format("{0}|scriptBlock|ScriptContentNoTags|{1}|", jsRedirect.Length, jsRedirect)
);
Response.Flush();
Response.End();
If you want to trigger from the code-behind you can do this:
if (ScriptManager.GetCurrent(Page).IsInAsyncPostBack) {
ScriptManager.RegisterStartupScript(updatepanelid, typeof(string), "redirect", "window.location = 'http://www.google.com';", true);
} else {
Response.Redirect("http://www.google.com");
}
Also note that if you use window.open(), a popup window will be opened (which may or may not be blocked). If you use window.location = "someurl"; it will just do a client-side redirect.
There is some terminology misunderstanding here. "Async postback" is not technically a postback at all; it is an xmlHttpRequest. If you want to do a redirect here, it must be done in javascript in an ajax callback function using window.open().
I'm not sure how you would implement this using asp.net AJAX. During the execution of your xmlHttpRequest code on the server, it is impossible to redirect a client (clarification - you may redirect, but the html you respond with will be (as in your case) incorrectly parsed by asp.NET's javascript ajax code.
With jQuery, this would be a pseudo-solution.
$.ajax({
success: function(data) {
if (data == 'redirect') {
window.open('yourRedirectUrl');
}
}
});

Executing Server-Side Methods by Clicking on a DIV

I'm working on an ASP.Net project, with C#.
Usually, when I need to put Buttons that will execute some methods, I will use the ASP Controller (Button) inside a runat="server" form.
But I feel that this really limits the capabilities of my website, because when I used to work with JSP, I used jquery to reach a servlet to execute some codes and return a responseText.
I did not check yet how this is done in ASP.Net, but my question concerns controllers and the famous runat="server".
When I add a runat="server" to any HTML Element, I'm supposed to be able to manipulate this HTML element in C# (Server-Side), and this actually works, I can change the ID, set the InnerText or InnerHtml, but the thing that I can't get, is why can't I execute a method by clicking on this element?
The "onclick" attribute is for JavaScript I think, and OnServerClick doesn't seem to work as well. Is it something wrong with my codes? or this doesn't work at all?
You will have to handle the click in the div using the Jquery and call
server-side methods through JQuery
There are several way to execute server side methods by clicking on a div or anything on your page. The first is mentioned __dopostback, second is handling the click in javascript or with jQuery and calling a function in a handler or a page method in a webservice or a page method in your page behind code.
Here is the handler version:
$("#btn1").click(function() {
$.ajax({
url: '/Handler1.ashx?param1=someparam',
success: function(msg, status, xhr) {
//doSomething, manipulate your html
},
error: function() {
//doSomething
}
});
});
I think the second version is better, because you can make a partial postback without any updatepanel, asyncronously. The drawback is, the server side code is separated from your page behind code.
Handler:
public class Handler1: IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/json";
var param1= context.Request.QueryString["param1"];
//param1 value will be "someparam"
// do something cool like filling a datatable serialize it with newtonsoft jsonconvert
var dt= new DataTable();
// fill it
context.Response.Write(JsonConvert.SerializeObject(dt));
}
}
If everything is cool, you get the response in the ajax call in the success section, and the parameter called "msg" will be your serialized JSON datatable.
You can execute a method from jquery click in server, using __doPostBack javascript function, see this threat for more details How to use __doPostBack()
Add this code in your jquery on div onclick and pass DIv id whcih call click
__doPostBack('__Page', DivID);
On page load add this code
if (IsPostBack)
{
//you will get id of div which called function
string eventargs = Request["__EVENTARGUMENT"];
if (!string.IsNullOrEmpty(eventargs))
{
//call your function
}
}
Make the div runat="server" and id="divName"
in page_Load event in cs:
if (IsPostBack)
{
if (Request["__EVENTARGUMENT"] != null && Request["__EVENTARGUMENT"] == "divClick")
{
//code to run in click event of divName
}
}
divName.Attributes.Add("ondivClick", ClientScript.GetPostBackEventReference(divName, "divClick"));
Hope it helps :)
if you are referring to divs with runat="server" attributes, they don't have onserverclick events, that's why it doesn't work

Executing JavaScript function in Code behind -- How to Get the Return Value in Code Behind

I have following JavaScript function, Which call Jquery JSON function and get the DateTime with respect to timezone. This works fine.
<script type="text/javascript">
function JSFunctionName() {
$(document).ready(function () {
var timezone = "US/Central";
$.getJSON("http://json-time.appspot.com/time.json?tz=" + timezone + "&callback=?",
function (data) {
if (data.hour < 12) {
//alert(data.hour + ':' + data.minute); // want to return this value data.hour + data.minute
//document.getElementById('<%=HiddenField1.ClientID %>').value = data.hour + ':' + data.minute;// this does not work
}
})
});
}
</script>
Now I am calling this Javascription function in Code behind on onclick of button
<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
Code behind
protected void Button1_Click(object sender, EventArgs e)
{
Page.ClientScript.RegisterStartupScript(this.GetType(),
"alert", "JSFunctionName();", true);
// here I need the DateTime value that is get from JSON
//Response.Write(HiddenField1.Value);
}
How can I return the value from Javascript to code behind immediate after call of Page.ClientScript.RegisterStartupScript
Please note I have try to set the value in HiddenField, but its not working. you can see in the comment.
Any idea or alternative solution will be appreciated.
Thanks
You can't do this without posting back to the server. The reason for this is that javascript executes on the client, and it will only execute after the page has left the server.
I assume this is a contrived example, but in this specific case, if you want to have the same information available on the client and server, you need to compute it on the server, and pass that out to the client.
If this isn't possible, you'll need to create a webservice, but that will have to handle the response asynchronously.
You can use ajax call to server from the javascript function or you may put another button on the form, hide it with style and cause click on this button after you set up calculated value to the hidden field in the JSFunctionName function.
Your problem is that the following line:
Page.ClientScript.RegisterStartupScript(this.GetType(),
"alert", "JSFunctionName();", true);
doesn't actually "execute" the Javascript funciton. It just adds the
JSFunctionName();
to the page in a script block, to be executed after your code has completed, and the page has loaded.
Rather than "calling the Javascript" from your button-click event, you could set the "OnClientClick" property of the button to "JSFunctionName()":
<asp:Button ID="Button1" runat="server" Text="Button"
onclick="Button1_Click" OnClientClick="JSFunctionName();" />
This will cause the JSFunctionName to fire before the postback happens. You can then set up your JSFunctionName() method to return true when it's done, which will then fire the postback.
You will then be able to access the value of HiddenField1 from the server-side click handler.

Categories

Resources