Using a background worker in ASP.NET with AJAX - asp.net

I have the need to perform a background task that has a progress bar that shows percentage done and a cancel button. Task specifics aside, for now, I just want to get an example working, so I just have the three main event handlers (DoWork, ProgressChanged, and RunWorkerCompleted) and a loop that just increments a counter and sleeps for 50ms in DoWork. However, it doesn't update except for once at the end.
In Windows Forms I use a Background worker and it functions correctly without any issues. I'd like to just use this same code. However, I have been seeing stuff that says ASP.NET must use AJAX to get the same functionality. So my questions are:
1) Do I really need AJAX to use the background worker?
2) If yes, I do need AJAX, what is the easiest, most simplest way a person that doesn't know a darn thing about AJAX could do to get the Background worker up and running on an ASP.NET webpage?
3) If no, I don't need AJAX, can anyone point me to a working sample that doesn't use it? I am interested even if it uses some other threading method than background workers.
Sorry for the multi-part question! If you can answer one or the other, it would be much appreciated. I don't really mind which method I end up using as long as it works.
Code for reference from the .cs page:
protected void bwProcess_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lblProgress.Text = "Task Complete: " + e.Result;
}
protected void bwProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
lblProgress.Text = e.ProgressPercentage.ToString();
}
protected void bwProcess_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
if (bwProcess.CancellationPending)
{
lblProgress.Text = "Task Cancelled.";
e.Cancel = true;
return;
}
bwProcess.ReportProgress(i);
Thread.Sleep(50);
}
e.Result = "100%";
}
protected void BWClick(object sender, EventArgs e)
{
lblProgress.Text = "Firing Process...";
bwProcess = new BackgroundWorker();
bwProcess.WorkerReportsProgress = true;
bwProcess.WorkerSupportsCancellation = true;
bwProcess.DoWork += new DoWorkEventHandler(bwProcess_DoWork);
bwProcess.ProgressChanged += new ProgressChangedEventHandler(bwProcess_ProgressChanged);
bwProcess.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwProcess_RunWorkerCompleted);
if (bwProcess != null)
{
bwProcess.RunWorkerAsync("StartAsynchronousProcess");
}
}
Other notes: I have entered Async="True" and EnableSessionState="ReadOnly" into the #Page.
Thanks in advance!

Web programming offer's many challenges which are easy to take for granted when desktop programming. Moving from one to the other can require a lot of changes. Spawning long-running threads is one of those things that require more care to avoid pitfalls. The application pool does not know about threads that you spawn so when it recycles it will kill those threads causing unexpected behavior in your application. See this post for more about that, Can I use threads to carry out long-running jobs on IIS?
This means you need to use a more persistent means to keep track of progress. A database would probably be best, but even a file would persist after the pool is recycled.
AJAX would be perfect for this because it will allow you to pull the progress from the database asynchronously in the background and update the webpage. Here is a breif example of how you might achieve this. All the percentage calculations are done on server side.
function getProgress() {
$.ajax({
url: '/progress', // send HTTP-GET to /progress page
dataType: 'json', // return format will be JSON
success: function(result) { // function to run once the result is pulled
$("#p1").html('Percentage: %' + result);
if (result == "100")
clearInterval(window.progressID);
}
});
}
function startGetProgress() {
window.progressID = setInterval(getProgress, 10000); // update progress every 10 seconds
}
The above example uses JQuery ajax method, http://api.jquery.com/jQuery.ajax/ so you will need to reference the JQuery library.
Whether you are using webforms or mvc you can add a web api controller to handle the ajax request. Let me know if you need me to clarify anything.

Related

How to Handle Two Jobs in MVC at same time

Hi my current project is calendar schedule. If i move time slot box in calendar, then called DayPilotCalendarWeek_EventMove and Time slate is moved successfully without Email functionality. But If i called Email Process it take some mints for mail. How to handle this Jobs???. I refer threading concept But I cannot Understand.Click Here for Screen shot
Requirement is
Job 1 : DayPilotCalendarWeek.Update(); // must done before sending Email.
After Update calender, Email functionality must work.
MY Code is :
Same Function two action
1.DayPilotCalendarWeek.Update();
2.SendEmail();
protected void DayPilotCalendarWeek_EventMove(object sender, DayPilot.Web.Ui.Events.EventMoveEventArgs e)
{
try
{
CommonCls com = new CommonCls();
//**Job 1:**
DayPilotCalendarWeek.DataSource = Moving(e.Id, e.OldStart.ToString(), e.NewStart.ToString());
DayPilotCalendarWeek.DataBind();
DayPilotCalendarWeek.Update();
//**Job 2**
SendEmail();
}
catch (Exception ex)
{
logger.Error(ex.ToString());
}
}
I dont know ASP.NET very well but i found this:
Response.AddHeader("REFRESH","10;URL=test.aspx");.
This should refresh the code after 10 secons. Maybe that will work.

Response.Redirect(Request.Url.AbsoluteUri) doesn't update page data

I have Page_load method like this:
private void Page_Load(object sender, System.EventArgs e)
{
if (!IsPostBack)
{
// Load Content
LoadContent();
return;
}
// some code here
}
And I use Response.Redirect(Request.Url.AbsoluteUri) at the end of methods to prevent re-post actions cased by page refrashing. When I run my app from source code that works well (debug or run mode), but when I publish the app (even on the same machine) page data (which loads on LoadContent) is not updated on updated page (but re-post actions is prevented).
Please, could anyone tell me why it happens?
ADDED:
There is LoadContent() method:
// firstly I get an supervisedGroups list TIBCO iProcess Engine via .NET vendor library, and then:
if (supervisedGroups != null)
{
rptSupervisedGroups.DataSource = supervisedGroups; // rpt.. is Repeater
rptSupervisedGroups.DataBind();
}
ADDED:
Method where Response.Redirect are used:
private void removeFromGroup(string strGroupName)
{
using(SqlConnection con = DBHelper.GetNewConnection())
{
con.Open();
// here comes query to DB
}
// Reload Page
Response.Redirect(Request.Url.AbsoluteUri);
}
You have two ways to solve this cache issue.
One is to give instructions to the browser to not cache this page, for example on page load you run:
Response.Cache.SetExpires(DateTime.UtcNow.AddYears(-4));
Response.Cache.SetValidUntilExpires(false);
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
Response.Cache.SetNoStore();
But a better solution is to add a random number at the end of the url when you make the redirect, or even better add the new id from the data that you have insert, eg:
Response.Redirect("samepage.aspx?newid=" + NewId);
that way the page will forced to be readed again, and you still have the cache functionality.
Most likely your page is cached. Try to hit shift-f5 to check the content. You can make all redirect urls unique to prevent the browser showing a cached page. Or disable caching for the specific page.

Using Background Worker and process.start not able to retrieve progress status

Good evening,
I just started playing around with C# and I tried creating a GUI for a program that runs in command line. I have been able to get it running, but now I am stuck trying to implement a progress bar to it.
I have read other post but I am unable to find the exact issue or to understand how to apply the solution to my issue.
Here is my code (apologize if this is very messy):
private void MethodToProcess(Object sender, DoWorkEventArgs args)
{
// Set all the strings for passthrough
String USMTPath_Work = USMTPath + USMTArch;
String USMTPath_full = USMTPath_Work + #"\Scanstate.exe";
String USMTFlags_Capture = #"/c /v:13 /o /l:scanstate.log /localonly /efs:copyraw";
String Argument_full = SavePath + XML1 + XML2 + USMTFlags_Capture;
// Test that USMT path is correct
if (USMTPath == null)
{
MessageBox.Show("Error: There is no USMT Path defined.");
return;
}
// Test that Windows folder is correct when offline
/* if (Windows_Path == null)
{
MessageBox.Show("Error: There is no Windows Path to capture.");
return;
} */
// Runs the capture
System.Diagnostics.Process Scanstate = new System.Diagnostics.Process();
Scanstate.StartInfo.FileName = USMTPath_full;
Scanstate.StartInfo.Arguments = Argument_full;
Scanstate.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
Scanstate.StartInfo.WorkingDirectory = USMTPath_Work;
//Scanstate.StartInfo.UseShellExecute = false;
Scanstate.StartInfo.CreateNoWindow = true;
//Scanstate.StartInfo.RedirectStandardOutput = true;
Scanstate.Start();
Scanstate.WaitForExit();
String Str_ExitCode = Scanstate.ExitCode.ToString();
if (Scanstate.ExitCode == 1)
MessageBox.Show("Error: Data has not been captured. Please check the log files for details.");
if (Scanstate.ExitCode == 0)
MessageBox.Show("Success: Data has been captured. For more information, check log files.");
else
{
MessageBox.Show("Error: Unknown error has occurred. Please check the log files for details.");
MessageBox.Show("Error Code: " + Str_ExitCode);
}
Scanstate.Close();
}
Basically, I am trying to run the process scanstate.exe. Now, I am trying to run backgroundworker in order to be able to retrieve progress and pass it to the progressbar.
private void btnCapture_Click(object sender, EventArgs e)
{
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Step = 1;
BackgroundWorker CaptureBG = new BackgroundWorker();
CaptureBG.WorkerReportsProgress = true;
CaptureBG.DoWork += new DoWorkEventHandler(MethodToProcess);
CaptureBG.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(CaptureBG_RunWorkerCompleted);
CaptureBG.ProgressChanged += new ProgressChangedEventHandler(CaptureBG_ProgressChanged);
CaptureBG.RunWorkerAsync();
}
and
private void CaptureBG_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs args)
{
progressBar1.Value = 100;
}
private void CaptureBG_ProgressChanged(object sender, ProgressChangedEventArgs args)
{
progressBar1.Value++;
}
However I am either missunderstanding the use or I am missing something, since the process runs, but I don't get any progress on the progressbar. It only fills once the process finish.
What am I doing wrong? In general, how would a process report progress if I don't know exactly how long is going to take?
Thanks in advance
The BackgroundWorker is responsible for updating the progress as it gets further complete with its task.
There is no interaction between your process that you launch and your code that would provide progress of that process back to your code.
In order for this to work, two things have to happen:
You need to define a mechanism for your process to report progress to the BackgroundWorker.
The BackgroundWorker must update its own progress by calling the ReportProgress method so that the ProgressChanged event is fired.
The first step is the tricky one and depends on how scanstate.exe works. Does it do anything to give an indication of progress, such as write to the console? If so, you can redirect the console output and parse that output to determine or at least estimate progress.
UPDATE
Scanstate.exe provides the ability to write progress to a log, e.g.:
scanstate /i:migapp.xml /i:miguser.xml \\fileserver\migration\mystore /progress:prog.log /l:scanlog.log
You could use a FileWatcher in your BackgroundWorker to look for changes to the progress log and update progress accordingly.

Can't redirect to another page using ASP.NET and WF 4

I am using WF 4 with ASP.NET and as part of the workflow the system may need to redirect to other pages for the user to input additional information under certain circumstances. Once they have entered that information, the system needs to resume the workflow where it left off.
I have this code so far in the initial page that kicks off the process and an activity in the workflow that sets a bookmark.
static InstanceStore instanceStore;
static AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
static Guid id;
protected void Page_Load(object sender, EventArgs e)
{
SetupInstanceStore();
}
protected void btnStartWorkflow_Click(object sender, EventArgs e)
{
app = Session["applicant"];
Dictionary<string, object> workflowInputs = new Dictionary<string, object>();
workflowInputs.Add("Applicant", app.Applicant);
WorkflowApplication workflowApplication = new WorkflowApplication(new IdentityCheckActivites.IdentityCheckWorkflow(), workflowInputs);
workflowApplication.InstanceStore = instanceStore;
//returning IdleAction.Unload instructs the WorkflowApplication to persist application state and remove it from memory
workflowApplication.PersistableIdle = (a) =>
{
return PersistableIdleAction.Persist;
};
workflowApplication.Unloaded = (a) =>
{
instanceUnloaded.Set();
};
workflowApplication.Completed = (a) =>
{
instanceUnloaded.Set();
};
workflowApplication.Persist();
id = workflowApplication.Id;
workflowApplication.Run();
Session["id"] = id;
workflowApplication.Idle = (a) =>
{
instanceUnloaded.Set();
};
instanceUnloaded.WaitOne();
var bookmarks = workflowApplication.GetBookmarks();
if (bookmarks != null && bookmarks[0].OwnerDisplayName == "CC")
{
workflowApplication.Unload();
Context.Response.Redirect("SecondPage.aspx");
}
Context.Response.Redirect("FinalPage.aspx");
}
private static void SetupInstanceStore()
{
instanceStore = new SqlWorkflowInstanceStore(#"Data Source=xxx;Initial Catalog=SampleInstanceStore;User Id=xxx;Password=xxx;Asynchronous Processing=True");
InstanceHandle handle = instanceStore.CreateInstanceHandle();
InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));
handle.Free();
instanceStore.DefaultInstanceOwner = view.InstanceOwner;
}
This seems to work very well in that it persists the workflow to the database and if the bookmark is set I want to redirect to a second page for the user to enter more data.
This is the part of the code that I am having problems with: -
var bookmarks = workflowApplication.GetBookmarks();
if (bookmarks != null && bookmarks[0].OwnerDisplayName == "CC")
{
workflowApplication.Unload();
Context.Response.Redirect("SecondPage.aspx");
}
Context.Response.Redirect("FinalPage.aspx");
If there's a bookmark set, I redirect to an intermediary page, if not and no user intervention was necessary, the page will just redirect to the final page.
This works if the bookmark is set, but if not the workflowApplication.GetBookmarks() statement throws an exception telling me that the workflow has completed.
I can't seem to find a way to detect at this stage which state the workflow is in so that I can redirect to the relevant page.
Maybe I have the wrong idea in general, as much as I search though, I cannot seem to find a lot of guidance on this subject.
Any ideas?
Thanks,
Jim.
I don't think there is a way to directly determine if the workflow is completed from WorkflowApplication (except for catching and inspecting the exception that is thrown).
But you could set a flag in side your Completed delegate which is executed only if the there is no bookmark set and the workflow is completed. You could then check this flag before calling GetBookmarks().
Not sure if I understand exactly, but it seems that your page controller is looking at the state of the workflow to understand what page to redirect to? The problem is that the state may be non-existent if the WF instance has ended?
If the above is correct then perhaps the approach is wrong. A more appropriate approach might be to have a WCF WF service on AppFabric (correlated by session id) handle the website request directly. (If a user in a particular session visits the site, then the WF determines what page to render, and if the user hits a certain button, then send a WCF WF message using net pipe binding)
instead of
workflow.idle
you need
wfApp.PersistableIdle
and don't forget
instanceUnloaded.Set();

javascript timer

I am developing an online exam application using asp.net. In the start exam page I have created a javascript countdown timer.
How can I move to the next page automatically after the timer reaches 00?
Here is my code:
long timerStartValue = 1000 ;
private int TimerInterval
{
get
{
int o =(int) ViewState["timerInterval"];
if(o==0)
{
return (o);
}
return 50 ;
}
set
{
ViewState["timerInterval"] = value;
}
}
protected void Page_PreInit(object sender,EventArgs e)
{
string timerVal = Request.Form["timerData"];
if(! String.IsNullOrEmpty(timerVal))
{
timerVal = timerVal.Replace(",", String.Empty) ;
this.timerStartValue = long.Parse(timerVal);
}
}
protected void Page_Load(object sender, EventArgs e)
{
if(! IsPostBack)
{
this.timerStartValue = 10000; //3599000;//14400000;
this.TimerInterval = 500;
}
}
protected void Button1_Click(object sender, EventArgs e)
{
this.timerStartValue = 3599000;
}
protected void Page_PreRender(object sender, EventArgs e)
{
System.Text.StringBuilder bldr=new System.Text.StringBuilder();
bldr.AppendFormat("var Timer = new myTimer({0},{1},'{2}','timerData');", this.timerStartValue, this.TimerInterval, this.lblTimerCount.ClientID);
bldr.Append("Timer.go()");
ClientScript.RegisterStartupScript(this.GetType(), "TimerScript", bldr.ToString(), true);
ClientScript.RegisterHiddenField("timerData", timerStartValue.ToString());
}
Thanks in advance,
sangita
It sounds like when you click the "Next" button, you are loading an entirely new page. This of course changes all the content and resets all the javascript. You can't maintain state across pages without a bit of work.
The solution to this could be to save the timer state when the next button is pressed, and pass it to the next stage. You could do this by saving the timer state to a hidden form input and submitting it along with the Next button.
The other option would be to load your questions via AJAX. Instead of moving to a new page every time the next button is clicked, you could simply replace the question portion of the page with a new question, and leave the timer intact. This is probably the solution I would use.
Are u reloading the entire page when clicking on the next button ? That may leads to realod the java script file also.So the variable values will reset.May be you can think about showing the questions /answers via Ajax.You need not reload the entire page when showing the next question.the part when you show the quiz will only be updated.so you can maintain the global variables in your java script too. Check the below link to know about partial page updating using jQuery.
http://www.west-wind.com/presentations/jquery/jquerypart2.aspx
Hope this helps.
You can put the timer in an iframe if you can't get rid of the postback.
You need a way to persist information between pages, and there's really only one possibility: To make it part of the next page request.
Now, this could be subdivided into 2 categories:
1) As part of the url: http://www.example.com/page?timer=123;
2) As part of the headers;
And number 2 opens new possibilities:
a) As part of POST data;
b) As a client-side cookie only;
c) As a cookie tied to information on the server;
Number 1, 2a and 2b can be manipulated by the user. So what you can do is store some value in a cookie, a hash for example or a database row ID, that you'll use to fetch information on the server.
tl;dr? Use a asp "Session object". It lets you keep things on the server-side and users will have no idea what they are.

Resources