Calling JavaScript function in Xamarin.Forms IOS WebViewRenderer - javascript

I am using Xamarin.Forms to build a cross platform IOS Android and windows phone app. One of my views is a web view which calls a url pointing to a page that has a javascript function in it. I need to call this function from within the mobile app, and pass it a string value.
So far all good, I achieve this in WindowsPhone by using a WebViewRenderer with a custom view. In the OnElementPropertyChanged handler in the renderer I can get get access to my view and the properties I need like so:
protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(e.PropertyName == SessionWebView.SessionIdProperty.PropertyName)
{
view = Element as SessionWebView;
Control.LoadCompleted += Control_LoadCompleted;
if (view != null)
{
_currentSessionId = view.SessionId;
}
}
}
Above I attach the the load complete handler of the Control which is the actual BrowserControl. Then below I can call the desired JavaScript function in the webpage.
void Control_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
if (!scriptCalled)
{
Control.IsScriptEnabled = true;
var param = new string[1];
param[0] = _currentSessionId;
Control.InvokeScript("initWebView", param);
scriptCalled = true;
}
}
This all works perfectly in Windows Phone. In IOS - which is what this question is about - I do something similar with a custom Rendered, And I can get my view and its properties.
However - I am unable to get the Native browser control in IOS to be able to call a JavaScript function on it. This is what I have so far:
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (_view == null && e.NewElement is SessionWebView)
{
_view = (SessionWebView) e.NewElement;
// Attach to the PropertyChanged event on the view
_view.PropertyChanged += _view_PropertyChanged;
}
}
private void _view_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Look for the sessionId propery
if (e.PropertyName == SessionWebView.SessionIdProperty.PropertyName)
{
if(_view != null && !string.IsNullOrEmpty(_view.SessionId))
{
// if we have a view set the sessionId
_currentSessionId = _view.SessionId;
// Now need Browser to attach to loaded or navaigated events...
}
}
}
Any idea on how I could access the Browser control to call the JavaScript function?

Have you had a look on XLabs HybridWebView? https://github.com/XLabs/Xamarin-Forms-Labs/blob/master/src/Forms/XLabs.Forms.iOS/Controls/HybridWebView/HybridWebViewRenderer.cs
It's based on UIWebView control and uses UIWebView.EvaluateJavascript method. Which native browser control do you use? Could you reveal the full renderer code?

Related

c# Cefsharp how to make correct sequence of JavaScript actions on the web site

These sequences of actions work with Thread.Sleep, somewhere in 1 second, somewhere in 2 seconds. I think using Thread.Sleep/Task.Delay is not good. Because it can be performed differently on different computers. How do I execute these sequences without using Thread.Sleep?
Or it is OK to using Thread.Sleep/Task.Delay?
private async void ButtonFind_Click(object sender, EventArgs e)
{
//Action1
string jsScript1 = "document.getElementById('story').value=" + '\'' + textFind.Text + '\'';
await chrome.EvaluateScriptAsync(jsScript1);
//Action2
string jsScript2 = "document.querySelector('body > div.wrapper > div.header > div.header44 > div.search_panel > span > form > button').click();";
await chrome.EvaluateScriptAsync(jsScript2);
//Action3
Thread.Sleep(1000); //it is necessary to set exactly 1 seconds
string jsScript3 = "document.getElementsByTagName('a')[2].click();";
await chrome.EvaluateScriptAsync(jsScript3);
//Action4
Thread.Sleep(2000); //it is necessary to set exactly 2 seconds
string jsScript4 = "document.querySelector('#dle-content > div.section > ul > li:nth-child(3)').click();";
await chrome.EvaluateScriptAsync(jsScript4);
}
I tried to use task expectations, but it didn't help me
...
var task4 = chrome.EvaluateScriptAsync(jsScript4);
task4.Wait();
I also tried to use DOM rendering expectations, which didn't help either
string jsScript4 = #"
if( document.readyState !== 'loading' ) {
myInitCode();
} else {
document.addEventListener('DOMContentLoaded', function () {
myInitCode();
});
}
function myInitCode() {
var a = document.querySelector('#dle-content > div.section > ul > li:nth-child(3)').click();
return a;
}
";
chrome.EvaluateScriptAsync(jsScript4);
My addition (21.04.2022)
In third action instead of using Thread.Sleep, im using "While" loop
Here the algorithm is correct, but for some reason, after pressing the application button, the application is hanging
bool test = false;
while(test == false)
{
string myScript = #"
(function(){
var x = document.getElementsByTagName('a')[1].outerText;
return x;
})();
";
var task = chrome.EvaluateScriptAsync(myScript);
task.ContinueWith(x =>
{
if (!x.IsFaulted)
{
var response = x.Result;
if (response.Success == true)
{
var final = response.Result;
if (final.ToString() == textFind.Text)
{
MessageBox.Show("You found the link");
test = true;
}
else
{
MessageBox.Show("You do not found the link");
}
}
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
My addition (23.04.2022)
string jsScript1 = "document.getElementById('story').value=" + '\'' + textFind.Text + '\'' + ";"
+ #"
Promise.resolve()
.then(() => document.querySelector('body > div.wrapper > div.header > div.header44 > div.search_panel > span > form > button').click())
.then(() => { var target = document.body;
const config = {
childList: true,
attributes: true,
characterData: true,
subtree: true,
attributeFilter: ['id'],
attributeOldValue: true,
characterDataOldValue: true
}
const callback = function(mutations)
{
document.addEventListener('DOMContentLoaded', function(){
if(document.getElementsByTagName('a')[1].innerText=='Troy')
{
alert('I got that link');
}
}, true);
};
const observer = new MutationObserver(callback);
observer.observe(target, config)});
";
var task1 = chrome.EvaluateScriptAsPromiseAsync(jsScript1);
task1.Wait();
Using a MutationObserver wrapped in a promise, using EvaluateScriptAsPromiseAsync to evaluate promise. Also didnt help.
I came to the conclusion that JavaScript does not save the code when clicking on a search button or after going to another page. How do I save the JavaScript code/request and continue it after clicking on a search button or after going to another page?
As your JavaScript causes a navigation you need to wait for the new page to load.
You can use something like the following to wait for the page load.
// create a static class for the extension method
public static Task<LoadUrlAsyncResponse> WaitForLoadAsync(this IWebBrowser browser)
{
var tcs = new TaskCompletionSource<LoadUrlAsyncResponse>(TaskCreationOptions.RunContinuationsAsynchronously);
EventHandler<LoadErrorEventArgs> loadErrorHandler = null;
EventHandler<LoadingStateChangedEventArgs> loadingStateChangeHandler = null;
loadErrorHandler = (sender, args) =>
{
//Actions that trigger a download will raise an aborted error.
//Generally speaking Aborted is safe to ignore
if (args.ErrorCode == CefErrorCode.Aborted)
{
return;
}
//If LoadError was called then we'll remove both our handlers
//as we won't need to capture LoadingStateChanged, we know there
//was an error
browser.LoadError -= loadErrorHandler;
browser.LoadingStateChanged -= loadingStateChangeHandler;
tcs.TrySetResult(new LoadUrlAsyncResponse(args.ErrorCode, -1));
};
loadingStateChangeHandler = (sender, args) =>
{
//Wait for while page to finish loading not just the first frame
if (!args.IsLoading)
{
browser.LoadError -= loadErrorHandler;
browser.LoadingStateChanged -= loadingStateChangeHandler;
var host = args.Browser.GetHost();
var navEntry = host?.GetVisibleNavigationEntry();
int statusCode = navEntry?.HttpStatusCode ?? -1;
//By default 0 is some sort of error, we map that to -1
//so that it's clearer that something failed.
if (statusCode == 0)
{
statusCode = -1;
}
tcs.TrySetResult(new LoadUrlAsyncResponse(statusCode == -1 ? CefErrorCode.Failed : CefErrorCode.None, statusCode));
}
};
browser.LoadingStateChanged += loadingStateChangeHandler;
browser.LoadError += loadErrorHandler;
return tcs.Task;
}
// usage example
private async void ButtonFind_Click(object sender, EventArgs e)
{
//Action1
string jsScript1 = "document.getElementById('story').value=" + '\'' + textFind.Text + '\'';
await chrome.EvaluateScriptAsync(jsScript1);
//Action2
string jsScript2 = "document.querySelector('body > div.wrapper > div.header > div.header44 > div.search_panel > span > form > button').click();";
await Task.WhenAll(chrome.WaitForLoadAsync(),
chrome.EvaluateScriptAsync(jsScript2));
//Action3
string jsScript3 = "document.getElementsByTagName('a')[2].click();";
await Task.WhenAll(chrome.WaitForLoadAsync(),
chrome.EvaluateScriptAsync(jsScript3));
//Action4
string jsScript4 = "document.querySelector('#dle-content > div.section > ul > li:nth-child(3)').click();";
await chrome.EvaluateScriptAsync(jsScript4);
}
You never must work with sleep because time changes between computers and, even in the same computer, a web page may be differ the time required to load.
I work a lot with scraping and IMO the best focus to manage this is working from JavaScript side. You inject/run your JavaScript to fill controls, click buttons...
With this focus, the problem is that navigations make you lose the state. When you navigate to other page, your JavaScript start from scratch. I revolve this sharing data to persist between JavaScript and C# through Bound Object and injecting JavaScript.
For example, you can run action 1, 2 and 3 with a piece of JavaScript code. Before click button, you can use your Bound Object to tell to your C# code that you are going to second page.
When your second page are loaded, you run your JavaScript for your second page (you know the step and can inject the JavaScript code for your 2 page).
In all cases, your JavaScript code must have some mechanism to wait. For example, set a timer to wait until your controls appears. In this way, you can run your JavaScript without wait to the page is fully loaded (sometimes this events are hard to manage).
UPDATE
My scraping library is huge. I'm going to expose pieces that you need to do the work but you need to assemble by yourself.
We create a BoundObject class:
public class BoundObject
{
public BoundObject(IWebBrowser browser)
{
this.Browser = browser;
}
public void OnJavaScriptMessage(string message)
{
this.Browser.OnJavaScriptMessage(message);
}
}
IWebBrowser is an interface of my custom browser, a wrapper to manage all I need. Create a Browser class, like CustomBrowser, for example, implementing this interface.
Create a method to ensure your Bound Object is working:
public void SetBoundObject()
{
// To get events in C# from JavaScript
try
{
var boundObject = new BoundObject();
this._browserInternal.JavascriptObjectRepository.Register(
"bound", boundObject, false, BindingOptions.DefaultBinder);
this.BoundObject = boundObject;
}
catch (ArgumentException ex)
{
if (!ex.ParamName.Identical("bound"))
{
throw;
}
}
}
_browserInternal is the CefSharp browser. You must run that method on each page load, when you navigate. Doing that, you have a window.bound object in JavaScript side with an onJavaScriptMessage function. Then, you can define a function in JavaScript like this:
function sendMessage(msg) {
var json = JSON.stringify(msg);
window.bound.onJavaScriptMessage(json);
return this;
};
You can send now any object to your C# application and manage in your CustomBrowser, on OnJavaScriptMessage method. In that method I manage my custom message protocol, like a typical one in sockets environment or the windows message system and generate a OnMessage that I implement in classes inheriting CustomBrowser.
Send information to JavaScript is trivial using ExecuteScriptAsync of CefSharp browser.
Going further
When I work in an intense scraping job. I create some scripts with classes to manage the entire Web to scrap. I create classes, for example, to do login, navigate to different sections, fill forms... like if I was the owner of the WebSite. Then, when page load, I inject my scripts and I can use my own classes in the remote WebSite making scraping... piece of cake.
My scripts are embedded resources so are into my final executable. In debug, I read them from disk to allow edit+reload+test until my scripts works fine. With the DevTools you can try in the console until you get the desired source. Then you add into your JavaScripts classes and reload.
You can add simple JavaScript with ExecuteScriptAsync, but with large files appears problems escaping quotes...
So you need insert an entire script file. To do that, implement ISchemeHandlerFactory to create and return an IResourceHandler. That resource handler must have a ProcessRequestAsync in which you receive a request.Url that you can use to locale your scripts:
this.ResponseLength = stream.Length;
this.MimeType = GetMimeType(fileExtension);
this.StatusCode = (int)HttpStatusCode.OK;
this.Stream = stream;
callback.Continue();
return true;
stream maybe a MemoryStream in which you write the content of your script file.

How to prevent page reload after JavaScript function is executed?

I am developing a web application in java. But for certain purposes, I have to use javascript. I am facing an infinitive loop issue caused by I believe that very same javascript.
I trigger the init method in java bean by clicking on the command link. This command link should open a new XHTML page. During init, I have to resolve the local IP address which I need for further implementation. I found javascript here on Stack Over Flow which will resolve that for me. After executing javascript I need to store that IP into some variable and pass it back to bean. I found a way of doing it by using <p:remoteCommand> which I trigger right from javascript itself. I managed to pass the variable to bean successfully, and use it for other functionality. At this point, I'm facing a problem, which I have no clue how to solve. After the script is executed successfully somehow the page is reloaded, and the init method is invoked again. So it basically creates an infinite loop.
Here is my init method in java bean:
public void init() {
RequestContext.getCurrentInstance()
.execute("getLocalIP();");
}
JavaScript for resolving local IP address:
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var pc = new RTCPeerConnection({
iceServers : []
}), noop = function() {};
pc.createDataChannel('');
pc.createOffer(pc.setLocalDescription.bind(pc), noop);
pc.onicecandidate = function(ice) {
if (ice && ice.candidate && ice.candidate.candidate) {
var localIP = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4})
{7})/.exec(ice.candidate.candidate)[1];
console.log('my IP: ', localIP);
// this will trigger the remoteCommand
// which will pass value from JS to java bean
loadLocalIP([{name:'localIPParam', value: localIP}]);
pc.onicecandidate = noop;
}
}
Here is my xhtml(remoteCommand) which is triggered from javascript:
<h:form id="localIPForm">
<p:remoteCommand name="loadLocalIP" action="#{aggregationStationController.loadData()}"/>
</h:form>
This method is invoked by remoteCommand:
public void loadData() {
Map<String, String> params = FacesContext.getCurrentInstance()
.getExternalContext()
.getRequestParameterMap();
ipValue = params.get("localIPParam").toString();
System.out.println(ipValue);
if (!ipValue.isEmpty()) {
resolveAggregationStationByIP(ipValue);
}
// after this line init method is invoked again and creates an infinite loop
}
private void resolveAggregationStationByIP(String ipAddress) {
aggregationStation = organizationStructureService.getOrgEntityByTypesAndAttrCodeAndAttrValue(
Arrays.asList(OrgEntityTypeCode.AGGREGATION_STATION.getCode(),
OrgEntityTypeCode.PROD_LINE.getCode()), "IP", ipAddress);
if (aggregationStation != null) {
printerName = organizationStructureService.
getOrgEntityAttrValue(aggregationStation, "PRINTER_NAME");
if (printerName == null) {
printerName = "";
}
// check if there is order in progress for station
selectedPackagingOrder = getInProgressOrderForStation();
loadOperationTypes();
populateDataFromDB();
} else {
error = true;
errorMessage = MessageUtil.interpolate("ip_not_registred_as_station", ipAddress,
MessageUtil.interpolate(OrgEntityTypeCode.AGGREGATION_STATION.getCode()));
}
}
My question is: How to avoid javascript execution more than once?
I would appreciate any help I can get to sort this one out. Thanks in advance!
Try and use event.preventDefault() to prevent the page refreshing.

CefSharp get a part from a Source Code of a selected/active element

Hello everyone 😊 and already thanks in advance!
I need to somehow get only a part of a loaded Website Source Code (Picture point 2) by hovering (if not possible I would also be happy with a mouse click) over an element (Picture point 1).
I know it sounds maybe weird because the DevTool does it already really nice with just a click (Picture point 3).
But if possible I would like to only read out the inner- and outer-HTML (whichever I need in the moment) the part which is active/selected.
What I have reached is:
int counter = 0;
private async void timer1_Tick(object sender, EventArgs e)
{
string returnValue = "";
string script = "(function() { return document.activeElement.outerHTML; })();";
var task = browser.GetMainFrame().EvaluateScriptAsync(script);
await task.ContinueWith(t =>
{
if (!t.IsFaulted)
{
var response = t.Result;
if (response.Success && response.Result != null)
{
returnValue = response.Result.ToString();
}
}
});
if (returnValue != "")
{
richTextBox1.Invoke(new Action(() => richTextBox1.Text = returnValue));
}
else // Just to check if there still happens something:
{
counter += 1;
richTextBox1.Invoke(new Action(() => richTextBox1.Text = counter.ToString() ));
}
}
With this code the problem seems solved 😆. But I wonder if there is an "better" way without an timer.
The answer or lets say the better solution is (thanks to #amaitland) to throw that timer away and use instead (in Form_Load or whereever you setup everything):
browser.JavascriptMessageReceived += OnBrowserJavascriptMessageReceived;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
and put my code in:
private void OnBrowserJavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
{
// the code
}
And also you need:
async void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e)
{ // Does wait till the Website is fully loaded.
if (e.Frame.IsMain)
{
//In the main frame we inject some javascript that's run on mouseUp
//You can hook any javascript event you like.
browser.ExecuteScriptAsync(#"
document.body.onmouseup = function()
{
//CefSharp.PostMessage can be used to communicate between the browser
//and .Net, in this case we pass a simple string,
//complex objects are supported, passing a reference to Javascript methods
//is also supported.
//See https://github.com/cefsharp/CefSharp/issues/2775#issuecomment-498454221 for details
CefSharp.PostMessage(window.getSelection().toString());
}
");
}
}

Crosswalk Cordova Android multiple file select

I have a hybrid app built using cordova and angularjs, for Android I run the app using crosswalk.
I've been scouring the internet to find the solution for the html5 file input to allow selection of multiple files.
I'm using the following element for file selecting:
<input type="file" multiple="multiple" name="files[]" />
I am running Android Lollipop version 5.1.1 and Crosswalk version 20, I have tested with Crosswalk version 18 and 19 also. Chrome is installed on my device running the latest version although I don't think that makes a difference.
When I click the input element above I get the expected dialog asking me to select from my Documents or Camera. If I choose to select from my Documents then I am only able to select single files, in this case images. This is true for every App that I can select images from, so the default android 'Images', 'Videos', 'Audio', etc and external Apps such as Google Photos - All only allow me to select one single file at a time.
In the image below you can see the files listed, a long press on each tile does not add the file to a multiple selection.
This works on the IOS version of the App.
After digging through all the material I can find online it seems that the multiple attribute is supported on Android 5+ running Chrome 49+.
I'm unsure if this is a crosswalk browser implementation or Android Operating System issue, or something else? Could anyone advise.
Edit
Just to confirm this does not work with or without using Crosswalk.
After weeks of trying to sort this out, I finally got it to work (Cordova without Crosswalk). This was done using Cordova Tools in Windows so please pardon the filespecs below.
Step 1: Change the minSdkVersion in platforms\Android\CordovaLib\AndroidManifest.xml to 21
Explanation: onShowFileChooser API was introduced in LOLLIPOP (API 21). It allows returning url[] instead of url returned by showFileChooser in earlier API versions. This gets called only when you change the API to 21 or greater.
Step 2: Update/Replace the onActivityResult method to retrieve multiple files.
Append the following after creating intent using fileChooserParams to allow choosing multiple files:
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
Location: platforms\android\CordovaLib\src\org\apache\cordova\engine\SystemWebChromeClient.java
Step 3: Update the corresponding onActivityResult method to return multiple urls using intent.getClipData().
Caveats:
Enables Multi-upload for all calls. You could update the intent based on fileChooserParams mode.
Disables Camera as a source in chooser which is available with crosswalk by default.
Final Code:
Uri photoUri;
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
// Check and use MIME Type.
String mimeType = "*/*";
int ACTION_CODE = FILECHOOSER_RESULTCODE;
try {
if (fileChooserParams.getAcceptTypes().length > 0) {
mimeType = fileChooserParams.getAcceptTypes()[0];
} else {
mimeType = "*/*";
}
} catch (Exception e) {
mimeType = "*/*";
};
// Check if Mutiple is specified
Boolean selectMultiple = false;
if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
selectMultiple = true;
};
Intent intent = new Intent();
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
if (selectMultiple) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); };
intent.setType(mimeType);
ACTION_CODE = FILECHOOSER_RESULTCODE;
final Intent chooserIntent = Intent.createChooser(intent, "Select Source");
// Add camera intent to the chooser if image and send URI to return full image
if (mimeType.equals("image/*")) {
photoUri = null;
try {
File photoFile = createImageFile();
photoUri = Uri.fromFile(photoFile);
}
catch (Exception ex) {
photoUri = null;
}
if (photoUri != null) {
Intent camIntent = new Intent();
camIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
camIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
camIntent.putExtra("return-data", true);
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent [] {camIntent} );
}
}
try {
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
#Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == Activity.RESULT_OK && intent != null) {
if (intent.getData() != null)
{
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
filePathsCallback.onReceiveValue(result);
}
else
{
if (intent.getClipData() != null) {
final int numSelectedFiles = intent.getClipData().getItemCount();
Uri[] result = new Uri[numSelectedFiles];
for (int i = 0; i < numSelectedFiles; i++) {
result[i] = intent.getClipData().getItemAt(i).getUri();
}
filePathsCallback.onReceiveValue(result);
}
else {
filePathsCallback.onReceiveValue(null);
}
}
}
else if(resultCode == Activity.RESULT_OK && (intent == null || intent.getData() == null )) {
Uri[] result = new Uri[1];
result[0] = photoUri;
filePathsCallback.onReceiveValue(result);
} else {
filePathsCallback.onReceiveValue(null);
}
}
}, chooserIntent, ACTION_CODE);
} catch (ActivityNotFoundException e) {
Log.w("No activity found to handle file chooser intent.", e);
filePathsCallback.onReceiveValue(null);
}
return true;
}

ASP.Net ThreadPool Delegate Callback -- JavaScript Not Firing On Callback Thread

I have searched for several days now, and have tried about every solution that I could find. I know this is something I am not doing correctly, however, I am not sure what the correct way is.
I have an ASP.Net C# web site, running on .Net Framework 4.5. I have a link button on a form, that when clicked fires off a long running process using the ThreadPool. I have a delegate callback setup, and the code does fire when the process is canceled or when it finishes. (I am using the Cancelation Token for canceling the process and the process is Active Reports in case that matters.)
Like I said, everything works great, except for when the callback code fires it does not execute the javascript. (FYI -- this is NOT a javascript callback, just trying to fire off some javascript code when the process finishes.)
Here is the code that i start the report...
string sThreadID = Thread.CurrentThread.ManagedThreadId.ToString();
ThreadPool.QueueUserWorkItem(new WaitCallback(StartReport), cts.Token);
Here is the code for the StartReport....
public static void StartReport(object obj) {
try {
OnTaskCompleteDelegate callback = new OnTaskCompleteDelegate(OnTaskComplete);
BoyceReporting.CallReport(boyce.BoyceThreadingEnvironment.OBRO, "THREADING");
if (boyce.BoyceThreadingEnvironment.CTS.Token.IsCancellationRequested) {
boyce.BoyceThreadingEnvironment.SESSION.sScriptToExecute = "alert('Report Canceled By User');";
callback("CANCELED");
} else {
callback("FINISHED");
}
} catch {
throw;
}
}
Here is the code for the CallBack code.....
public static void OnTaskComplete(string ReportResult) {
try {
sReportResult = ReportResult;
if (ReportResult == "CANCELED") {
// In case we need to do additional things if the report is canceled
}
string sThreadID = Thread.CurrentThread.ManagedThreadId.ToString();
boyce.BoyceThreadingEnvironment.THISPAGE.ClientScript.RegisterStartupScript(boyce.BoyceThreadingEnvironment.THISPAGE.GetType(), "FireTheScript" + DateTime.Now.ToString(), boyce.BoyceThreadingEnvironment.SESSION.sScriptToExecute, true);
ScriptManager.RegisterStartupScript(boyce.BoyceThreadingEnvironment.THISPAGE, boyce.BoyceThreadingEnvironment.THISPAGE.GetType(), "DisplayReport" + DateTime.Now.ToString(), boyce.BoyceThreadingEnvironment.SESSION.sScriptToExecute, true);
} catch {
throw;
}
}
Here is the issue that I am having.....
Everything works fine except i can not get the last line of code to fire the script.
ScriptManager.RegisterStartupScript
Here is what I think is happening.....
From looking at the thread ID, I am sure the reason that the code is not firing is because the ScriptManager code that I am trying to fire in the Call Back event is on a different thread, other than the main thread.
Here is my question(s).....
(1) Am I correct in why this is not firing the JavaScript
(2) How can I (from inside of the CallBack) get this JavaScript to fire? Is there a way to force this to execute on the main Thread?
It's not firing in JS because you're spinning off a new thread. In the meantime, the request has long since returned to the client and closed the connection. By the time the thread tries to write something out to the Response, it's already finished.
Instead of doing it this way, just have your button click (or whatever it is that kicks off the report), inside of an UpdatePanel. Then, you don't need to fire off a new thread.
Here is the cod I used in the C# Code Behind to call the web service to start monitoring this process.
----------------------------------------------------------------------------------------------
CurrentSession.bIsReportRunning = true;
ScriptManager.RegisterStartupScript(this, this.GetType(), "WaitForReport" + DateTime.Now.ToString(), "jsWaitOnCallReport();", true);
MultiThreadReport.RunTheReport(HttpContext.Current, CurrentSession, this, oBRO);
Here is the code that calls the method, using the threadpool, and the method called..
----------------------------------------------------------------------------------------------
ThreadPool.QueueUserWorkItem(new WaitCallback(StartReport), cts.Token);
public static void StartReport(object obj) {
try {
OnTaskCompleteDelegate callback = new OnTaskCompleteDelegate(OnTaskComplete);
BoyceReporting.CallReport(boyce.BoyceThreadingEnvironment.OBRO, "THREADING");
HttpContext.Current = boyce.BoyceThreadingEnvironment.CONTEXT;
if (boyce.BoyceThreadingEnvironment.CTS.Token.IsCancellationRequested) {
boyce.BoyceThreadingEnvironment.SESSION.sScriptToExecute = "alert('Report Canceled By User');";
boyce.BoyceThreadingEnvironment.SESSION.bIsReportRunning = false;
callback("CANCELED");
} else {
boyce.BoyceThreadingEnvironment.SESSION.bIsReportRunning = false;
callback("FINISHED");
}
} catch {
throw;
}
}
Here is the web service method I created to monitor the process, with a built in safety net
--------------------------------------------------------------------------------------------
[WebMethod(EnableSession = true)]
public string WaitOnReport() {
try {
HttpContext.Current = boyce.BoyceThreadingEnvironment.CONTEXT;
SessionManager CurrentSession;
CurrentSession = (SessionManager)boyce.BoyceThreadingEnvironment.SESSION;
DateTime dtStartTime = DateTime.Now;
DateTime dtCurrentTime = DateTime.Now;
if (CurrentSession != null) {
do {
// Build a safety limit into this loop to avoid an infinate loope
// If this runs longer than 20 minutes, then force an error due to timeout
// This timeout should be lowered when they find out what the issue is with
// the "long running reports". For now, I set it to 20 minutes but shoud be MUCH lower.
dtCurrentTime = DateTime.Now;
TimeSpan span = dtCurrentTime-dtStartTime;
double totalMinutes = span.TotalMinutes;
if (totalMinutes>=20) {
return "alert('Error In Creating Report (Time-Out)');";
}
} while (CurrentSession.bIsReportRunning == true);
// If all goes well, return the script to either OPEN the report or display CANCEL message
return CurrentSession.sScriptToExecute;
} else {
return "alert('Error In Creating Report (Session)');";
}
} catch {
throw;
}
}
And here is the JavaScript code I used to initiate the Web Service Call and Also The Postback
--------------------------------------------------------------------------------------------
function jsWaitOnCallReport() {
try {
var oWebService = BoyceWebService.WaitOnReport(jsWaitOnCallReport_CallBack);
} catch (e) {
alert('Error In Calling Report Screen -- ' + e);
}
}
function jsWaitOnCallReport_CallBack(result) {
try {
eval(result);
var myExtender = $find('ModalPopupExtenderPROGRESS');
if (myExtender != null) {
try {
myExtender.hide();
} catch (e) {
// Ignore Any Error That May Be Thrown Here
}
}
$find('PROGRESS').hide();
} catch (e) {
alert('Error In Opening Report Screen -- ' + e);
}
}
Hope this helps someone else out.. Like I said, I am not sure this is the best solution, but it works.. I would be interested in other solutions for this issue to try... Thanks.

Categories

Resources