SignalR Reconnecting Event Not Firing Properly in Xamarin Forms - c#

I'm writing a Xamarin Forms Android app that connects to a SignalR server. My goal is to alert the user when their server connection is lost, but when the HubConnection.Reconnecting Event is fired, the content of my handler (shown below) doesn't run. Here's the code:
public static class SignalRService
{
private static HubConnection _connection { get; set; }
public static void SetupSignalRService(string url, string hubEndpoint)
{
_connection = new HubConnectionBuilder()
.WithUrl($"{url}/{hubEndpoint}")
.WithAutomaticReconnect()
.Build();
_connection.Reconnecting += Connection_Reconnecting;
}
public static async Task Connect()
{
await _connection.StartAsync();
}
public static Task Connection_Reconnecting(Exception arg)
{
Application.Current.MainPage.DisplayAlert("Reconnecting", "Check your server status.", "ok");
return Task.CompletedTask;
}
}
When using breakpoints, I can see that the thread makes its way to the opening code block { and first line, but jumps out of the method after I continue. I've tried very similar code on a C# console app project which worked right away (with Console.WriteLine instead of DisplayAlert). Any ideas on what else I can try?

Many thanks to users/1338/jason for supplying the answer:
have you tried running the DisplayAlert on the MainThread?
With the Xamarin.Essentials' MainThread class, the code indeed worked:
public static Task Connection_Reconnecting(Exception arg)
{
MainThread.BeginInvokeOnMainThread(() =>
Application.Current.MainPage.DisplayAlert("Reconnecting", "Check your server status.", "ok"));
return Task.CompletedTask;
}

Related

Azure Service Bus WinForms App - Not Receiving/Displaying Messages

I am trying to display messages to a list box in a WinForms application but it is not working. I am using the most recent Azure namespace, hence using asynchronous methods.
Below is Program.cs:
namespace App
{
public class Program
{
static ServiceBusClient client;
static ServiceBusProcessor processor;
public static List<string> data = new List<string>();
[STAThread]
public static async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
data.Add(body);
// complete the message. messages is deleted from the subscription.
await args.CompleteMessageAsync(args.Message);
}
public static void Main(string[] args)
{
Application.Run(new Form1());
}
public static async Task MainAsync()
{
client = new ServiceBusClient(_serviceBusConn);
// create a processor that we can use to process the messages
processor = client.CreateProcessor(_serviceBustopic, _ServiceBusSubscription, new ServiceBusProcessorOptions());
try
{
// add handler to process messages
processor.ProcessMessageAsync += MessageHandler;
// add handler to process any errors
processor.ProcessErrorAsync += ErrorHandler;
// start processing
await processor.StartProcessingAsync();
}
finally
{
await processor.DisposeAsync();
await client.DisposeAsync();
}
}
}
}
//end of Program.cs
And the Form.cs:
namespace App
{
public partial class Form1 : Form
{
public static List<string> AppNames = new List<string>();
public Form1()
{
InitializeComponent();
}
public static async Task receiveMessage()
{
await Program.MainAsync();
AppNames = Program.data;
}
public async void button1_Click(object sender, EventArgs e)
{
await receiveMessage();
for (int i = 0; i < AppNames.Count; i++)
{
listBox1.Items.Add("item" + AppNames[i].ToString());
}
}
}
}
There is a console version of this program that is functional, but I cannot seem to get it to display the messages in this Winforms Application. Some debugging showed me that the program was getting into the Main async. method upon the button being clicked, but it was not going into the Message Handler despite messages being sent through the service bus.
The pattern that you're using for the Service Bus client and processor isn't going to work in your scenario.
In MainAsync, when you call StartProcessingAsync, the method will return once the processor has started. Execution is then going to reach the finally block, where the processor and client are disposed. At that point, the processor is not running and, therefore, is not receiving messages.
Each time button1_Click runs, you're creating a new set of clients, establishing a new connection to Azure, and then immediately throwing them away.
The processor is intended to be a long-lived type that runs continuously in the background, calling back into your code as messages are available. Likewise, the client is intended to be used as a singleton for the lifetime of your application. I'd suggest reading through the Service Bus "Hello World" sample, which would help to explain some of the types and recommended patterns for use.

How to await an async method which isn't expected to return

I'm making a TCP server. I'm using async/await to handle the multi-threading. The methods I am using for listening to incoming clients and there subsequent messages look a bit like this:
private static async Task Listener()
{
while (Online)
{
TcpClient socket = await tcpListener.AcceptTcpClientAsync();
OnReceivedTcpClient(socket);
}
}
As you can tell, this method isn't expected to return anytime soon. The question I have is regarding how I should call this listener method. Currently the way I'm doing it is this:
(Console App)
In Program.cs within Main, I call Server.Start()
static void Main(string[] args)
{
Console.Title = "Server (Prototype)";
Server.Start(100, 26950);
ConsoleKeyInfo input;
do
{
input = Console.ReadKey();
// check input and do stuff
}
}
while (input.Key != ConsoleKey.C);
}
Server.Start initilises some values and then calls an event which in turn calls the listener
private static event EventHandler<EventArgs> StartEvent;
private static void OnStartEvent() => StartEvent?.Invoke(null, new EventArgs());
public static void Start(int maxClients, int port)
{
Stop();
Console.WriteLine("Starting server...");
Init(maxClients, port);
OnStartEvent();
}
private async void ServerOnStartEvent(object sender, EventArgs e)
{
Online = true;
Console.WriteLine($"Server started on port {Port}");
await Listener();
}
If I had called await Listener(); inside of Server.Start then that method would need the async keyword, and it would have to either return void (Which I know is not an ideal design) or return a Task which then means I would have to call _ = Server.Start() inside program.cs (Which also is not great design).
So my question is, is my solution a good way of awaiting an async Task method and are there better ways to go about it?
The way I usually deal with this is to also add a Stop-method. So Start launches the task and saves it in a field. the stop method requests the task to stop (by whatever means), and returns the task that was stored.
So the caller can await the result from the stop method, and once the task completes, the caller can be sure any resources are cleaned up etc.
A variant would be to let the Start method return something like a IAsyncDisposable, that could allow the using-statement to automatically stop and wait for cleanup when going out of scope.
Example:
public class MyClass
volatile bool stopRequested; // or use CancellationTokenSource
Task task;
public void Start() => task = Task.Run(DoWork); // Should probably make this "longRunning"
public void DoWork(){
while(!stopRequested){
// Do something that take a limited amount of time.
}
// Do cleanup
}
public Task Stop(){
stopRequested = true;
return task;
}

Creating a Remote Desktop Client Application without using Windows Forms (C#)

I need to build a Remote Desktop Client application with C#, which establishes a connection to a remote Windows Server, and then programmatically starts some services to the remote PC.
It's important that, when I logon, the Desktop Environment on the Server side exists, because the services I want to start make use of it, but on the client side I don't want any Windows Forms container, because I want to create these sessions dynamically.
To understand the question better, imagine that i want to establish a Remote Desktop Connection, using a console application.
The point is, in the client side I don't need any GUI, but the services on the Host side need the windows, mouse, internet explorer etc UI handles.
So far I tried to use the MSTSClib to create an RdpClient as discribed here, but that didn't help, because it makes use of the AxHost, which is Windows Forms dependent.
Any ideas on if that's possible, and how can I achieve that?
UPDATE:
Tried this:
using System;
using AxMSTSCLib;
using System.Threading;
using System.Windows.Forms;
namespace RDConsole
{
class Program
{
static void Main(string[] args)
{
var thread = new Thread(() =>
{
var rdp = new AxMsRdpClient9NotSafeForScripting();
rdp.CreateControl();
rdp.OnConnecting += (s, e) => { Console.WriteLine("connecting"); };
rdp.Server = "xxx.xxx.xxx.xxx";
rdp.UserName = "Administrator";
rdp.AdvancedSettings9.AuthenticationLevel = 2;
rdp.AdvancedSettings9.ClearTextPassword = "xxxxxxxxxx";
rdp.Connect();
Console.ReadKey();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
Console.ReadKey();
}
}
}
but i get a null reference exception
"Object reference not set to an instance of an object.
Finally, I'm posting the answer to this.
This is the wrapper for the remote control library, together with the WinForms-like message loop. You still have to reference the windows forms dll and create a form to host the rdpclient, but this now can run from a console app, a windows service, or whatever.
using AxMSTSCLib;
public class RemoteDesktopApi
{
#region Methods
public void Connect((string username, string domain, string password, string machineName) credentials)
{
try
{
var form = new Form();
var remoteDesktopClient = new AxMsRdpClient6NotSafeForScripting();
form.Controls.Add(remoteDesktopClient);
form.Show();
remoteDesktopClient.AdvancedSettings7.AuthenticationLevel = 0;
remoteDesktopClient.AdvancedSettings7.EnableCredSspSupport = true;
remoteDesktopClient.Server = credentials.machineName;
remoteDesktopClient.Domain = credentials.domain;
remoteDesktopClient.UserName = credentials.username;
remoteDesktopClient.AdvancedSettings7.ClearTextPassword = credentials.password;
remoteDesktopClient.Connect();
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
#endregion
#region Nested type: MessageLoopApartment
public class MessageLoopApartment : IDisposable
{
#region Fields/Consts
private static readonly Lazy<MessageLoopApartment> Instance = new Lazy<MessageLoopApartment>(() => new MessageLoopApartment());
private TaskScheduler _taskScheduler;
private Thread _thread;
#endregion
#region Properties
public static MessageLoopApartment I => Instance.Value;
#endregion
private MessageLoopApartment()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
_thread = new Thread(startArg =>
{
void IdleHandler(object s, EventArgs e)
{
Application.Idle -= IdleHandler;
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
}
Application.Idle += IdleHandler;
Application.Run();
});
_thread.SetApartmentState(ApartmentState.STA);
_thread.IsBackground = true;
_thread.Start();
_taskScheduler = tcs.Task.Result;
}
#region IDisposable Implementation
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region Methods
public Task Run(Action action, CancellationToken token)
{
return Task.Factory.StartNew(() =>
{
try
{
action();
}
catch (Exception)
{
// ignored
}
}, token, TaskCreationOptions.LongRunning, _taskScheduler);
}
protected virtual void Dispose(bool disposing)
{
if (_taskScheduler == null) return;
var taskScheduler = _taskScheduler;
_taskScheduler = null;
Task.Factory.StartNew(
Application.ExitThread,
CancellationToken.None,
TaskCreationOptions.None,
taskScheduler)
.Wait();
_thread.Join();
_thread = null;
}
#endregion
}
#endregion
}
and this is how I call the Connect method
public void ConnectToRemoteDesktop((string username, string domain, string password, string machineName) credentials)
{
RemoteDesktopApi.MessageLoopApartment.I.Run(() =>
{
var ca = new RemoteDesktopApi();
ca.Connect(credentials);
}, CancellationToken.None);
}
This may also be useful with other types ActiveX controls.
Can you please update your question related to the first comments :)
Then if I fully understand your question you can have a look to this MSD forum: https://social.msdn.microsoft.com/Forums/vstudio/en-US/6c8a2d19-a126-4b4b-aab7-0fa4c22671ed/hosting-remote-desktop-connection-in-wpf-app?forum=wpf
You can try something like this (this seems to based on your research) :
try
{
axMsRdpClient.Server = ServerName;
axMsRdpClient.DesktopHeight = 768;
axMsRdpClient.DesktopWidth = 1024;
axMsRdpClient.Connect();
}
catch (Exception Ex)
{
MessageBox.Show(Ex.Message);
}
The problem you are trying solve sounds like a textbook case for a web service solution.
You should have an application running on the server that is a web service, waiting for requests.
Your client application (console application, whatever) sends calls to the web service to request that the server take some action.
The application on the server receives the request and performs the required tasks.
Is there some specific reason you want to be able to access the mouse, etc. on the server from the client?

Subscribe to category stream, event never appears in subscription client

Being a first time user of GetEventStore and having read the docs, I have an issue where events never appears on my subscription client.
This is possible due to a configuration step I've missed.
Having this console application client:
public class EventStoreSubscriptionClient : ISubscriptionClient
{
private const string GroupName = "liner";
private const string StreamName = "$ce-happening";
private readonly IProvideEventStoreConnection _eventStoreConnection;
private readonly UserCredentials _userCredentials;
private EventStorePersistentSubscriptionBase EventStorePersistentSubscriptionBase { get; set; }
public EventStoreSubscriptionClient(IProvideEventStoreConnection eventStoreConnection, UserCredentials userCredentials)
{
_eventStoreConnection = eventStoreConnection;
_userCredentials = userCredentials;
}
public void Connect()
{
var connection = _eventStoreConnection.ConnectAsync().Result;
EventStorePersistentSubscriptionBase = connection.ConnectToPersistentSubscription(
StreamName,
GroupName,
EventAppeared,
SubscriptionDropped,
_userCredentials,
10,
false
);
}
private void SubscriptionDropped(EventStorePersistentSubscriptionBase subscription, SubscriptionDropReason reason, Exception ex)
{
Connect();
}
private async void EventAppeared(EventStorePersistentSubscriptionBase subscription, ResolvedEvent resolvedEvent)
{
Console.WriteLine("Event appeared: " + resolvedEvent.Event.EventId);
}
public void Dispose()
{
EventStorePersistentSubscriptionBase.Stop(TimeSpan.FromSeconds(15));
}
}
Upon starting this console application, the connection goes fine to http://myserver:1113. In the administration panel of my event store I can see there is a connection to this stream/group on the competing consumers tab:
But if I send a event like to happening-<guid> it shows up on the stream browser, but my subscription client never gets an event appeared event:
Have I misunderstood on how subscriptions, streams and groups works? Please enlighten me.
The answer here is that projections for the Event Store was disabled.
Start the store with --run-projections=all

Code Execution in Form Stops

I'm writing an application which is supposed to communicate with a windows mobile 6.5 device. When the device is plugged in, the activeHandler callback is returned.
The problem that I'm having is that code execution stops at the
lblStatus.Text = "someString";
line. No Exception is thrown, the code execution is justed stopped and the gui gets the focus. I've tried the same thing with using the invoke method on the label property which yielded the same result. If calling non form code in that method, everything runs fine.
public partial class MyClass: Form
{
public MyClass()
{
ActiveHandler active = new ActiveHandler(ActiveSync_Active);
sync.addHandler(active)
}
private void ActiveSync_Active() {
lblStatus.Text = "someString";
//Some code
}
}
Edit:
The invoke call that didn't work
private delegate void StatusLabelChange(string str);
private void ChangeStatusLabelText(string str)
{
lblStatus.Text = str;
}
private void ActiveSync_Active() {
lblStatus.Invoke(new StatusLabelChange(ChangeStatusLabelText), new object[] {"asd"});
}
Try BeginInvoke too, since that'll push it async.

Categories