C# WPF run PowerShell async with Reactive and AsyncRelayCommand - c#

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
});
}

Related

XamarinForms how to make two input fields in a popup element

Please tell me how to make it possible to add another message to DisplayPromptAsync? that is, one more line to enter, so that there are two of them. Maybe somehow override the method?
You can achieve it with dependency service , check the sample for iOS
Define a interface
public interface IAlert
{
//return the input string
Task<string> showSingle(string title, string message);
//return the two input strings
Task<string[]> showMutiple(string title,string message);
}
Implement in platform project
[assembly: Dependency(typeof(Alert))]
namespace ColeForms.iOS
{
public class Alert : IAlert
{
public Task<string> showSingle(string title, string message)
{
var tcs = new TaskCompletionSource<string>();
string result = "";
UIAlertController c = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert);
c.AddTextField((t) => {});
UIAlertAction action = UIAlertAction.Create("OK", UIAlertActionStyle.Default, (a) => {
c.DismissModalViewController(true);
result = c.TextFields[0].Text;
tcs.SetResult(result);
});
c.AddAction(action);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(c, true, null);
return tcs.Task;
}
public Task<string[]> showMutiple(string title, string message)
{
var tcs = new TaskCompletionSource<string[]>();
string[] result = new string[2];
UIAlertController c = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert);
c.AddTextField((t) => {});
c.AddTextField((t) => {});
UIAlertAction action = UIAlertAction.Create("OK", UIAlertActionStyle.Default, (a) => {
c.DismissModalViewController(true);
result[0] = c.TextFields[0].Text;
result[1] = c.TextFields[1].Text;
tcs.SetResult(result);
});
c.AddAction(action);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(c, true, null);
return tcs.Task;
}
}
}
Invoke in Forms
var result = await DependencyService.Get<IAlert>().showSingle("title","msg");

EvaluatejavaScrip in IOS Xamarin

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

Testing callback with observable pattern

I want to write some Unittests with NUnit for our wpf application.
The application downloads some data with System.Net.WebClient in the background using the observer pattern.
Here is an example:
Download.cs
public class Download : IObservable<string>
{
private string url { get; }
private List<IObserver<string>> observers = new List<IObserver<string>>();
private bool closed = false;
private string data = null;
public Download(string url)
{
this.url = url;
startDownload();
}
public IDisposable Subscribe(IObserver<string> observer)
{
if (!observers.Contains(observer))
{
if (!closed)
{
observers.Add(observer);
}
else
{
sendAndComplete(observer);
}
}
return new Unsubscriber(observer, observers);
}
private void startDownload()
{
WebClient client = new WebClient();
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler((object sender, DownloadStringCompletedEventArgs e) => {
if (e.Error != null)
{
data = e.Result;
}
closed = true;
sendAndComplete();
});
client.DownloadStringAsync(new Uri(url));
}
private void sendAndComplete()
{
foreach (var observer in observers)
{
sendAndComplete(observer);
}
observers.Clear();
}
private void sendAndComplete(IObserver<string> observer)
{
if (data != null)
{
observer.OnNext(data);
}
else
{
observer.OnError(new Exception("Download failed!"));
}
observer.OnCompleted();
}
private class Unsubscriber : IDisposable
{
private IObserver<string> _observer { get; }
private List<IObserver<string>> _observers { get; }
public Unsubscriber(IObserver<string> _observer, List<IObserver<string>> _observers)
{
this._observer = _observer;
this._observers = _observers;
}
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
{
_observers.Remove(_observer);
}
}
}
}
DownloadInspector.cs
public class DownloadInspector : IObserver<string>
{
private Action<string> onSuccessAction { get; }
private Action<Exception> onErrorAction { get; }
private Action onCompleteAction { get; }
public DownloadInspector(Action<string> onSuccessAction, Action<Exception> onErrorAction, Action onCompleteAction)
{
this.onSuccessAction = onSuccessAction;
this.onErrorAction = onErrorAction;
this.onCompleteAction = onCompleteAction;
}
public void OnCompleted()
{
onCompleteAction.Invoke();
}
public void OnError(Exception error)
{
onErrorAction.Invoke(error);
}
public void OnNext(string value)
{
onSuccessAction.Invoke(value);
}
}
example (usage)
Download download = new Download("http://stackoverflow.com");
DownloadInspector inspector = new DownloadInspector(
(string data) =>
{
Debug.WriteLine("HANDLE DATA");
},
(Exception error) =>
{
Debug.WriteLine("HANDLE ERROR");
},
() =>
{
Debug.WriteLine("HANDLE COMPLETE");
}
);
I'm still new in c# and not very familiar with asynchronous programming in that language. I know the await and async keywords and know that they work with NUnit, but the current construct don't use this keywords.
Can you help me creating a unit test for this case? Its okay to change/remove the observer pattern.
The constructor for the Download class starts the download, which means that I can't subscribe an observer until after the download has started. That's a race condition. It's possible (although unlikely) that observers will be notified before they can be subscribed.
public Download(string url)
{
this.url = url;
startDownload();
}
But I can go ahead and test because I'm subscribing an observer before that can happen. If you can I'd recommend not doing that. Allow the caller to construct the class in one step and then start the download with a method call.
I also had to change this method. I figured that testing for an error would be the easiest first step, but it needs to do data = e.Result if there is no error, not if there is an error.
private void StartDownload()
{
WebClient client = new WebClient();
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler((object sender, DownloadStringCompletedEventArgs e) =>
{
if (e.Error == null) // <== because of this
{
data = e.Result;
}
closed = true;
sendAndComplete();
});
client.DownloadStringAsync(new Uri(url));
}
What I didn't see coming is that WebClient.DownloadStringAsync isn't actually async. It doesn't return a Task. It just takes a callback. What that means is that there's no sure way to know whether it's done except to wait for it to notify the observer that the download is complete.
My NUnit test runner wasn't running, so I used MsTest. It's the same thing.
The basic approach is that I'm creating some flags, and the inspector responds to notifications by setting the flags. That way I can see which notifications were raised.
The last problem is that because DownloadStringComplete is a callback, the test exits before the Assert. That means it will always pass. So in order to fix it I had to do something I've never seen before, which I found here:
[TestMethod]
public void download_raises_error_notification()
{
var success = false;
bool error = false;
bool complete = false;
var pause = new ManualResetEvent(false);
var download = new Download("http://NoSuchUrlAnywhere.com");
var inspector = new DownloadInspector(
onSuccessAction: s => success = true,
onCompleteAction: () =>
{
complete = true;
pause.Set();
},
onErrorAction: s => error = true
);
download.Subscribe(inspector);
// allow 500ms for the download to fail. This is a race condition.
pause.WaitOne(500);
Assert.IsTrue(error,"onErrorAction was not called.");
}
This is technically an integration test since it must actually attempt a download in order to run. That could be remedied by mocking WebClient.

C# Discord Bot Coding: Creating a command that spams messages then stops with another command

I just need a working line of code that spams a message every five seconds then stops after another command is entered. Like for an example, if a user enters "~raid" then the bot will spam "RAID RAID" every five seconds and stops when the user does "~stop". If anyone could help that'd be awesome.
Here is what I got so far:
class MyBot
{
DiscordClient discord;
CommandService commands;
public MyBot()
{
discord = new DiscordClient(x =>
{
x.LogLevel = LogSeverity.Info;
x.LogHandler = Log;
});
discord.UsingCommands(x =>
{
x.PrefixChar = '~';
x.AllowMentionPrefix = true;
});
commands = discord.GetService<CommandService>();
commands.CreateCommand("checked")
.Do(async (e) =>
{
commands.CreateCommand("weewoo")
.Do(async (e) =>
{
await e.Channel.SendMessage("**WEE WOO**");
});
discord.ExecuteAndWait(async () =>
{
await discord.Connect("discordkeyhere", TokenType.Bot);
});
}
public void Log(object sender, LogMessageEventArgs e)
{
Console.WriteLine(e.Message);
}
}
}
Here is a small example how you could do it.
In this case, you can Start and Stop your bot from outside using the methods Start and Stop.
class MyBot
{
public MyBot()
{
}
CancellationTokenSource cts;
public void Start()
{
cts = new CancellationTokenSource();
Task t = Task.Run(() =>
{
while (!cts.IsCancellationRequested)
{
Console.WriteLine("RAID RAID");
Task.Delay(5000).Wait();
}
}, cts.Token);
}
public void Stop()
{
cts?.Cancel();
}
}
Here is the code to test the MyBot class
static void Main(string[] args)
{
try
{
var b = new MyBot();
while (true)
{
var input = Console.ReadLine();
if (input.Equals("~raid", StringComparison.OrdinalIgnoreCase))
b.Start();
else if (input.Equals("~stop", StringComparison.OrdinalIgnoreCase))
b.Stop();
else if (input.Equals("exit", StringComparison.OrdinalIgnoreCase))
break;
Task.Delay(1000);
}
}
catch (Exception)
{
throw;
}
}

Continuous update of ViewModel from DataService MVVM Light

Originally my ViewModel had the following:
public ObservableCollection<DataListItem> files { get; private set; }
private object _filesLock = new object();
public MainViewModel(IDataService dataService)
{
files = new ObservableCollection<DataListItem>();
BindingOperations.EnableCollectionSynchronization(files, _filesLock);
_dataService = dataService;
}
One of the commands ran this code
await Task.Run(() => {
Parallel.ForEach(files, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (file) =>
{
this.files.Add(new DataListItem(file));
});
});
But this doesn't work at design time. So I moved the code into the DataService.
public async void ScanFolder()
{
if (!CanScanFolder()) return;
files.Clear();
await Task.Run(() =>
{
_dataService.GetData(SelectedFolder, filter, IncludeSubs, (myfiles, error) =>
{
if (error != null)
{
return;
}
foreach (var file in myfiles.files)
{
files.Add(file);
}
}
);
});
}
The DataService code looks like this
public async void GetData(string folder, string filter, bool includeSubs, Action<DataItem, Exception> callback)
{
// Use this to connect to the actual data service
var item = new DataItem(folder, filter, includeSubs);
await item.ScanFolderAsync();
callback(item, null);
}
public async Task<bool> ScanFolderAsync()
{
try
{
var ret = new List<DataListItem>();
var files = Directory.EnumerateFiles(folder, filter, includeSubs ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
await Task.Run(() => {
Parallel.ForEach(files, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (file) =>
{
this.files.Add(new DataListItem(file));
});
});
return true;
}
catch (Exception)
{
return false;
}
}
But as far as I can tell there is no continuous communication channel between the DataService and the ViewModel. So with the new approach it reads all of the files and then displays it in the grid, but I want it to display each update as it goes.
My instinct is to add a Messenger within the foreach and subscribe to it in the ViewModel, but there does not seem to be a way to use a messenger within a data service.
How can the DataService send a message to the ViewModel each time it has scanned a file? Alternatively is there a better architecture for loading the data?
It turns out I was just missing the using statement for messenger and it is possible to update the ViewModel using it.
In the ViewModel constructor I added
Messenger.Default.Register<DataListItem>(this, (item) => { files.Add(item); });
and the ScanFolderAsync() method was updated to send the message
using GalaSoft.MvvmLight.Messaging;
public async Task<bool> ScanFolderAsync()
{
try
{
var ret = new List<DataListItem>();
var files = Directory.EnumerateFiles(folder, filter, includeSubs ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
await Task.Run(() => {
Parallel.ForEach(files, new ParallelOptions() { MaxDegreeOfParallelism = 1 }, (file) =>
{
var item = new DataListItem(file);
Messenger.Default.Send<DataListItem>(item);
this.files.Add(item);
});
});
return true;
}
catch (Exception)
{
return false;
}
}

Categories