I use the following code to run javascript on iOS's WkWebView,but this code doesn't wait for the result
C# code
WKJavascriptEvaluationResult handler = (NSObject result, NSError error) =>
{
if (error == null && !string.IsNullOrWhiteSpace(result.ToString()))
{
string resultFromJSCall = result.ToString(); // I'd expect "MyResult"
};
};
webView.EvaluateJavaScript("test()", handler);
CallBackResult(resultFromJSCall)
JavaScript code:
function test() {
return new Promise(resolve => setTimeout(() => resolve("MyResult"),5000));
}
The javascript should execute when webview has finished loading, otherwise it will not work .
We can do it in DidFinishNavigation method .
webview.NavigationDelegate = new WebViewDelate();
public class WebViewDelate : WKNavigationDelegate
{
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
WKJavascriptEvaluationResult handler = (NSObject result, NSError error) => {
if (error != null)
{
Console.WriteLine(result.ToString());
CallBackResult(result.ToString());
}
};
webView.EvaluateJavaScript("test()", handler);
}
}
Refer to
https://stackoverflow.com/a/50160111/8187800
Related
Hi I want to invoke a method in my angular app from C# code. My angular app resides inside WPF WebBrowser control. Below are the code snippets from C# & Angular.
C# Code snippet:
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
[ComVisible(true)]
public partial class WebBrowserView : UserControl
{
public WebBrowserView()
{
InitializeComponent();
myBrowser.Unloaded += myBrowser_Unloaded;
}
private void myBrowser_Unloaded(object sender, RoutedEventArgs e)
{
// This works:
myBrowser.InvokeScript("execScript", new object[] { "this.alert(123)", "JavaScript" });
// This is what I actually want, but it doesn't work:
// For both of these I get the Script Error:
// System.Runtime.InteropServices.COMException: 'Exception from HRESULT: 0x80020101'
myBrowser.InvokeScript("eval", "alertExample()");
string javascript = "CoreHelper.alertExample();";
myBrowser.InvokeScript("eval", new object[] { javascript });
}
}
Angular 10 snippet:
Global function in Global.ts file:
export function alertExample(){
alert('test');
}
Inside an abstract class CoreHelper.ts
export class CoreHelper {
public static alertExample(){
alert('happy');
}
}
Definitely my alertExample is intended to do a lot more than alerting. I do not want to put any scripts inside index.html.
What is that I am missing/doing wrong here?
I also tried adding the script directly in index.html:
Angular 10 index.html:
<script type="text/javascript">
function test(params) {
alert('index.html');
}
</script>
C#
// This works:
myBrowser.InvokeScript("test");
// This doesn't work:
myBrowser.InvokeScript("eval", new object[] { "test" });
Also tried this:
Angular 10 index.html:
<script type="text/javascript" src="./assets/scripts/global-scripts.js">
</script>
global-scripts.js
function test() {
alert('You have arrived: ');
}
C#
// None of these work:
myBrowser.InvokeScript("test");
myBrowser.InvokeScript("eval", new object[] { "test" });
Thanks,
RDV
Posting my solution for this issue:
C# end:
WebBrowserView (XAML View) code:
We can use WebBrowser Navigating event, I needed more control, hence using explicit API to shutdown the connection.
public Tuple<bool, string> OnShutdownEvent(string scriptName)
{
Tuple<bool, string> result = new Tuple<bool, string>(true, "");
if (scriptName.IsNullOrEmpty())
{
return result;
}
try
{
DependencyObject sender = VisualTreeHelper.GetChild(myBrowser, 0);
if (sender != null)
{
if ((sender is WebBrowser) && ((sender as WebBrowser).Document != null))
{
//If the script is not present, we fall in catch condition
//No way to know if the script worked fine or not.
(sender as WebBrowser).InvokeScript(scriptName);
result = new Tuple<bool, string>(true, string.Format("Successfully executed script- {0}", scriptName));
}
else
{
result = new Tuple<bool, string>(true, "No script invoked.");
}
}
}
catch (Exception e)
{
result = new Tuple<bool, string>(false, e.Message);
}
return result;
}
WebBrowserViewModel code snippet:
public bool OnHandleShutdown()
{
try
{
Tuple<bool, string> result = (this.View as XbapPageView).OnShutdownEvent("closeConnection");
}
catch (Exception) { }
finally
{
XBAP_URI = "about:blank";
}
return true;
}
Now in the angular app, following changes are required:
index.html
<script type="text/javascript">
function closeConnection(params) {
window.webSocketServiceRef.zone.run(
function () {
window.webSocketServiceRef.closeConnection(params);
});
}
webSocketService.ts:
constructor(
private _webSocketService: TestWebsocketService,
private _ngZone: NgZone) {
window['webSocketServiceRef'] = {
component: this,
zone: this._ngZone,
closeConnection: (params) => this.onCloseConnectionEvent(params)
};
}
onCloseConnectionEvent(params) {
this._msgSubscription.unsubscribe();
this._webSocketMsgSubject.complete();
return true;
}
Now WebSocket connection is properly closed.
Thanks,
RDV
I want to use a button click to execute a PowerShell script asynchronously and display the output directly in my UI.
It is based on this StackOverflow question.
Since I couldn't quite figure out the implementation, I tried to solve it myself. It works, but I think there is a lot of room for improvement.
Is my implementation a good approach? In particular, the three observables that I combine in the GetPowerShellOutputObservable method don't feel right to me.
onCompleted is never called, although the observer.OnCompleted(); is passed through in powershellEnded-Observable.
MyViewModel.cs
public MyViewModel()
{
ExecuteCommand = new AsyncRelayCommand<string>(Execute, CanExecute);
// ...
}
public IAsyncRelayCommand ExecuteCommand { get; }
private Task Execute(string scriptFile)
{
_service.GetPowerShellOutputObservable(scriptFile).Subscribe(
onNext: output => print(output),
onError: exception => print($"Exception: {exception.Message}"),
onCompleted: () => print("Completed!")
);
return Task.CompletedTask;
}
private bool CanExecute(string param)
{
return !string.IsNullOrEmpty(param) && File.Exists(param);
}
public void print(string message)
{
Console.WriteLine(message);
//App.Current.Dispatcher.Invoke((Action)delegate
//{
CompleteCommandOutput += DateTime.Now.ToString("HH:mm:ss:fff ") + message + "\r\n";
//});
}
MyPowerShellWorker.cs
// ...
public IObservable<string> GetPowerShellOutputObservable(string scriptFile)
{
var scriptContents = File.ReadAllText(scriptFile);
var powerShell = PowerShell.Create();
powerShell.AddScript(scriptContents);
var powerShellOutputBuffer = new PSDataCollection<string>();
// Observable<string>: Streams.Information
var powershellStreamInformationDataAdded = Observable.FromEventPattern<DataAddedEventArgs>(
handler => powerShell.Streams.Information.DataAdded += handler,
handler => powerShell.Streams.Information.DataAdded -= handler)
.Select(eventPattern =>
{
PSDataCollection<InformationRecord> collection = eventPattern.Sender as PSDataCollection<InformationRecord>;
return collection[eventPattern.EventArgs.Index].MessageData.ToString();
});
// Observable<string>: OutputBuffer
var powershellOutputBufferDataAdded = Observable.FromEventPattern<DataAddedEventArgs>(
handler => powerShellOutputBuffer.DataAdded += handler,
handler => powerShellOutputBuffer.DataAdded -= handler)
.Select(eventPattern =>
{
PSDataCollection<string> collection = eventPattern.Sender as PSDataCollection<string>;
return collection[eventPattern.EventArgs.Index];
});
// Observable<string>: PowerShell ended
var powershellEnded = Observable.Create<string>(
observer =>
{
var invokeAndEndInvokePowerShellTask = Task.Factory.FromAsync(
powerShell.BeginInvoke(
(PSDataCollection<PSObject>)null,
powerShellOutputBuffer),
powerShell.EndInvoke);
invokeAndEndInvokePowerShellTask.ContinueWith(a => {
powerShell.Dispose();
});
invokeAndEndInvokePowerShellTask.ContinueWith(a => {
if (a.Exception == null)
{
observer.OnCompleted();
}
else
{
observer.OnError(a.Exception);
}
});
return Disposable.Empty;
});
return Observable.Merge(new List<IObservable<string>>() {
powershellStreamInformationDataAdded,
powershellOutputBufferDataAdded,
powershellEnded
});
}
Periodically, the application begins to update itself. There is a constant call in the logs:
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
When this happens, if, for example, you open the menu , it closes itself, if something is filled in, it is cleared, the page is updated. There are no timers in the code. I'm testing the app on Xiaomi Redmi. I repeat sometimes it happens sometimes it doesn't. What is it?
I do not know what the problem is, but occasionally, it happens that the application throws the fingerprint to the page. It is intermittent. Sometimes everything works fine. That is, I go through the fingerprint, the next page opens, everything is normal and a second after 5 I am again thrown to the page where you need to enter the fingerprint.
Code for the authorization page:
public authentification()
{
try
{
InitializeComponent();
bool auth = CrossSettings.Current.GetValueOrDefault("authorized", false);
if (auth == false) { CheckAuth(); }
else
{
Application.Current.MainPage = new MasterLk();
}
}
catch { }
}
async void CheckAuth()
{
try
{
var avail = await CrossFingerprint.Current.IsAvailableAsync();
if (!avail)
{
CrossSettings.Current.GetValueOrDefault("authorized", true);
Application.Current.MainPage = new MasterLk();
}
else
{
var request = new AuthenticationRequestConfiguration("NeedAuth", "-");
var result = await CrossFingerprint.Current.AuthenticateAsync(request);
if (result.Authenticated)
{
CrossSettings.Current.GetValueOrDefault("authorized", true);
Application.Current.MainPage = new MasterLk();
}
else
{
CheckAuth();
}
}
}
catch { }
}
On the page where it throws it there is a ListView with a binding:
public class OrdersViewModel : BaseViewModel
{
private Table oldLoan;
private bool isRefreshing;
private readonly string clientId;
public bool IsRefreshing
{
get
{
return isRefreshing;
}
set
{
isRefreshing = value;
OnPropertyChanged("IsRefreshing");
}
}
public ICommand RefreshCommand { get; set; }
public ObservableCollection<Table> Loans { get; set; }
public void ShowOrHideLoan(Table loan)
{
if (oldLoan == loan)
{
loan.IsExpanded = !loan.IsExpanded;
Reload(loan);
}
else
{
if (oldLoan != null)
{
oldLoan.IsExpanded = false;
Reload(oldLoan);
}
loan.IsExpanded = true;
Reload(loan);
}
oldLoan = loan;
}
private void Reload(Table loan)
{
var index = Loans.IndexOf(loan);
Loans.Remove(loan);
Loans.Insert(index, loan);
}
public async Task LoadDataAsync()
{
IsRefreshing = true;
Loans.Clear();
try
{
var loans = await ConnectAPI.GetOrdersAsync(clientId);
await Task.Delay(1000);
foreach (var item in loans)
{
Loans.Add(item);
}
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
finally
{
oldLoan = null;
IsRefreshing = false;
}
}
public OrdersViewModel(string clientId)
{
IsRefreshing = false;
this.clientId = clientId;
Loans = new ObservableCollection<Table>();
RefreshCommand = new Command(async () =>
{
await LoadDataAsync();
});
Task.Run(async () => await LoadDataAsync());
}
}
That is, whenever the [ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4 event appears.MainActivity#a894c70
Throws it to the print page...
and if you stay on this page, it is updated after a while.
MIUI 12 has made an intelligent dark theme. The system itself repaints the applications if they do not support the dark theme. Apparently this service is ForceDarkHelper. And ExcludeList is in the settings a list of applications that cannot be repainted
I have a Xamarin Forms project and implemented a dependency service to send an SMS but I can't figure out how to convert the device independent callbacks into an async await so that I can return it. For example, with my iOS implementation I have something like:
[assembly: Xamarin.Forms.Dependency(typeof(MySms))]
namespace MyProject.iOS.DS
{
class MySms : IMySms
{
// ...
public void SendSms(string to = null, string message = null)
{
if (MFMessageComposeViewController.CanSendText)
{
MFMessageComposeViewController smsController= new MFMessageComposeViewController();
// ...
smsController.Finished += SmsController_Finished;
}
}
}
private void SmsController_Finished(object sender, MFMessageComposeResultEventArgs e)
{
// Convert e.Result into my smsResult enumeration type
}
}
I can change public void SendSms to public Task<SmsResult> SendSmsAsyc but how do I await for the Finished callback and get it's result so that I can have SendSmsAsync return it?
public interface IMySms
{
Task<bool> SendSms(string to = null, string message = null);
}
public Task<bool> SendSms(string to = null, string message = null)
{
//Create an instance of TaskCompletionSource, which returns the true/false
var tcs = new TaskCompletionSource<bool>();
if (MFMessageComposeViewController.CanSendText)
{
MFMessageComposeViewController smsController = new MFMessageComposeViewController();
// ...Your Code...
//This event will set the result = true if sms is Sent based on the value received into e.Result enumeration
smsController.Finished += (sender, e) =>
{
bool result = e.Result == MessageComposeResult.Sent;
//Set this result into the TaskCompletionSource (tcs) we created above
tcs.SetResult(result);
};
}
else
{
//Device does not support SMS sending so set result = false
tcs.SetResult(false);
}
return tcs.Task;
}
Call it like:
bool smsResult = await DependencyService.Get<IMySms>().SendSms(to: toSmsNumber, message: smsMessage);
I have implemented IHttpAsyncHandler in my class to perform 5-6 long running process in background and acknowledge to client on start of each task.
Earlier I was using one session variable and updating it with current status of task, and giving async call request to server from jquery in interval of 5 seconds to get current status, but this implementation is not good because its continually hitting request to server for status.
Then I implemented IHttpAsyncHandler in my application, now server itself send acknowledgement to client, but as per my implementation I am able to send only one acknowledgement! if I try to send more than one then its giving error as "object reference not set to an instance of an object"
please check my sample code.
in my code
ExecuteFirst() method works fine sending acknowledgement to client but ExecuteSecond() does not send acknowledgement its giving error.
I goggled a lot but not getting proper way to send multiple acknowledgement to client.
this is my sample code, please help me if any one have any idea.
Javascript
function postRequest(url) {
var url = 'StartLongRunningProcess.ashx?JOBCODE=18'
var xmlhttp = getRequestObject();
xmlhttp.open("POST", url, true);
xmlhttp.onreadystatechange =
function () {
if (xmlhttp.readyState == 4) {
var response = xmlhttp.responseText;
divResponse.innerHTML += "<p>" + response + "</p>";
}
}
xmlhttp.send();
}
function getRequestObject() {
var req;
if (window.XMLHttpRequest && !(window.ActiveXObject)) {
try {
req = new XMLHttpRequest();
}
catch (e) {
req = false;
}
}
else if (window.ActiveXObject) {
try {
req = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
req = new ActiveXObject('Microsoft.XMLHTTP');
}
catch (e) {
req = false;
}
}
}
return req;
}
StartLongRunningProcess.ashx.cs
public class StartLongRunningProcess: IHttpAsyncHandler, IRequiresSessionState
{
private AsyncRequestResult _asyncResult;
public void ProcessRequest(HttpContext context) {}
public bool IsReusable
{
get { return true;}
}
public IAsyncResult BeginProcessRequest(HttpContext context, System.AsyncCallback cb, object extraData)
{
int32 jobCode= convert.ToInt32(context.Request["JOBCODE"]);
_asyncResult = new AsyncRequestResult(context, cb, jobCode);
if(jobCode==18)
{
StartProcess18()
}
else
{
//StartProcessOther()
}
}
private StartProcess18()
{
var task1= new Task(() =>
{
ExecuteFirst();
});
var task2 = task1.ContinueWith((t1) =>
{
ExecuteSecond();
}, TaskContinuationOptions.OnlyOnRanToCompletion);
task1.Start();
}
private ExecuteFirst()
{
//notify to client that this job has been started
_asyncResult.CurrentContext.Response.Write("First task has been started");
_asyncResult.Notify();
// Above line of code successfully send a acknowledgement to client
//Doing some long running process
}
private ExecuteSecond()
{
//notify to client that this job has been started
_asyncResult.CurrentContext.Response.Write("Second task has been started");
// Above line of code giving error and if I skip it and call Notify() this also does not work.
_asyncResult.Notify();
//Doing some long running process
}
public void EndProcessRequest(IAsyncResult result)
{
}
}
AsyncRequestResult.cs
public class AsyncRequestResult : IAsyncResult
{
private HttpContext context;
private AsyncCallback callback;
private ManualResetEvent completeEvent = null;
private object data;
private object objLock = new object();
private bool isComplete = false;
public AsyncRequestResult(HttpContext ctx, AsyncCallback cb, object d)
{
this.context = ctx;
this.callback = cb;
this.data = d;
}
public HttpContext Context
{
get { return this.context; }
}
public void Notify()
{
//isComplete = true;
lock(objLock)
{
if(completeEvent != null)
{
completeEvent.Set();
}
}
if (callback != null)
{
callback(this);
}
}
public object AsyncState
{
get { return this.data; }
}
public bool CompletedSynchronously
{
get { return false; }
}
public WaitHandle AsyncWaitHandle
{
get
{
lock(objLock)
{
if (completeEvent == null)
completeEvent = new ManualResetEvent(false);
return completeEvent;
}
}
}
public bool IsCompleted
{
get { return this.isComplete; }
}
}
HttpContext.Current is not null only if you access it in a thread that handles incoming requests.
Your continuation task that is running is most likely running on a ThreadPool thread without the HttpContext.Current flowing to the continuation, hence it being null.
Try setting your TaskScheduler to TaskScheduler.FromCurrentSynchronizationContext() in order to execute it in the same context.
private StartProcess18()
{
var task1= new Task(() =>
{
ExecuteFirst();
});
var task2 = task1.ContinueWith((t1) =>
{
ExecuteSecond();
}, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task1.Start();
}