When the user clicks on a link to generate report I make an AJAX call which generates a pdf file in the background.Now the files are huge running upto 10mb or more.So it takes some time.In the mean time the user should be able to navigate other links as if nothing has happened.So I need to implement in such a way that the pdf generation process gets started & user doesn't have to wait for the process to finish.Is this possible?I am using AJAX Pro with c# with dot net framework 2.0
The problem here is that as soon as the AJAX activity begins the browser enters into a hung stage & the user has to wait although he clicks on a different link.
I would probably create a 'queue' or an 'inbox' for the user ...
start your pdf generation routine with a ThreadPool.QueueUserWorkItem (you would also need to modify your generation method to output to their inbox)
then on each http request check that inbox and notify the user of the item ... you can always poll the server on an interval or somthing
Sure, but once the user navigates to another page, the Javascript that is waiting for the Ajax response is no longer running, so that request is lost. You'd have to either find a way to keep that page open (using frames or exclusively Ajaxified navigiation), or find a way to store the response and notify the user of its completion on the next page view. For instance, storing a session variable that indicates that the operation is completed, or storing it in a database with (perhaps) an "unread" boolean value.
You can have asynchronous Ajax call with which you can do other tasks while response objects returns from the Ajax page.
Here is some example, testAjax.aspx is the Ajax page here :
http_request.onreadystatechange = function() { alertContents(http_request); };
http_request.open('GET', 'testAjax.aspx?', true);
http_request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
http_request.send(null);
function alertContents(http_request)
{//debugger;
if (http_request.readyState == 4)
{
if (http_request.status == 200)
{
var vResult;
vResult=http_request.responseText;
//Write your logic after successful Ajax call here.
}
else
{
alert('There was a problem with the request.');
}
}
}
Related
I'm creating a file processor for use in an intranet.
I described it in another question - ERR_EMPTY_RESPONSE when processing a large number of files in ASP.Net using C#
Now, as suggested on above question's answer, I'm trying to use threads to execute the file processing task.
But there is a problem. I need the newly created thread to write feedbacks to a component in page (asp:panel, or div, or whatever). Those feedbacks would be results from several database operations.
The application reads those txts, interprets each line of it, and insert data in database. Each line inserted in database must return a feedback, like "registry 'regname' inserted successfully", or "i got problems inserting registry 'regname' in file 'filename', skipping to next registry".
I did test with something very simple:
protected void DoImport()
{
try
{
MainBody.Style.Add(HtmlTextWriterStyle.Cursor, "wait");
int x = 0;
while (x < 10000)
{
ReturnMessage(String.Format("Number {0}<hr />", x), ref pnlConfirms);
x++;
}
}
catch (Exception ex)
{
ReturnMessage(String.Format("<font style='color:red;'><b>FATAL ERROR DURING DATA IMPORT</b></font><br /><br /><font style='color:black;'><b>Message:</b></font><font style='color:orange;'> {0}</font><br />{1}", ex.Message, ex.StackTrace), ref pnlErrors);
}
finally
{
MainBody.Style.Add(HtmlTextWriterStyle.Cursor, "default");
}
}
This function is called from Page_Load, and fills an asp:panel called "pnlConfirms" with a row of numbers, but all at once, on load.
I changed it to:
protected void DoImport()
{
try
{
MainBody.Style.Add(HtmlTextWriterStyle.Cursor, "wait");
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
}
catch (Exception ex)
{
ReturnMessage(String.Format("<font style='color:red;'><b>FATAL ERROR DURING DATA IMPORT</b></font><br /><br /><font style='color:black;'><b>Message:</b></font><font style='color:orange;'> {0}</font><br />{1}", ex.Message, ex.StackTrace), ref pnlErrors);
}
finally
{
MainBody.Style.Add(HtmlTextWriterStyle.Cursor, "default");
}
}
private void DoWork(Object stateInfo)
{
int x = 0;
while (x < 10000)
{
ReturnMessage(String.Format("Number {0}<hr />", x), ref pnlConfirms);
x++;
}
}
And both uses this function:
public void ReturnMessage(string message, ref Panel panel, bool reset = false)
{
if (reset)
{
panel.Controls.Clear();
}
Label msg = new Label();
msg.Attributes.Add("width", "100%");
msg.Text = message;
panel.Controls.Add(msg);
}
I need ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork)); to fill those asp:panels with feedbacks - like insertion errors and warnings.
My code already has those feedbacks under try...catch statements, but they're not getting output to any asp:panel from threadpool (it works when invoked directly from DoImport() function, like in the first example I posted).
I'm doing something very wrong, but I can't find out what (and I'm researching this for almost 2 weeks). Please, help!
In ASP.NET, when a browser requests a page, that page is rendered and sent to the browser as soon as its processing finishes, so the browser will show the page as it's finally rendered.
According to your code you're trying to render a page, show a wait cursor, and expect it's shown on the browser and then, the cursor is changed by a default cursor. As I explained, independently from using or not additional threads, the page won't be sent to the browser until it's completely rendered. So you'l never see the wait cursor on the client side.
The easiest wait to get what you're trying to do is to use web services (traditional .asmx or WCF) and AJAX (jquery os ASP.NET AJAX).
1) create a web service that does the processing
2) create a page which is sent to the browser, and, using javascript (jQuery or ASP.NET AJAX) make a call to the web service, and show something to let the user know that the request is being processed. (a wait cursor, or even better an animated gif)
3) when the process finishes, your javascript will get the responde from the web service, and you can update the page to let the user know the process has finished.
if you don't have experience on javascript, you can make most of this task using:
ScriptManager which can be used to create a javascript web service proxy for your client side (other interesting article) and is required for the rest of the controls
some javascript (or jquery) which can be use to update the "process running/ process finished hints" on the client side. I.e. when the call to the web service ends, you can use javascript to update the page using DOM, or load a new page or the same page with an special parameter to show the result of the process
In this way you can do what you want:
1) show a page in a state that shows the process is running
2) show the same, or other page, in a state that shows the end of the process
The trick is comunicating the browser with the server, and this can only be done using some of the available ajax techniques.
Another typical technique is using jQuery.ajax, like explained in encosia.com
According to the OP message, the process of all the files would be so slow that it would tiemout the web service call. If this is the case, you can use this solution:
1) Create a web service that process one (or a batch) of the pending files, and returns at least the number of pending files when it finishes the processing of the current file (or batch).
2) from the client side (javascript), call the web service. When it finishes, update the page showing the number of pending files, and, if this number is greater than zero, call the web service again.
3) when the call to the web service returns 0 pending files, you can update the page to show the work is finished, and don't call it any more.
If you process all the files at once, there will be no feedback on the client side, and there will also be a timeout. Besides, IIS can decide to stop the working thread which is making the work. IIS does this for several reasons.
A more reliable solution, but harder to implement, is:
1) implement a Windows Service, that does the file processing
2) implement a web service that returns the number of pending files (you can communicate the Windows Service and Web App indirectly using the file system, a database table or something like that)
3) use a timer (ajax timer, or javascript setInterval) from your web page to poll the server every N seconds using the web service, until the number of pending files is 0.
An even harder way to do this is hosting a WCF service in your Windows Service, instead of the indirect communication between your web app and windows service. This case is much more complicated because you need to use threads to do the work, and attend the calls to the wcf service. If you can use indirect communitacion it's much easier to implemente. The dtabse table is a simple and effective solution: your working process updates a row a table whenever it process a file, and the web service reads the progress state from this table.
There are many different soultions for a not so simple problem.
You are starting new thread (or more precise running your code on one of free threads in thread pool)and not waiting for results in main thread. Something like Thread.Join (if you would use manual thread creation) or other synchronization mechanism as events need to be used if you want to go this route.
The question you've linked to suggests using asynchronous pages which you are not doing. You would start processing request, kick off the task and release the thread, when the task is finished you complete request.
Side note: consider simply doing all conversion on main thread that handles request. Unless you expect slow I/O to complete the task moving CPU work from one thread to another may not produce significant gains. Please measure performance of your current solution and confirm that it does not meet performance goals you have set up for your application. (this does not apply if you doing it for fun/educational purposes).
I'm creating a page that get uploaded text files and builds them into multiple PDFs. They are just exports from Excel. Each row in the file corresponds to a new PDF that needs to be created.
Anyway, once the files are uploaded I want to begin processing them, but I don't want the user to have to stay on the page, or even still have their session open. For example they could close the browser and come back 10 minutes later, log in, and the progress information will say like 112/200 files processed or something. It will be a lot quicker than that though.
So two questions really, how can I pass this processing job to something (Handler?Thread?) that will continue to run when the page is closed, and will return as soon as the job has started (so the browser isn't stopped)? Secondly, where can I store this information so that when the user comes back to the page, they can see the current progress.
I realise that I can't use sessions, and since it will be processing about a file a second I don't really want to update a DB every second. Is there some way I can do this? Is it possible?
I solved this by using the link provided by astander above. I simply create an object in the HttpContext.Application to store progress variables, and then Set the method which does my processing inside a new Thread.
// Create the new progress object
BatchProgress bs = new BatchProgress(0);
if(Application["BatchProgress"] != null)
{
// Should never happen
Application["BatchProgress"] = bs;
}
else
{
Application.Add("BatchProgress","bs");
}
//Set up new thread, run batch is the method that does all the processing.
ThreadStart ts = new ThreadStart(RunBatch);
Thread t = new Thread(ts);
t.Start();
It then returns after the thread starts and I can use jQuery to get the Application["BatchProgress"] object at regular intervals. At the end of my thread the BatchProgress object has its status set to "Complete", then when jQuery queries it, it sees the complete status and removes the progress object from the application.
There are various ways to handle session timeouts, like "meta refreshes" javascript on load functions etc.
I would like something neat like: 5 minutes before timeout, warn the user...
I am also contemplating keeping the session open for as long as the browser is open(still need to figure out how to do it though... probably some iframe with refreshing).
How do you handle session timeouts, and what direction do you think i should go in?
The best approach to handle sessions timeouts.
I say that there is 2 basic cases.
One is when the users enter little or no data, and just read reports, or do small thinks with his mouse. In this case there is not easy way to inform him that the session is going to expire. If you going to check the time left for the session calling the code behind, then automatically you update the session. Then if you have a timer to count down the session, then maybe the user have open a new tab of your web and the session is going to expired but not the time you have note with javascript and the user receive wrong message.
So for me, when the user enter little or no data, just let the session expired, if he lose one click, it will do it again later.
Second is when the user need to enter many data, that some time can take time, a long text for example, to write it and fix it. In this case I use the below technique and I am not let the session go out.
How to keep the session open as long as the browser.
Here is a very nice and simple technique, I use an image that I make an reload of it before the session is timeout using JavaScript.
<img id="keepAliveIMG" width="1" height="1" src="/img/ui/spacer.gif?" />
<script language="javascript" type="text/javascript">
var myImg = document.getElementById("keepAliveIMG");
if (myImg){
window.setInterval(function(){
myImg.src = myImg.src.replace(/\?.*$/, '?' + Math.random());
}, 6000);
}
</script>
In a third case, you can do this. We care if the session is expired only on post back. When the user have enter some data and on the post back the application is redirect him on the login page and the post lost.
In this third case you can capture the post data and saved them until the user re-login. You capture the post data on global.asax on the
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
This is the function that called before the redirect to the login page, and there you see if you have post data and the use required to login, you save that post data, ether to a new redirect page, ether to the server (maybe on session, maybe on your temporary database).
Now after the user is login again, you redirect him again to the last page with the saved post data, and the user is continue as it is.
The only trick here is to make a middle page, that render the form with the last posted data and an automatically redirect javascript call.
The only thing I can think of is to generate some script on the page that creates a client timer, so that when the page is received and rendered, it can show an alert X-minutes later (that is 5mins before expire).
If you'd rather have the session just keep itself alive, you can do this with a generic handler (ASHX) that you periodically call via AJAX. This will help refresh the session and it should stay alive for as long as the AJAX calls continue.
Example "keepalive.ASHX":
<%# WebHandler Language="C#" Class="keepalive" %>
using System;
public class keepalive : System.Web.IHttpHandler
{
public void ProcessRequest (System.Web.HttpContext context)
{
context.Response.ContentType = "text/json";
var thisUser = System.Web.Security.Membership.GetUser();
if (thisUser != null)
context.Response.Write("[{\"User\": \"" + thisUser.UserName + "\"}]");
}
public bool IsReusable
{
get { return false; }
}
}
And here's the script on the page to call it (with jQuery for simplicity):
<script type='text/javascript'>
function keepAliveInterval()
{
$.ajax(
{
url: "keepalive.ashx",
context: document.body,
error: function () {
alert("AJAX keepalive.ashx error :(");
}
});
}
$(document).ready(function () {
window.setInterval('keepAliveInterval()', 60000);
});
</script>
Use some jquery that keys off of your session timeout variable in the web.config. You can use this Jquery delay trick that when a specific time occurs (x number of minutes after load of the page), it pops up a div stating session timeout in x minutes. Nice, clean and pretty simple.
Regarding session timeout, Codesleuth's ajax call would be perfect.
When I try to redirect to another page through Response.Redirect(URL) am getting the following error:- System.Web.HttpException: Cannot redirect after HTTP headers have been sent.
I wrote one Response.Write("Sometext"); and Response.Flush() before calling redirect Method.
In this case how do we use Response.Redirect(URL)?
I'm executing a Stored procedure through Asynch call. The SP will take almost 3 min to execute. By that time I'll get load balancer timeout error from Server because this application is running in Cloud computer. For avoiding load balancer timeout I'm writing some text to browser (response.write() and Flush() ) .
You need to ensure that you do not write/flush anything before trying to send a HTTP header.
After sending headers there is no proper way to do a redirect as the only things you can do are outputting JavaScript to do the redirect (bad) or sending a 'meta refresh/location' tag which will most likely not be at the correct position (inside HEAD) and thus result in invalid html.
I had the same error and same approach. You might want to try using a javascript instead of directly calling Response.Redirect.
Response.Write("<script type='text/javascript'>");
Response.Write("window.location = '" + url + "'</script>");
Response.Flush();
Worked fine with me however I still need to check it on different browsers.
if (!Response.IsRequestBeingRedirected)
Response.Redirect("~/RMSPlusErrorPage.aspx?ErrorID=" + 100, false);
You can't use Response.Redirect as you've gone past headers and written out "Sometext". You have to check (redirect condition) before you start writing out data to the client or make a META redirect.
If you want one of those pages that shows text and redirects after 5s META is your option.
You won't get this error, if you redirect before the rendering of your page begins (for example when you redirect from the Load or PreRender events of the page).
I see now in your comments, that you would like to redirect after a long-running stored procedure completes. You might have to use a different approach in this case.
You could put for example an AJAX UpdatePanel with a Timer on your page, and the Timer could check in every few seconds whether the stored procedure has completed, and then do the redirection.
This approach also has the advantage, that you can put some "in progress" message on the page, while the procedure is running, so your user would know, that things are still happening.
Try to do the following:
catch (System.Threading.ThreadAbortException)
{
// To Handle HTTP Exception "Cannot redirect after HTTP headers have been sent".
}
catch (Exception e)
{//Here you can put your context.response.redirect("page.aspx");}
In my case, cause of the problem is that loading data in the scroll gridview is taking a long time. And before gridview data is not loaded completely, but I press the redirect button. I get this error.
You can lessen your data get
or
before loading completion prevent to press redirect button
So I'm trying to load some returned html from an .aspx page but a click event needs to fire before it doing some stuff that the AJAX request depends upon. More specifically, I'm doing this. When a user types in a text field this function is run...
function KeyPress() {
$("#" + HiddenButtonId).click();
$("#test").load("TempJumpToAJAX.aspx");
}
then $("#" + HiddenButtonId).click(); does sets some session data in a code behind file. Specifically...
Session["SearchText"] = Search.Text;
then, $("#test").load("TempJumpToAJAX.aspx"); calls that .aspx page that returns a newly built select box...
Response.Expires = -1;
StringBuilder builder = new StringBuilder();
builder.Append("<select id=\"testListId\" name=\"testList\" size=\"4\" style=\"width:200px;\">");
builder.Append("<option>");
builder.Append(Session["SearchText"]);
builder.Append("</option>");
builder.Append("</select>");
Response.ContentType = "text/HTML";
Response.Write(builder.ToString());
Response.End();
The problem is, the order seems to be screwed up it tries to append the Session["SearchText"] first, then runs the code that the click event runs. So it functions more like this...
function KeyPress() {
$("#test").load("TempJumpToAJAX.aspx");
$("#" + HiddenButtonId).click();
}
Where the commands are reversed. So what in effect happens is the session variable has an empty string as opposed to what the user typed in the text box. What's odd to me is that it doesn't seem like it should have an empty string, it seems like it should have nothing since the session variable has not been initialized to anything at this point. I really have no idea what's happening. Chalk it up to inexperience I guess. Any ideas?
You are mixing technologies here. The hiddenButtonID click event is trying to do a full postback for the page, whereas the AJAX call will not do a postback. There is no reason to do a post back and then follow it up with an AJAX call. The point of AJAX is to eliminate the need to postback the page and instead just reload a small amount of the HTML on the page using a small callback to the server. Instead of accessing the Search Textbox text in the HiddenButtonID click event handler you should be passing that data to the Server in the AJAX call parameters.
The following client side script should do this.
function KeyPress() {
$("#test").load("TempJumpToAJAX.aspx", {searchText: $("#").val()});
}
In this code you are getting the ID of the search textbox and then using jQuery to retrieve the value of that text box. This will get passed to the TempJumpToAJAX.aspx page as POST variable called 'searchText'. You should be able access this data by accessing the Request['searchText'] variable in the 'TempJumpToAJAX.aspx' page.
Another smart extension to what you are doing, is not to do the ajax call as soon as a key is pressed. If the user types in a long word quickly, you'll get multiple ajax http requests triggered faster than they can return and update your UI.
This adds to your server load, and might make the client's UI sluggish.
Instead, onkeypress, store all the appropriate details and then call
var timeout = setTimeout('some_method()',100);
Which says call the some_method() in 100 milliseconds. The first thing you should do within your keypress method is cancel/clear the Timeout call with clearTimeout.
http://www.w3schools.com/js/js_timing.asp
The some_method() should also clear any timeout, then make the http ajax request.
The net effect here is that your ajax request is delayed slightly, but never happens if the user presses another key. Or put another way, don't try and fire an ajx request until the user has stopped/paused typing. 100 milliseconds might be too high or too low, but you hopefully get the idea!
You should also take care to deal with a "slow server" situation. Consider this:
User types "hello", pauses for 1 second, you fire an ajax request with "hello" as the parameter
User continues to type " world", so the textfield now contains "hello world", and stops typing.
You fire a second ajax request with "hello world" as the parameter.
The second request (for "hello world") returns first, you update your UI
The first request (for "hello") returns, you update your UI again.
Ooops! Have the server include the original query string in the (json) data it returns to the client. When the client gets the ajax data back, check that the result/data is for the query ("hello" / "hello world") that matches what is currently in the text field.
There is a JQuery plugin that encapsulates what the previous answer is talking about. Its called TypeWatch. It allows a function to fire once the user has stopped typing in a text field for a specified number of milliseconds. I've used it before and it works quite well.
Awesome! Thanks to everyone all your answers were quite insightful. The real issue I learned was that I did not know how to send variables with that ajax request. This here.
$("#test").load("TempJumpToAJAX.aspx", {searchText: $("#").val()});
Also mintywalker, thanks for the insight. It was all good.