XamarinForms problem with code thas is OBSOLETE - c#

I try to use map functionality in my app using Xamarin.Forms.
In this part of the code I get the permission for allow to see my location in the map.
[Obsolete]
private async void GetPermession()
{
try
{
var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Plugin.Permissions.Abstractions.Permission.LocationWhenInUse);
//var status = await CrossPermissions.Current.CheckPermissionStatusAsync<LocationWhenInUsePermission>();
if (status != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Plugin.Permissions.Abstractions.Permission.LocationWhenInUse))
{
await
DisplayAlert("Need your location", "We need to acces your location", "Ok");
}
var result = await CrossPermissions.Current.RequestPermissionsAsync(Plugin.Permissions.Abstractions.Permission.LocationWhenInUse);
if (result.ContainsKey(Plugin.Permissions.Abstractions.Permission.LocationWhenInUse))
status = result[Plugin.Permissions.Abstractions.Permission.LocationWhenInUse];
}
if (status == Plugin.Permissions.Abstractions.PermissionStatus.Granted)
this.LocationMap.IsShowingUser = true;
else
await
DisplayAlert("Need your location", "We need to acces your location", "Ok");
}
catch (Exception exception)
{
await
DisplayAlert("Error", exception.Message, "Ok");
}
}
Visual Studio tell me that
CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Plugin.Permissions.Abstractions.Permission.LocationWhenInUse)) is obsolete, and the new method is:
CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync()
But, if I use the new method
var result = await CrossPermissions.Current.RequestPermissionsAsync(Plugin.Permissions.Abstractions.Permission.LocationWhenInUse);
if (result.ContainsKey(Plugin.Permissions.Abstractions.Permission.LocationWhenInUse))
status = result[Plugin.Permissions.Abstractions.Permission.LocationWhenInUse];
the result doesn't have a .ContainsKey method.
How can I solve this?
Sorry for my bad English, and thanks

You can grab the permission using Xamarin.Essential and Dependency service like this:
Interface
public interface IPermissionHelper
{
Task<PermissionStatus> RequestPermissionsAsync();
}
Call it from Shared project:
await DependencyService.Get<IPermissionHelper>().RequestPermissionsAsync();
iOS Renderer:
public class PermissionHelper : IPermissionHelper
{
public PermissionHelper()
{
}
/// <summary>
/// Defines the showTrackingMap.
/// </summary>
LocationCheck showTrackingMap;
/// <summary>
/// The RequestPermissionsAsync.
/// </summary>
/// /// <returns>The <see cref="Task<PermissionStatus>"/>.</returns>
public async Task<PermissionStatus> RequestPermissionsAsync()
{
PermissionStatus permission = PermissionStatus.Unknown;
showTrackingMap = new LocationCheck((s, ev) => {
if ((ev as LocationCheck.LocationCheckEventArgs).Allowed)
{
permission = PermissionStatus.Granted;
}
else
{
permission = PermissionStatus.Denied;
}
showTrackingMap.Dispose();
});
while (permission == PermissionStatus.Unknown)
{
await Task.Delay(10);
}
return permission;
}
}
public class LocationCheck : NSObject, ICLLocationManagerDelegate
{
public class LocationCheckEventArgs : EventArgs
{
public readonly bool Allowed;
public LocationCheckEventArgs(bool Allowed)
{
this.Allowed = Allowed;
}
}
CLLocationManager locationManager;
EventHandler locationStatus;
public LocationCheck(EventHandler locationStatus)
{
this.locationStatus = locationStatus;
Initialize();
}
public LocationCheck(NSObjectFlag x) : base(x) { Initialize(); }
public LocationCheck(IntPtr handle) : base(handle) { Initialize(); }
public LocationCheck(IntPtr handle, bool alloced) : base(handle, alloced) { Initialize(); }
public void Initialize()
{
locationManager = new CLLocationManager
{
Delegate = this
};
locationManager.RequestWhenInUseAuthorization();
}
[Export("locationManager:didChangeAuthorizationStatus:")]
public void AuthorizationChanged(CLLocationManager manager, CLAuthorizationStatus status)
{
switch (status)
{
case CLAuthorizationStatus.AuthorizedAlways:
case CLAuthorizationStatus.AuthorizedWhenInUse:
locationStatus.Invoke(locationManager, new LocationCheckEventArgs(true));
break;
case CLAuthorizationStatus.Denied:
case CLAuthorizationStatus.Restricted:
locationStatus.Invoke(locationManager, new LocationCheckEventArgs(false));
break;
}
}
protected override void Dispose(bool disposing)
{
locationStatus = null;
locationManager.Delegate = null;
locationManager.Dispose();
base.Dispose(disposing);
}
}

public async void GetPermission()
{
var permission = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
if (permission != PermissionStatus.Granted)
{
permission = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
}
else if(permission == PermissionStatus.Granted)
this.LocationMap.IsShowingUser = true;
if (permission != PermissionStatus.Granted)
return;
}
I resolve, using this method, thaks to all to answering

Related

Virtual switch using SignalR server (Host my app)

I just created a chat App using signalR in C#(server) and JS(Client) and every thing is good and there is no problem when I run the server and client in my same device (localhost)
Now I want to run the client app out side my network , for example I want to make a chat with my friend who is living in another country using My app ,but I don't now how to do that and from where I need to start my search to do so.
Pleas if any one know how to do that or if any one know source that I can learn from it Help me
This is My signalR server Code
namespace PmmTcpListener
{
public class PmmSignalRServer : IDisposable
{
#region Private Properties
/// <summary>
/// To be set in <see cref="Dispose(bool)"/> method
/// </summary>
private bool _disposedValue;
public string SignalRwebAddress = "https://localhost:5050";
public bool IsStarted = false;
public bool cIsStarted = false;
public string cSignalRwebAddress = "https://localhost:5050/pmmchat";
private string PrevMessage = "";
private string CrntMessage = "";
private string ClientUrl = "";
#endregion
#region Constructor
/// <summary>
/// The main constructor for the class
/// Doing instantiation for Sql Commands based on database type <see cref="DbTypes"/>
/// By the way the default selected one is type <see cref="DbTypes.mysql"/>
/// </summary>
public PmmSignalRServer()
{
//Initialize the Sql Commands
//Sqlcmd = new MySqlCommand();
//AlarmsCommand = new MySqlCommand();
//SqlServerCmd = new SqlCommand();
//SqlServerAlarmsCommand = new SqlCommand();
//SqliteCmd = new SQLiteCommand();
//SqliteAlarmsCommand = new SQLiteCommand();
}
#endregion
#region MainFunctions
/// <summary>
/// To be set
/// </summary>
//public void Start(string SRport = "https://localhost:5050")
//{
// CreateWebHostBuilder().Build().Run();
//}
//private static IWebHostBuilder CreateWebHostBuilder() => WebHost.CreateDefaultBuilder().UseStartup<iStartup>().UseUrls("https://localhost:5050");
/// <summary>
/// To be set
/// </summary>
public void StartServe(string SRport = "http://localhost:5050")
{
SignalRwebAddress = SRport;
IsStarted = true;
Thread Serverthread = new Thread(StartSeerverThread);
Serverthread.Start();
}
/// <summary>
/// To be set
/// </summary>
private void StartSeerverThread()
{
try
{
WebHost.CreateDefaultBuilder()
.ConfigureServices(services => services.AddSignalR())
.ConfigureServices(services => services.AddCors(options => options.AddPolicy("CorsPolicy",
builder =>
{
//builder.AllowAnyMethod().AllowAnyHeader()
// .WithOrigins("http://localhost:59054")
// .AllowCredentials();
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
})))
.Configure(app => app.UseRouting().UseCors("CorsPolicy").UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/PmmChat");
})
)
.UseUrls(SignalRwebAddress)
.ConfigureLogging(config =>
{
config.ClearProviders();
})
.Build()
.Run();
}
catch (Exception ex)
{
PmmGlobFunctions.TrackThis(" StartServerThread : " + ex.Message);
}
}
/// <summary>
/// To be set
/// </summary>
public void StartClient(string hubUrl = "https://localhost:5050")
{
SignalRwebAddress = hubUrl;
cIsStarted = true;
//ClientUrl
ClientUrl = SignalRwebAddress.Replace(#"*", "localhost");
ClientUrl = ClientUrl + #"/pmmchat";
Thread thread = new Thread(StartClientThread);
thread.Start();
}
/// <summary>
/// To be set
/// </summary>
private void StartClientThread()
{
try
{
//IsStarted = true; .WithUrl(SignalRwebAddress+"/pmmchat")
var connection = new HubConnectionBuilder().WithUrl(ClientUrl).Build();
connection.StartAsync().Wait();
connection.InvokeCoreAsync("SendMessage", args: new[] { "PMM Users" }); // join the main group "PMM Users"
while (cIsStarted)
{
if (CrntMessage != PrevMessage)
{
connection.InvokeCoreAsync("SendMessage", args: new[] { "PmmServer", CrntMessage });
PrevMessage = CrntMessage;
}
}
connection.DisposeAsync();
}
catch (Exception ex)
{
PmmGlobFunctions.TrackThis(" StartClientThread : " + ex.Message);
}
}
/// <summary>
/// To be set
/// </summary>
public void StopClient()
{
cIsStarted = false;
}
/// <summary>
/// To be set
/// </summary>
public void SendFromServer(string message)
{
CrntMessage = message;
//connection.On("ReceiveMessage", (string userName, string message) =>
//{
// Console.WriteLine(userName + " : " + message);
//});
}
#endregion
#region Implement Dispose
/// <summary>
/// if <paramref name="disposing"/> = true, The dispose method has been called
/// by our code. Managed and unmanaged objects can be disposed
/// if <paramref name="disposing"/> = false, The dispose method has been called
/// by runtime finalizer. Unmanaged objects only should be disposed.
/// </summary>
/// <param name="disposing">disposing value</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
// Dispose any managed objects
// ...
}
// Now disposed of any unmanaged objects
// ...
_disposedValue = true;
}
}
/// <summary>
/// Dispose method which implemented by <see cref="IDisposable"/>
/// </summary>
public void Dispose()
{
// Calling override Dispose method to be used in implemented method
Dispose(true);
// Commented by Dr.Mahmoud
//GC.SuppressFinalize(this);
}
#endregion
}
public class ConnectionMapping<T>
{
private readonly Dictionary<T, HashSet<string>> _connections =
new Dictionary<T, HashSet<string>>();
public int Count
{
get
{
return _connections.Count;
}
}
public void Add(T key, string connectionId)
{
lock (_connections)
{
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections))
{
connections = new HashSet<string>();
_connections.Add(key, connections);
}
lock (connections)
{
connections.Add(connectionId);
}
}
}
public IEnumerable<string> GetConnections(T key)
{
HashSet<string> connections;
if (_connections.TryGetValue(key, out connections))
{
return connections;
}
return Enumerable.Empty<string>();
}
public void Remove(T key, string connectionId)
{
lock (_connections)
{
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections))
{
return;
}
lock (connections)
{
connections.Remove(connectionId);
if (connections.Count == 0)
{
_connections.Remove(key);
}
}
}
}
}
public class ChatHub : Hub
{
// My Work --> Mohannad
private readonly static ConnectionMapping<string> _connections = new ConnectionMapping<string>();
public override Task OnConnectedAsync()
{
Groups.AddToGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
return base.OnConnectedAsync();
}
public async Task SendMessageToClient(string user, string receiverConnectionId, string message)
{
foreach (var connectionId in _connections.GetConnections(user))
{
await Clients.Client(connectionId).SendAsync("ReceiveMessage", user, message);
}
// await Clients.Client(receiverConnectionId).SendAsync("ReceiveMessage", user, message);
}
public async Task SendMessageUsers(string user, string[] receiverUsersIds, string message)
{
// here you need to set the list of Users
await Clients.Users(receiverUsersIds).SendAsync("ReceiveMessage", user, message);
}
public async Task SendMessageOthers(string user, string message)
{
await Clients.Others.SendAsync("ReceiveMessage", user, message);
}
public async Task SendMessageToUser(string user, string receiverConnectionId, string message)
{
await Clients.Client(receiverConnectionId).SendAsync("ReceiveMessage", user, message);
}
public string GetConnectionId() => Context.ConnectionId;
//public override Task OnConnectedAsync()
//{
// string name = Context.User.Identity.Name;
// _connections.Add(name, Context.ConnectionId);
// return base.OnConnectedAsync();
//}
//public override Task OnDisconnectedAsync(Exception exception)
//{
// string name = Context.User.Identity.Name;
// _connections.Remove(name, Context.ConnectionId);
// return base.OnDisconnectedAsync(exception);
//}
// end Mohannad
public async Task SendMessage(string user, string message)
{
// await Clients.All.SendAsync("ReceiveMessage", user, PmmCommandReader.ReadCommand(message));
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public Task SendMessageToCaller(string user, string message)
{
return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}
public Task SendMessageToGroup(string user, string message)
{
return Clients.Group("PMM Users").SendAsync("ReceiveMessage", user, message);
}
public Task JoinGroup(string groupName)
{
return Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
}
//End of NameSpace
}

Xamarin Android ForceDarkHelper What is it?

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

c# Xamarin Android return response bool from AlertDialog

I am trying to return a bool true if the user selects yes from AlertDialog and visa versa.
at the moment it always returns false. it seems like the bool "result" is never being set.
public bool AskForConfirmation(string messege, Context context)
{
bool result;
Android.Support.V7.App.AlertDialog.Builder dialog = new Android.Support.V7.App.AlertDialog.Builder(context);
dialog.SetPositiveButton("Yes", (sender, args) =>
{
result = true;
});
dialog.SetNegativeButton("No", (sender, args) =>
{
result = false;
}).SetMessage(messege).SetTitle("System Message");
dialog.Show();
return result;
}
And I call the method
this.RunOnUiThread(() =>
{
bool response = ioManager.AskForConfirmation("Message", this);
Console.WriteLine("Response is " + response);
});
You can create a Task-based dialog via a ManualResetEvent or a TaskCompletionSource so you can call it like this:
Usage via TaskCompletionSource Example:
try
{
var result = await DialogAsync.Show(this, "StackOverflow", "Does it rock?");
Log.Debug("SO", $"Dialog result: {result}");
}
catch (TaskCanceledException ex)
{
Log.Debug("SO", $"Dialog cancelled; backbutton, click outside dialog, system-initiated, .... ");
}
DialogAsync via TaskCompletionSource Example:
public class DialogAsync : Java.Lang.Object, IDialogInterfaceOnClickListener, IDialogInterfaceOnCancelListener
{
readonly TaskCompletionSource<bool?> taskCompletionSource = new TaskCompletionSource<bool?>();
public DialogAsync(IntPtr handle, Android.Runtime.JniHandleOwnership transfer) : base(handle, transfer) { }
public DialogAsync() { }
public void OnClick(IDialogInterface dialog, int which)
{
switch (which)
{
case -1:
SetResult(true);
break;
default:
SetResult(false);
break;
}
}
public void OnCancel(IDialogInterface dialog)
{
taskCompletionSource.SetCanceled();
}
void SetResult(bool? selection)
{
taskCompletionSource.SetResult(selection);
}
public async static Task<bool?> Show(Activity context, string title, string message)
{
using (var listener = new DialogAsync())
using (var dialog = new AlertDialog.Builder(context)
.SetPositiveButton("Yes", listener)
.SetNegativeButton("No", listener)
.SetOnCancelListener(listener)
.SetTitle(title)
.SetMessage(message))
{
dialog.Show();
return await listener.taskCompletionSource.Task;
}
}
}
Usage Via ManualResetEvent Example:
using (var cancellationTokenSource = new CancellationTokenSource())
{
var result = await DialogAsync.Show(this, "StackOverflow", "Does it rock?", cancellationTokenSource);
if (!cancellationTokenSource.Token.IsCancellationRequested)
{
Log.Debug("SO", $"Dialog result: {result}");
}
else
{
Log.Debug("SO", $"Dialog cancelled; backbutton, click outside dialog, system-initiated, .... ");
}
}
DialogAsync via ManualResetEvent Example:
public class DialogAsync : Java.Lang.Object, IDialogInterfaceOnClickListener, IDialogInterfaceOnCancelListener
{
readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
CancellationTokenSource cancellationTokenSource;
bool? result;
public DialogAsync(IntPtr handle, Android.Runtime.JniHandleOwnership transfer) : base(handle, transfer) { }
public DialogAsync() { }
public void OnClick(IDialogInterface dialog, int which)
{
switch (which)
{
case -1:
SetResult(true);
break;
default:
SetResult(false);
break;
}
}
public void OnCancel(IDialogInterface dialog)
{
cancellationTokenSource.Cancel();
SetResult(null);
}
void SetResult(bool? selection)
{
result = selection;
resetEvent.Set();
}
public async static Task<bool?> Show(Activity context, string title, string message, CancellationTokenSource source)
{
using (var listener = new DialogAsync())
using (var dialog = new AlertDialog.Builder(context)
.SetPositiveButton("Yes", listener)
.SetNegativeButton("No", listener)
.SetOnCancelListener(listener)
.SetTitle(title)
.SetMessage(message))
{
listener.cancellationTokenSource = source;
context.RunOnUiThread(() => { dialog.Show(); });
await Task.Run(() => { listener.resetEvent.WaitOne(); }, source.Token);
return listener.result;
}
}
}

UWP AppServiceConnection - SendResponseAsync returns AppServiceResponseStatus.Failure

I'm trying to create a UWP service app on the Raspberry Pi3 which provides the access to the on board UART. I'm facing an issue about the AppConnection Request/response.
this is the service method that handles the incoming requests from client apps
internal class Inbound
{
public static async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
var messageDeferral = args.GetDeferral();
var response = new ValueSet();
bool success = false;
var msg = args.Request.Message.Keys;
if (args.Request.Message.TryGetValue(ServiceApiRequests.Keys.Command, out object command))
{
try
{
switch (command)
{
case ServiceApiRequests.CommandValues.UartWrite:
if (args.Request.Message.TryGetValue(ServiceApiRequests.Keys.UartTxBuffer, out object txBuffer))
{
string rxBuff = "";
success = await Pi3.Peripherals.Uart.GerInstance(57600).Write((string)txBuffer);
if (success)
{
Debug.WriteLine("Tx: " + (string)txBuffer);
if (args.Request.Message.TryGetValue(ServiceApiRequests.Keys.ReadUartResponse, out object getResponse))
{
if ((string)getResponse == ServiceApiRequests.ReadUartResponse.Yes)
{
rxBuff = await Pi3.Peripherals.Uart.GerInstance(57600).Read();
Debug.WriteLine("Rx: " + rxBuff);
}
}
}
response.Add(ServiceApiRequests.Keys.UartRxBuffer, rxBuff);
}
break;
}
}
catch (Exception ex)
{
success = false;
}
}
response.Add(new KeyValuePair<string, object>(ServiceApiRequests.Keys.Result, success ? ServiceApiRequests.ResultValues.Ok : ServiceApiRequests.ResultValues.Ko));
var result = await args.Request.SendResponseAsync(response);
if (result == AppServiceResponseStatus.Failure)
{
Debug.WriteLine("Failed to send the response");
}
messageDeferral.Complete();
}
}
As you can figure out, the Uart class is get using the Singleton pattern using the method Pi3.Peripherals.Uart.GerInstance(57600).
Following the code i using for send the request from the client app.
public static class Uart
{
public static IAsyncOperation<string> SendCommand(this AppServiceConnection DriverControllerConnection, string txBuffer, string awaitResponse = ServiceApiRequests.ReadUartResponse.Yes)
{
return _SendCommand(DriverControllerConnection, txBuffer, awaitResponse).AsAsyncOperation();
}
private static async Task<string> _SendCommand(AppServiceConnection DriverControllerConnection, string txBuffer, string awaitResponse)
{
AppServiceResponse response = null;
string response_str = "";
try
{
if (DriverControllerConnection != null)
{
response = await DriverControllerConnection.SendMessageAsync(new ServiceApiRequests.UartWrite().GetCommand(txBuffer, awaitResponse));
if (response.Status == AppServiceResponseStatus.Success)
{
if (response.Message.TryGetValue(ServiceApiRequests.Keys.Result, out object result))
{
if ((string)result == ServiceApiRequests.ResultValues.Ok && awaitResponse == ServiceApiRequests.ReadUartResponse.Yes)
{
response_str = response.Message[ServiceApiRequests.Keys.UartRxBuffer] as string;
}
}
}
}
}
catch (Exception ex)
{
// TODO: log
}
return response_str;
}
}
The system works well just for a while, until i have response.Status == AppServiceResponseStatus.Success , then the result of the request changes and it becomes AppServiceResponseStatus.Failure. This way the program counter never steps into the condition if (response.Status == AppServiceResponseStatus.Success).
Any idea about the cause?
Thank you so much for the help.
EDIT
Follow the suggestions, i added an handler for the ServiceClosed event. This is the main class.
public sealed class DriverListener : IBackgroundTask
{
private BackgroundTaskDeferral backgroundTaskDeferral;
private AppServiceConnection appServiceConnection;
public void Run(IBackgroundTaskInstance taskInstance)
{
backgroundTaskDeferral = taskInstance.GetDeferral();
// taskInstance.Canceled += OnTaskCanceled;
var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails;
appServiceConnection = triggerDetails.AppServiceConnection;
appServiceConnection.RequestReceived += Inbound.OnRequestReceived;
appServiceConnection.ServiceClosed += OnTaskCanceled;
}
private void OnTaskCanceled(AppServiceConnection sender, AppServiceClosedEventArgs reason)
{
if (this.backgroundTaskDeferral != null)
{
Debug.WriteLine("ServiceClosed");
// Complete the service deferral.
this.backgroundTaskDeferral.Complete();
}
}
}
Placing a breakpoint in this function, i see that it was never triggered.
The app connection is opened using the singleton pattern, and putted in a dll that i use in the client app
public static AppServiceConnection GetDriverConnectionInstance()
{
if (_DriverConnectionInstance == null)
{
try
{
_DriverConnectionInstance = OpenDriverConnection().AsTask().GetAwaiter().GetResult();
}
catch
{
}
}
return _DriverConnectionInstance;
}
I also add a Request to the service that toggles a led, and i noticed that the led status changes but the response from the app service is still "Failure" and the message is null.
The AppService has a default lifetime of 25sec, unless it is being requested by the foreground experience. When the service shuts down the connection, your client process will receive the ServiceClosed event, so you know you will need to reopen the connection the next time you want to send a request.

Volatile IEnlistmentNotification, TransactionScope.AsyncFlowEnabled = true and complex async/wait

This is a followup question to the following question:
Volatile IEnlistmentNotification and TransactionScope.AsyncFlowEnabled = true
The approach accepted in the question above works as long as you don't await multiple statements. Let me show an example:
public class SendResourceManager : IEnlistmentNotification
{
private readonly Action onCommit;
public SendResourceManager(Action onCommit)
{
this.onCommit = onCommit;
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Commit(Enlistment enlistment)
{
Debug.WriteLine("Committing");
this.onCommit();
Debug.WriteLine("Committed");
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
}
public class AsyncTransactionalMessageSender : ISendMessagesAsync
{
private readonly List<Message> sentMessages = new List<Message>();
public IReadOnlyCollection<Message> SentMessages
{
get { return new ReadOnlyCollection<Message>(this.sentMessages); }
}
public async Task SendAsync(Message message)
{
if (Transaction.Current != null)
{
await Transaction.Current.EnlistVolatileAsync(
new SendResourceManager(async () => await this.SendInternal(message)),
EnlistmentOptions.None);
}
else
{
await this.SendInternal(message);
}
}
private async Task SendInternal(Message message)
{
Debug.WriteLine("Sending");
await Task.Delay(1000);
this.sentMessages.Add(message);
Debug.WriteLine("Sent");
}
}
[Test]
public async Task ScopeRollbackAsync_DoesntSend()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
// We do not commit the scope
}
sender.SentMessages.Should().BeEmpty();
}
[Test]
public async Task ScopeCompleteAsync_Sends()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
tx.Complete();
}
sender.SentMessages.Should().HaveCount(3)
.And.Contain(m => m.Value == "First")
.And.Contain(m => m.Value == "Second")
.And.Contain(m => m.Value == "Last");
}
As soon as you introduce a Task.Delay like shown in the example above the generated asynchronous statemachine will never come back and invoke the this.sentMessages.Add(message) and Debug.WriteLine("Sent")
The problem is I currently see now way to properly enlist asynchronous code inside the enlistment notification. Any ideas how to tackle this challenge?

Categories